ethereum_mysql/
sql_uint.rs

1pub use alloy::primitives::Uint;
2pub use alloy::primitives::U256;
3use std::ops::Deref;
4use std::str::FromStr;
5
6#[cfg(feature = "serde")]
7use serde::{Deserialize, Serialize};
8
9mod convert;
10mod operation;
11mod primitive_ops;
12
13/// A SQL-compatible wrapper for 256-bit unsigned integers.
14///
15/// `SqlU256` wraps `alloy::primitives::U256` and implements all necessary traits
16/// for seamless SQLx database integration. It provides full arithmetic operations,
17/// type conversions, and consistent hexadecimal storage format across databases.
18///
19/// # Features
20///
21/// - **Arithmetic Operations**: Supports +, -, *, /, %, bitwise operations, and more
22/// - **Type Conversions**: Convert from/to various integer types with overflow checking
23/// - **Database Storage**: Consistent hexadecimal format (0x...) across all databases
24/// - **Input Flexibility**: `FromStr` accepts both decimal and hexadecimal strings
25/// - **SQLx Integration**: Implements `Type`, `Encode`, and `Decode` for MySQL, PostgreSQL, SQLite
26///
27/// # Examples
28///
29/// ```rust
30/// use ethereum_mysql::SqlU256;
31/// use alloy::primitives::U256;
32/// use std::str::FromStr;
33///
34/// // Create from various types
35/// let from_u64 = SqlU256::from(42u64);
36/// let from_decimal = SqlU256::from_str("123456789").unwrap();
37/// let from_hex = SqlU256::from_str("0x75bcd15").unwrap();
38/// let zero = SqlU256::ZERO;
39///
40/// // Arithmetic operations
41/// let a = SqlU256::from(100u64);
42/// let b = SqlU256::from(50u64);
43/// let sum = a + b;                    // 150
44/// let product = a * b;                // 5000
45/// let power = a.pow(2);               // 10000
46///
47/// // Safe operations
48/// let checked = a.checked_add(b);     // Some(150)
49/// let saturated = a.saturating_sub(SqlU256::from(200u64)); // 0
50///
51/// // Type conversions
52/// let back_to_u256: U256 = from_u64.into();  // SqlU256 -> U256 (always safe)
53/// let back_to_u64: u64 = from_u64.try_into().unwrap(); // SqlU256 -> u64 (may overflow)
54/// ```
55#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
56#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
57pub struct SqlUint<const BITS: usize, const LIMBS: usize>(Uint<BITS, LIMBS>);
58/// A type alias for a 256-bit unsigned integer, commonly used for Ethereum values.
59pub type SqlU256 = SqlUint<256, 4>;
60
61impl<const BITS: usize, const LIMBS: usize> SqlUint<BITS, LIMBS> {
62    /// Creates a new `SqlUint` from a `Uint` value.
63    ///
64    /// # Examples
65    ///
66    /// Equivalent to `SqlU256::from(0u64)` but available as a compile-time constant.
67    pub const ZERO: Self = SqlUint(Uint::ZERO);
68
69    /// Returns a reference to the inner `U256` value.
70    ///
71    /// This is useful when you need to interact with APIs that expect `U256` directly.
72    ///
73    /// # Examples
74    ///
75    /// ```rust
76    /// use ethereum_mysql::SqlU256;
77    ///
78    /// let sql_u256 = SqlU256::from(42u64);
79    /// let inner_ref: &alloy::primitives::U256 = sql_u256.inner();
80    /// ```
81    pub fn inner(&self) -> &Uint<BITS, LIMBS> {
82        &self.0
83    }
84
85    /// Consumes self and returns the inner Uint value.
86    pub fn into_inner(self) -> Uint<BITS, LIMBS> {
87        self.0
88    }
89}
90
91impl SqlU256 {
92    /// The number of wei in one ether (10^18).
93    pub const ETHER: Self = Self(U256::from_limbs([0x0, 0x8AC7230489E80000, 0, 0]));
94
95    /// Creates a SqlU256 from a big-endian byte slice (pads/truncates as alloy U256).
96    pub fn from_be_slice(bytes: &[u8]) -> Self {
97        Self(alloy::primitives::U256::from_be_slice(bytes))
98    }
99
100    /// Try to convert this value to u8. Returns Err if out of range.
101    pub fn as_u8(&self) -> Result<u8, &'static str> {
102        if self.0 > U256::from(u8::MAX) {
103            Err("SqlU256 value too large for u8")
104        } else {
105            Ok(self.0.to::<u8>())
106        }
107    }
108    /// Try to convert this value to u16. Returns Err if out of range.
109    pub fn as_u16(&self) -> Result<u16, &'static str> {
110        if self.0 > U256::from(u16::MAX) {
111            Err("SqlU256 value too large for u16")
112        } else {
113            Ok(self.0.to::<u16>())
114        }
115    }
116    /// Try to convert this value to u32. Returns Err if out of range.
117    pub fn as_u32(&self) -> Result<u32, &'static str> {
118        if self.0 > U256::from(u32::MAX) {
119            Err("SqlU256 value too large for u32")
120        } else {
121            Ok(self.0.to::<u32>())
122        }
123    }
124    /// Try to convert this value to u64. Returns Err if out of range.
125    pub fn as_u64(&self) -> Result<u64, &'static str> {
126        if self.0 > U256::from(u64::MAX) {
127            Err("SqlU256 value too large for u64")
128        } else {
129            Ok(self.0.to::<u64>())
130        }
131    }
132    /// Try to convert this value to u128. Returns Err if out of range.
133    pub fn as_u128(&self) -> Result<u128, &'static str> {
134        if self.0 > U256::from(u128::MAX) {
135            Err("SqlU256 value too large for u128")
136        } else {
137            Ok(self.0.to::<u128>())
138        }
139    }
140}
141
142impl<const BITS: usize, const LIMBS: usize> AsRef<Uint<BITS, LIMBS>> for SqlUint<BITS, LIMBS> {
143    fn as_ref(&self) -> &Uint<BITS, LIMBS> {
144        &self.0
145    }
146}
147impl<const BITS: usize, const LIMBS: usize> Deref for SqlUint<BITS, LIMBS> {
148    type Target = Uint<BITS, LIMBS>;
149
150    fn deref(&self) -> &Self::Target {
151        &self.0
152    }
153}
154
155impl<const BITS: usize, const LIMBS: usize> From<Uint<BITS, LIMBS>> for SqlUint<BITS, LIMBS> {
156    fn from(value: Uint<BITS, LIMBS>) -> Self {
157        SqlUint(value)
158    }
159}
160
161impl<const BITS: usize, const LIMBS: usize> From<SqlUint<BITS, LIMBS>> for Uint<BITS, LIMBS> {
162    fn from(value: SqlUint<BITS, LIMBS>) -> Self {
163        value.0
164    }
165}
166
167impl<const BITS: usize, const LIMBS: usize> FromStr for SqlUint<BITS, LIMBS> {
168    type Err = <Uint<BITS, LIMBS> as FromStr>::Err;
169
170    fn from_str(s: &str) -> Result<Self, Self::Err> {
171        Uint::from_str(s).map(SqlUint)
172    }
173}
174
175impl<const BITS: usize, const LIMBS: usize> std::fmt::Display for SqlUint<BITS, LIMBS> {
176    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
177        write!(f, "0x{:x}", self.0)
178    }
179}
180
181impl<const BITS: usize, const LIMBS: usize> std::fmt::LowerHex for SqlUint<BITS, LIMBS> {
182    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
183        self.0.fmt(f)
184    }
185}
186
187impl<const BITS: usize, const LIMBS: usize> std::fmt::UpperHex for SqlUint<BITS, LIMBS> {
188    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
189        self.0.fmt(f)
190    }
191}
192
193impl Default for SqlU256 {
194    fn default() -> Self {
195        SqlU256::ZERO
196    }
197}
198
199// Allow comparison between SqlU256 and primitive unsigned integer types
200impl PartialEq<u8> for crate::SqlU256 {
201    fn eq(&self, other: &u8) -> bool {
202        *self == crate::SqlU256::from(*other)
203    }
204}
205impl PartialEq<u16> for crate::SqlU256 {
206    fn eq(&self, other: &u16) -> bool {
207        *self == crate::SqlU256::from(*other)
208    }
209}
210impl PartialEq<u32> for crate::SqlU256 {
211    fn eq(&self, other: &u32) -> bool {
212        *self == crate::SqlU256::from(*other)
213    }
214}
215impl PartialEq<u64> for crate::SqlU256 {
216    fn eq(&self, other: &u64) -> bool {
217        *self == crate::SqlU256::from(*other)
218    }
219}
220impl PartialEq<u128> for crate::SqlU256 {
221    fn eq(&self, other: &u128) -> bool {
222        *self == crate::SqlU256::from(*other)
223    }
224}
225
226impl PartialOrd<u8> for crate::SqlU256 {
227    fn partial_cmp(&self, other: &u8) -> Option<std::cmp::Ordering> {
228        self.partial_cmp(&crate::SqlU256::from(*other))
229    }
230}
231impl PartialOrd<u16> for crate::SqlU256 {
232    fn partial_cmp(&self, other: &u16) -> Option<std::cmp::Ordering> {
233        self.partial_cmp(&crate::SqlU256::from(*other))
234    }
235}
236impl PartialOrd<u32> for crate::SqlU256 {
237    fn partial_cmp(&self, other: &u32) -> Option<std::cmp::Ordering> {
238        self.partial_cmp(&crate::SqlU256::from(*other))
239    }
240}
241impl PartialOrd<u64> for crate::SqlU256 {
242    fn partial_cmp(&self, other: &u64) -> Option<std::cmp::Ordering> {
243        self.partial_cmp(&crate::SqlU256::from(*other))
244    }
245}
246impl PartialOrd<u128> for crate::SqlU256 {
247    fn partial_cmp(&self, other: &u128) -> Option<std::cmp::Ordering> {
248        self.partial_cmp(&crate::SqlU256::from(*other))
249    }
250}
251
252// Allow reverse comparison: primitive unsigned integer types vs SqlU256
253impl PartialEq<crate::SqlU256> for u8 {
254    fn eq(&self, other: &crate::SqlU256) -> bool {
255        crate::SqlU256::from(*self) == *other
256    }
257}
258impl PartialEq<crate::SqlU256> for u16 {
259    fn eq(&self, other: &crate::SqlU256) -> bool {
260        crate::SqlU256::from(*self) == *other
261    }
262}
263impl PartialEq<crate::SqlU256> for u32 {
264    fn eq(&self, other: &crate::SqlU256) -> bool {
265        crate::SqlU256::from(*self) == *other
266    }
267}
268impl PartialEq<crate::SqlU256> for u64 {
269    fn eq(&self, other: &crate::SqlU256) -> bool {
270        crate::SqlU256::from(*self) == *other
271    }
272}
273impl PartialEq<crate::SqlU256> for u128 {
274    fn eq(&self, other: &crate::SqlU256) -> bool {
275        crate::SqlU256::from(*self) == *other
276    }
277}
278
279impl PartialOrd<crate::SqlU256> for u8 {
280    fn partial_cmp(&self, other: &crate::SqlU256) -> Option<std::cmp::Ordering> {
281        crate::SqlU256::from(*self).partial_cmp(other)
282    }
283}
284impl PartialOrd<crate::SqlU256> for u16 {
285    fn partial_cmp(&self, other: &crate::SqlU256) -> Option<std::cmp::Ordering> {
286        crate::SqlU256::from(*self).partial_cmp(other)
287    }
288}
289impl PartialOrd<crate::SqlU256> for u32 {
290    fn partial_cmp(&self, other: &crate::SqlU256) -> Option<std::cmp::Ordering> {
291        crate::SqlU256::from(*self).partial_cmp(other)
292    }
293}
294impl PartialOrd<crate::SqlU256> for u64 {
295    fn partial_cmp(&self, other: &crate::SqlU256) -> Option<std::cmp::Ordering> {
296        crate::SqlU256::from(*self).partial_cmp(other)
297    }
298}
299impl PartialOrd<crate::SqlU256> for u128 {
300    fn partial_cmp(&self, other: &crate::SqlU256) -> Option<std::cmp::Ordering> {
301        crate::SqlU256::from(*self).partial_cmp(other)
302    }
303}
304
305// Fallible conversions: SqlU256 -> u8/u16/u32/u64/u128
306
307#[cfg(test)]
308mod tests {
309    use super::*;
310
311    #[cfg(feature = "serde")]
312    #[test]
313    fn test_serde() {
314        let value = "0x12345678";
315        let s_value = SqlUint::<32, 1>::from_str(value).unwrap();
316        let json = serde_json::to_string(&s_value).unwrap();
317        assert_eq!(json, "\"0x12345678\"");
318        let de: SqlUint<32, 1> = serde_json::from_str(&json).unwrap();
319        assert_eq!(s_value, de);
320    }
321
322    #[test]
323    fn test_creation_and_constants() {
324        // Test ZERO constant
325        let zero = SqlU256::ZERO;
326        assert_eq!(zero, SqlU256::from(0u64));
327
328        // Test from() constructor
329        let value = SqlU256::from(U256::from(42u64));
330        assert_eq!(value, SqlU256::from(42u64));
331    }
332
333    #[test]
334    fn test_from_conversions() {
335        // Test From<U256> for SqlU256
336        let u256_val = U256::from(123456789u64);
337        let sql_u256 = SqlU256::from(u256_val);
338        assert_eq!(sql_u256.inner(), &u256_val);
339
340        // Test From<SqlU256> for U256
341        let back_to_u256: U256 = sql_u256.into();
342        assert_eq!(back_to_u256, u256_val);
343    }
344
345    #[test]
346    fn test_inner_and_deref() {
347        let sql_u256 = SqlU256::from(42u64);
348
349        // Test inner() method
350        let inner_ref: &U256 = sql_u256.inner();
351        assert_eq!(*inner_ref, U256::from(42u64));
352
353        // Test AsRef trait
354        let as_ref: &U256 = sql_u256.as_ref();
355        assert_eq!(*as_ref, U256::from(42u64));
356
357        // Test Deref trait (automatic dereferencing)
358        let deref_val: U256 = *sql_u256;
359        assert_eq!(deref_val, U256::from(42u64));
360    }
361
362    #[test]
363    fn test_from_str_parsing() {
364        // Test decimal string parsing
365        let from_decimal = SqlU256::from_str("123456789").unwrap();
366        assert_eq!(from_decimal, SqlU256::from(123456789u64));
367
368        // Test hexadecimal string parsing
369        let from_hex = SqlU256::from_str("0x75bcd15").unwrap();
370        assert_eq!(from_hex, SqlU256::from(123456789u64));
371
372        // Test that decimal and hex produce same result
373        assert_eq!(from_decimal, from_hex);
374
375        // Test zero parsing
376        let zero_decimal = SqlU256::from_str("0").unwrap();
377        let zero_hex = SqlU256::from_str("0x0").unwrap();
378        assert_eq!(zero_decimal, zero_hex);
379        assert_eq!(zero_decimal, SqlU256::ZERO);
380    }
381
382    #[test]
383    fn test_from_str_edge_cases() {
384        // Test maximum value
385        let max_hex = format!("0x{:x}", U256::MAX);
386        let max_sql = SqlU256::from_str(&max_hex).unwrap();
387        assert_eq!(max_sql.inner(), &U256::MAX);
388
389        // Test U256's lenient parsing behavior - these all parse as zero
390        let zero_cases = [
391            ("", "empty string"),
392            ("0", "zero"),
393            ("00", "double zero"),
394            ("0x", "just 0x prefix"),
395            ("0x0", "0x0"),
396            ("0x00", "0x00"),
397        ];
398
399        for (input, _desc) in zero_cases {
400            let result = SqlU256::from_str(input).unwrap();
401            assert_eq!(
402                result,
403                SqlU256::ZERO,
404                "Input '{}' should parse as zero",
405                input
406            );
407        }
408
409        // Test clearly invalid strings that should fail
410        assert!(SqlU256::from_str("not_a_number").is_err());
411        assert!(SqlU256::from_str("0xnot_hex").is_err());
412        assert!(SqlU256::from_str("123abc").is_err());
413        assert!(SqlU256::from_str("0x123xyz").is_err());
414    }
415
416    #[test]
417    fn test_display_formatting() {
418        let test_cases = [
419            (0u64, "0x0"),
420            (255u64, "0xff"),
421            (0xdeadbeef_u64, "0xdeadbeef"),
422            (123456789u64, "0x75bcd15"),
423        ];
424
425        for (input, expected) in test_cases {
426            let sql_u256 = SqlU256::from(input);
427            let display_str = format!("{}", sql_u256);
428            assert_eq!(display_str, expected);
429        }
430    }
431
432    #[test]
433    fn test_round_trip_consistency() {
434        let test_values = [
435            U256::from(0u64),
436            U256::from(1u64),
437            U256::from(255u64),
438            U256::from(0xdeadbeef_u64),
439            U256::from(123456789u64),
440            U256::MAX,
441        ];
442
443        for original_u256 in test_values {
444            let sql_u256 = SqlU256::from(original_u256);
445
446            // Test Display -> FromStr round trip
447            let display_str = format!("{}", sql_u256);
448            let parsed_back = SqlU256::from_str(&display_str).unwrap();
449            assert_eq!(sql_u256, parsed_back);
450
451            // Test U256 conversion round trip
452            let back_to_u256: U256 = sql_u256.into();
453            assert_eq!(back_to_u256, original_u256);
454        }
455    }
456
457    #[test]
458    fn test_equality_and_comparison() {
459        let a = SqlU256::from(100u64);
460        let b = SqlU256::from(100u64);
461        let c = SqlU256::from(200u64);
462
463        // Test equality
464        assert_eq!(a, b);
465        assert_ne!(a, c);
466
467        // Test with ZERO constant
468        let zero1 = SqlU256::ZERO;
469        let zero2 = SqlU256::from(0u64);
470        assert_eq!(zero1, zero2);
471    }
472
473    #[test]
474    fn test_clone_and_copy() {
475        let original = SqlU256::from(42u64);
476
477        // Test Clone
478        let cloned = original.clone();
479        assert_eq!(original, cloned);
480
481        // Test Copy (implicit)
482        let copied = original;
483        assert_eq!(original, copied);
484
485        // Original should still be usable (Copy semantics)
486        assert_eq!(original, SqlU256::from(42u64));
487    }
488
489    #[test]
490    fn test_debug_formatting() {
491        let sql_u256 = SqlU256::from(42u64);
492        let debug_str = format!("{:?}", sql_u256);
493        // Should contain the inner SqlUint value
494        assert!(debug_str.contains("SqlUint"));
495    }
496
497    #[test]
498    fn test_sql_u256_hash() {
499        use std::collections::{HashMap, HashSet};
500
501        let val1 = SqlU256::from(123u64);
502        let val2 = SqlU256::from(123u64);
503        let val3 = SqlU256::from(456u64);
504
505        // Test Hash trait - equal values should have equal hashes
506        use std::hash::{DefaultHasher, Hash, Hasher};
507
508        let mut hasher1 = DefaultHasher::new();
509        let mut hasher2 = DefaultHasher::new();
510        let mut hasher3 = DefaultHasher::new();
511
512        val1.hash(&mut hasher1);
513        val2.hash(&mut hasher2);
514        val3.hash(&mut hasher3);
515
516        assert_eq!(hasher1.finish(), hasher2.finish());
517        assert_ne!(hasher1.finish(), hasher3.finish());
518
519        // Test usage in HashSet
520        let mut value_set = HashSet::new();
521        value_set.insert(val1);
522        value_set.insert(val2); // Should not increase size since val1 == val2
523        value_set.insert(val3);
524        value_set.insert(SqlU256::ZERO);
525
526        assert_eq!(value_set.len(), 3);
527        assert!(value_set.contains(&val1));
528        assert!(value_set.contains(&val2));
529        assert!(value_set.contains(&val3));
530        assert!(value_set.contains(&SqlU256::ZERO));
531
532        // Test usage in HashMap
533        let mut value_map = HashMap::new();
534        value_map.insert(val1, "First value");
535        value_map.insert(val2, "Same value"); // Should overwrite
536        value_map.insert(val3, "Different value");
537        value_map.insert(SqlU256::ZERO, "Zero value");
538
539        assert_eq!(value_map.len(), 3);
540        assert_eq!(value_map.get(&val1), Some(&"Same value"));
541        assert_eq!(value_map.get(&val2), Some(&"Same value"));
542        assert_eq!(value_map.get(&val3), Some(&"Different value"));
543        assert_eq!(value_map.get(&SqlU256::ZERO), Some(&"Zero value"));
544
545        // Test with large values
546        let large1 =
547            SqlU256::from_str("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")
548                .unwrap();
549        let large2 = SqlU256::from_str(
550            "115792089237316195423570985008687907853269984665640564039457584007913129639935",
551        )
552        .unwrap(); // Same as large1 in decimal
553
554        let mut large_hasher1 = DefaultHasher::new();
555        let mut large_hasher2 = DefaultHasher::new();
556
557        large1.hash(&mut large_hasher1);
558        large2.hash(&mut large_hasher2);
559
560        assert_eq!(large_hasher1.finish(), large_hasher2.finish());
561        assert_eq!(large1, large2);
562    }
563
564    #[test]
565    fn test_sql_u256_hash_consistency_with_alloy_u256() {
566        use std::hash::{DefaultHasher, Hash, Hasher};
567
568        fn calculate_hash<T: Hash>(t: &T) -> u64 {
569            let mut hasher = DefaultHasher::new();
570            t.hash(&mut hasher);
571            hasher.finish()
572        }
573
574        let test_values = [
575            "0",
576            "42",
577            "1000000000000000000",
578            "0x75bcd15",
579            "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
580        ];
581
582        for value_str in &test_values {
583            let alloy_u256 = U256::from_str(value_str).unwrap();
584            let sql_u256 = SqlU256::from_str(value_str).unwrap();
585
586            let alloy_hash = calculate_hash(&alloy_u256);
587            let sql_hash = calculate_hash(&sql_u256);
588
589            // Critical: SqlU256 must produce the same hash as the underlying U256
590            assert_eq!(
591                alloy_hash, sql_hash,
592                "Hash mismatch for value {}: alloy={}, sql={}",
593                value_str, alloy_hash, sql_hash
594            );
595        }
596
597        // Test conversion consistency
598        let original = U256::from(12345u64);
599        let sql_wrapped = SqlU256::from(original);
600        let converted_back: U256 = sql_wrapped.into();
601
602        assert_eq!(calculate_hash(&original), calculate_hash(&sql_wrapped));
603        assert_eq!(calculate_hash(&original), calculate_hash(&converted_back));
604        assert_eq!(
605            calculate_hash(&sql_wrapped),
606            calculate_hash(&converted_back)
607        );
608
609        // Test zero constant consistency
610        assert_eq!(calculate_hash(&U256::ZERO), calculate_hash(&SqlU256::ZERO));
611
612        // Test maximum value consistency
613        let max_alloy = U256::MAX;
614        let max_sql = SqlU256::from(max_alloy);
615        assert_eq!(calculate_hash(&max_alloy), calculate_hash(&max_sql));
616    }
617}