Skip to main content

triblespace_core/value/schemas/
iu256.rs

1use crate::id::ExclusiveId;
2use crate::id::Id;
3use crate::id_hex;
4use crate::macros::entity;
5use crate::metadata;
6use crate::metadata::{ConstDescribe, ConstId};
7use crate::repo::BlobStore;
8use crate::trible::Fragment;
9use crate::value::schemas::hash::Blake3;
10use crate::value::ToValue;
11use crate::value::TryFromValue;
12use crate::value::Value;
13use crate::value::ValueSchema;
14use std::convert::Infallible;
15use std::num::TryFromIntError;
16
17use ethnum;
18
19/// A value schema for a 256-bit unsigned integer in little-endian byte order.
20pub struct U256LE;
21
22impl ConstId for U256LE {
23    const ID: Id = id_hex!("49E70B4DBD84DC7A3E0BDDABEC8A8C6E");
24}
25
26/// A value schema for a 256-bit unsigned integer in big-endian byte order.
27pub struct U256BE;
28
29impl ConstId for U256BE {
30    const ID: Id = id_hex!("DC3CFB719B05F019FB8101A6F471A982");
31}
32
33/// A value schema for a 256-bit signed integer in little-endian byte order.
34pub struct I256LE;
35
36impl ConstId for I256LE {
37    const ID: Id = id_hex!("DB94325A37D96037CBFC6941A4C3B66D");
38}
39
40/// A value schema for a 256-bit signed integer in big-endian byte order.
41pub struct I256BE;
42
43impl ConstId for I256BE {
44    const ID: Id = id_hex!("CE3A7839231F1EB390E9E8E13DAED782");
45}
46
47/// A type alias for a 256-bit signed integer.
48/// This type is an alias for [I256BE].
49pub type I256 = I256BE;
50
51/// A type alias for a 256-bit unsigned integer.
52/// This type is an alias for [U256BE].
53pub type U256 = U256BE;
54
55impl ConstDescribe for U256LE {
56    fn describe<B>(blobs: &mut B) -> Result<Fragment, B::PutError>
57    where
58        B: BlobStore<Blake3>,
59    {
60        let id = Self::ID;
61        let description = blobs.put(
62            "Unsigned 256-bit integer stored in little-endian byte order. The full 32 bytes are dedicated to the magnitude.\n\nUse for large counters, identifiers, or domain-specific fixed-width numbers that exceed u128. Prefer U256BE when bytewise ordering or protocol encoding matters.\n\nIf a smaller width suffices, prefer U64 or U128 in your schema to reduce storage and improve readability.",
63        )?;
64        let tribles = entity! {
65            ExclusiveId::force_ref(&id) @
66                metadata::name: blobs.put("u256le")?,
67                metadata::description: description,
68                metadata::tag: metadata::KIND_VALUE_SCHEMA,
69        };
70
71        #[cfg(feature = "wasm")]
72        let tribles = {
73            let mut tribles = tribles;
74            tribles += entity! { ExclusiveId::force_ref(&id) @
75                metadata::value_formatter: blobs.put(wasm_formatter::U256_LE_WASM)?,
76            };
77            tribles
78        };
79        Ok(tribles)
80    }
81}
82impl ValueSchema for U256LE {
83    type ValidationError = Infallible;
84}
85impl ConstDescribe for U256BE {
86    fn describe<B>(blobs: &mut B) -> Result<Fragment, B::PutError>
87    where
88        B: BlobStore<Blake3>,
89    {
90        let id = Self::ID;
91        let description = blobs.put(
92            "Unsigned 256-bit integer stored in big-endian byte order. Bytewise comparisons align with numeric order.\n\nUse when ordering or network serialization matters. Prefer U256LE for local storage or interop with little-endian APIs.\n\nIf you do not need the full 256-bit range, smaller integer schemas are easier to handle and faster to encode.",
93        )?;
94        let tribles = entity! {
95            ExclusiveId::force_ref(&id) @
96                metadata::name: blobs.put("u256be")?,
97                metadata::description: description,
98                metadata::tag: metadata::KIND_VALUE_SCHEMA,
99        };
100
101        #[cfg(feature = "wasm")]
102        let tribles = {
103            let mut tribles = tribles;
104            tribles += entity! { ExclusiveId::force_ref(&id) @
105                metadata::value_formatter: blobs.put(wasm_formatter::U256_BE_WASM)?,
106            };
107            tribles
108        };
109        Ok(tribles)
110    }
111}
112impl ValueSchema for U256BE {
113    type ValidationError = Infallible;
114}
115impl ConstDescribe for I256LE {
116    fn describe<B>(blobs: &mut B) -> Result<Fragment, B::PutError>
117    where
118        B: BlobStore<Blake3>,
119    {
120        let id = Self::ID;
121        let description = blobs.put(
122            "Signed 256-bit integer stored in little-endian twos-complement. This enables extremely large signed ranges in a fixed width.\n\nUse for large signed quantities such as balances or offsets beyond i128. Prefer I256BE when bytewise ordering or external protocols require big-endian.\n\nIf values fit within i64 or i128, smaller schemas are more compact and easier to interoperate with.",
123        )?;
124        let tribles = entity! {
125            ExclusiveId::force_ref(&id) @
126                metadata::name: blobs.put("i256le")?,
127                metadata::description: description,
128                metadata::tag: metadata::KIND_VALUE_SCHEMA,
129        };
130
131        #[cfg(feature = "wasm")]
132        let tribles = {
133            let mut tribles = tribles;
134            tribles += entity! { ExclusiveId::force_ref(&id) @
135                metadata::value_formatter: blobs.put(wasm_formatter::I256_LE_WASM)?,
136            };
137            tribles
138        };
139        Ok(tribles)
140    }
141}
142impl ValueSchema for I256LE {
143    type ValidationError = Infallible;
144}
145impl ConstDescribe for I256BE {
146    fn describe<B>(blobs: &mut B) -> Result<Fragment, B::PutError>
147    where
148        B: BlobStore<Blake3>,
149    {
150        let id = Self::ID;
151        let description = blobs.put(
152            "Signed 256-bit integer stored in big-endian twos-complement. This variant is convenient for protocol encoding and deterministic ordering.\n\nUse for interoperability or stable bytewise comparisons across systems. Prefer I256LE for local storage or when endianness does not matter.\n\nAs with any signed integer, consider whether the sign bit has semantic meaning and avoid mixing signed and unsigned ranges.",
153        )?;
154        let tribles = entity! {
155            ExclusiveId::force_ref(&id) @
156                metadata::name: blobs.put("i256be")?,
157                metadata::description: description,
158                metadata::tag: metadata::KIND_VALUE_SCHEMA,
159        };
160
161        #[cfg(feature = "wasm")]
162        let tribles = {
163            let mut tribles = tribles;
164            tribles += entity! { ExclusiveId::force_ref(&id) @
165                metadata::value_formatter: blobs.put(wasm_formatter::I256_BE_WASM)?,
166            };
167            tribles
168        };
169        Ok(tribles)
170    }
171}
172impl ValueSchema for I256BE {
173    type ValidationError = Infallible;
174}
175
176#[cfg(feature = "wasm")]
177mod wasm_formatter {
178    use core::fmt::Write;
179
180    use triblespace_core_macros::value_formatter;
181
182    #[value_formatter(const_wasm = U256_LE_WASM)]
183    pub(crate) fn u256_le(raw: &[u8; 32], out: &mut impl Write) -> Result<(), u32> {
184        fn div_mod10(limbs: &mut [u64; 4]) -> u8 {
185            let mut rem: u128 = 0;
186            for limb in limbs.iter_mut() {
187                let n = (rem << 64) | (*limb as u128);
188                *limb = (n / 10) as u64;
189                rem = n % 10;
190            }
191            rem as u8
192        }
193
194        fn is_zero(limbs: &[u64; 4]) -> bool {
195            limbs.iter().all(|&limb| limb == 0)
196        }
197
198        let mut buf = [0u8; 8];
199        buf.copy_from_slice(&raw[0..8]);
200        let w0 = u64::from_le_bytes(buf);
201        buf.copy_from_slice(&raw[8..16]);
202        let w1 = u64::from_le_bytes(buf);
203        buf.copy_from_slice(&raw[16..24]);
204        let w2 = u64::from_le_bytes(buf);
205        buf.copy_from_slice(&raw[24..32]);
206        let w3 = u64::from_le_bytes(buf);
207
208        let mut limbs = [w3, w2, w1, w0];
209        if is_zero(&limbs) {
210            out.write_char('0').map_err(|_| 1u32)?;
211            return Ok(());
212        }
213
214        let mut digits = [0u8; 78];
215        let mut len = 0usize;
216        while !is_zero(&limbs) {
217            let digit = div_mod10(&mut limbs);
218            digits[len] = b'0' + digit;
219            len += 1;
220        }
221
222        for &digit in digits[..len].iter().rev() {
223            out.write_char(digit as char).map_err(|_| 1u32)?;
224        }
225
226        Ok(())
227    }
228
229    #[value_formatter(const_wasm = U256_BE_WASM)]
230    pub(crate) fn u256_be(raw: &[u8; 32], out: &mut impl Write) -> Result<(), u32> {
231        fn div_mod10(limbs: &mut [u64; 4]) -> u8 {
232            let mut rem: u128 = 0;
233            for limb in limbs.iter_mut() {
234                let n = (rem << 64) | (*limb as u128);
235                *limb = (n / 10) as u64;
236                rem = n % 10;
237            }
238            rem as u8
239        }
240
241        fn is_zero(limbs: &[u64; 4]) -> bool {
242            limbs.iter().all(|&limb| limb == 0)
243        }
244
245        let mut buf = [0u8; 8];
246        buf.copy_from_slice(&raw[0..8]);
247        let w0 = u64::from_be_bytes(buf);
248        buf.copy_from_slice(&raw[8..16]);
249        let w1 = u64::from_be_bytes(buf);
250        buf.copy_from_slice(&raw[16..24]);
251        let w2 = u64::from_be_bytes(buf);
252        buf.copy_from_slice(&raw[24..32]);
253        let w3 = u64::from_be_bytes(buf);
254
255        let mut limbs = [w0, w1, w2, w3];
256        if is_zero(&limbs) {
257            out.write_char('0').map_err(|_| 1u32)?;
258            return Ok(());
259        }
260
261        let mut digits = [0u8; 78];
262        let mut len = 0usize;
263        while !is_zero(&limbs) {
264            let digit = div_mod10(&mut limbs);
265            digits[len] = b'0' + digit;
266            len += 1;
267        }
268
269        for &digit in digits[..len].iter().rev() {
270            out.write_char(digit as char).map_err(|_| 1u32)?;
271        }
272
273        Ok(())
274    }
275
276    #[value_formatter(const_wasm = I256_LE_WASM)]
277    pub(crate) fn i256_le(raw: &[u8; 32], out: &mut impl Write) -> Result<(), u32> {
278        fn div_mod10(limbs: &mut [u64; 4]) -> u8 {
279            let mut rem: u128 = 0;
280            for limb in limbs.iter_mut() {
281                let n = (rem << 64) | (*limb as u128);
282                *limb = (n / 10) as u64;
283                rem = n % 10;
284            }
285            rem as u8
286        }
287
288        fn is_zero(limbs: &[u64; 4]) -> bool {
289            limbs.iter().all(|&limb| limb == 0)
290        }
291
292        fn twos_complement(limbs: &mut [u64; 4]) {
293            for limb in limbs.iter_mut() {
294                *limb = !*limb;
295            }
296
297            let mut carry: u128 = 1;
298            for limb in limbs.iter_mut().rev() {
299                let sum = (*limb as u128) + carry;
300                *limb = sum as u64;
301                carry = sum >> 64;
302                if carry == 0 {
303                    break;
304                }
305            }
306        }
307
308        let mut buf = [0u8; 8];
309        buf.copy_from_slice(&raw[0..8]);
310        let w0 = u64::from_le_bytes(buf);
311        buf.copy_from_slice(&raw[8..16]);
312        let w1 = u64::from_le_bytes(buf);
313        buf.copy_from_slice(&raw[16..24]);
314        let w2 = u64::from_le_bytes(buf);
315        buf.copy_from_slice(&raw[24..32]);
316        let w3 = u64::from_le_bytes(buf);
317
318        let mut limbs = [w3, w2, w1, w0];
319        let negative = (limbs[0] & (1u64 << 63)) != 0;
320        if negative {
321            twos_complement(&mut limbs);
322        }
323
324        if is_zero(&limbs) {
325            out.write_char('0').map_err(|_| 1u32)?;
326            return Ok(());
327        }
328
329        let mut digits = [0u8; 78];
330        let mut len = 0usize;
331        while !is_zero(&limbs) {
332            let digit = div_mod10(&mut limbs);
333            digits[len] = b'0' + digit;
334            len += 1;
335        }
336
337        if negative {
338            out.write_char('-').map_err(|_| 1u32)?;
339        }
340
341        for &digit in digits[..len].iter().rev() {
342            out.write_char(digit as char).map_err(|_| 1u32)?;
343        }
344
345        Ok(())
346    }
347
348    #[value_formatter(const_wasm = I256_BE_WASM)]
349    pub(crate) fn i256_be(raw: &[u8; 32], out: &mut impl Write) -> Result<(), u32> {
350        fn div_mod10(limbs: &mut [u64; 4]) -> u8 {
351            let mut rem: u128 = 0;
352            for limb in limbs.iter_mut() {
353                let n = (rem << 64) | (*limb as u128);
354                *limb = (n / 10) as u64;
355                rem = n % 10;
356            }
357            rem as u8
358        }
359
360        fn is_zero(limbs: &[u64; 4]) -> bool {
361            limbs.iter().all(|&limb| limb == 0)
362        }
363
364        fn twos_complement(limbs: &mut [u64; 4]) {
365            for limb in limbs.iter_mut() {
366                *limb = !*limb;
367            }
368
369            let mut carry: u128 = 1;
370            for limb in limbs.iter_mut().rev() {
371                let sum = (*limb as u128) + carry;
372                *limb = sum as u64;
373                carry = sum >> 64;
374                if carry == 0 {
375                    break;
376                }
377            }
378        }
379
380        let mut buf = [0u8; 8];
381        buf.copy_from_slice(&raw[0..8]);
382        let w0 = u64::from_be_bytes(buf);
383        buf.copy_from_slice(&raw[8..16]);
384        let w1 = u64::from_be_bytes(buf);
385        buf.copy_from_slice(&raw[16..24]);
386        let w2 = u64::from_be_bytes(buf);
387        buf.copy_from_slice(&raw[24..32]);
388        let w3 = u64::from_be_bytes(buf);
389
390        let mut limbs = [w0, w1, w2, w3];
391        let negative = (limbs[0] & (1u64 << 63)) != 0;
392        if negative {
393            twos_complement(&mut limbs);
394        }
395
396        if is_zero(&limbs) {
397            out.write_char('0').map_err(|_| 1u32)?;
398            return Ok(());
399        }
400
401        let mut digits = [0u8; 78];
402        let mut len = 0usize;
403        while !is_zero(&limbs) {
404            let digit = div_mod10(&mut limbs);
405            digits[len] = b'0' + digit;
406            len += 1;
407        }
408
409        if negative {
410            out.write_char('-').map_err(|_| 1u32)?;
411        }
412
413        for &digit in digits[..len].iter().rev() {
414            out.write_char(digit as char).map_err(|_| 1u32)?;
415        }
416
417        Ok(())
418    }
419}
420
421impl ToValue<U256BE> for ethnum::U256 {
422    fn to_value(self) -> Value<U256BE> {
423        Value::new(self.to_be_bytes())
424    }
425}
426
427impl TryFromValue<'_, U256BE> for ethnum::U256 {
428    type Error = Infallible;
429    fn try_from_value(v: &Value<U256BE>) -> Result<Self, Infallible> {
430        Ok(ethnum::U256::from_be_bytes(v.raw))
431    }
432}
433
434impl ToValue<U256LE> for ethnum::U256 {
435    fn to_value(self) -> Value<U256LE> {
436        Value::new(self.to_le_bytes())
437    }
438}
439
440impl TryFromValue<'_, U256LE> for ethnum::U256 {
441    type Error = Infallible;
442    fn try_from_value(v: &Value<U256LE>) -> Result<Self, Infallible> {
443        Ok(ethnum::U256::from_le_bytes(v.raw))
444    }
445}
446
447impl ToValue<I256BE> for ethnum::I256 {
448    fn to_value(self) -> Value<I256BE> {
449        Value::new(self.to_be_bytes())
450    }
451}
452
453impl TryFromValue<'_, I256BE> for ethnum::I256 {
454    type Error = Infallible;
455    fn try_from_value(v: &Value<I256BE>) -> Result<Self, Infallible> {
456        Ok(ethnum::I256::from_be_bytes(v.raw))
457    }
458}
459
460impl ToValue<I256LE> for ethnum::I256 {
461    fn to_value(self) -> Value<I256LE> {
462        Value::new(self.to_le_bytes())
463    }
464}
465
466impl TryFromValue<'_, I256LE> for ethnum::I256 {
467    type Error = Infallible;
468    fn try_from_value(v: &Value<I256LE>) -> Result<Self, Infallible> {
469        Ok(ethnum::I256::from_le_bytes(v.raw))
470    }
471}
472
473impl ToValue<U256LE> for u8 {
474    fn to_value(self) -> Value<U256LE> {
475        Value::new(ethnum::U256::new(self.into()).to_le_bytes())
476    }
477}
478
479impl ToValue<U256LE> for u16 {
480    fn to_value(self) -> Value<U256LE> {
481        Value::new(ethnum::U256::new(self.into()).to_le_bytes())
482    }
483}
484
485impl ToValue<U256LE> for u32 {
486    fn to_value(self) -> Value<U256LE> {
487        Value::new(ethnum::U256::new(self.into()).to_le_bytes())
488    }
489}
490
491impl ToValue<U256LE> for u64 {
492    fn to_value(self) -> Value<U256LE> {
493        Value::new(ethnum::U256::new(self.into()).to_le_bytes())
494    }
495}
496
497impl ToValue<U256LE> for u128 {
498    fn to_value(self) -> Value<U256LE> {
499        Value::new(ethnum::U256::new(self).to_le_bytes())
500    }
501}
502
503impl ToValue<U256BE> for u8 {
504    fn to_value(self) -> Value<U256BE> {
505        Value::new(ethnum::U256::new(self.into()).to_be_bytes())
506    }
507}
508
509impl ToValue<U256BE> for u16 {
510    fn to_value(self) -> Value<U256BE> {
511        Value::new(ethnum::U256::new(self.into()).to_be_bytes())
512    }
513}
514
515impl ToValue<U256BE> for u32 {
516    fn to_value(self) -> Value<U256BE> {
517        Value::new(ethnum::U256::new(self.into()).to_be_bytes())
518    }
519}
520
521impl ToValue<U256BE> for u64 {
522    fn to_value(self) -> Value<U256BE> {
523        Value::new(ethnum::U256::new(self.into()).to_be_bytes())
524    }
525}
526
527impl ToValue<U256BE> for u128 {
528    fn to_value(self) -> Value<U256BE> {
529        Value::new(ethnum::U256::new(self).to_be_bytes())
530    }
531}
532
533impl ToValue<I256LE> for i8 {
534    fn to_value(self) -> Value<I256LE> {
535        Value::new(ethnum::I256::new(self.into()).to_le_bytes())
536    }
537}
538
539impl ToValue<I256LE> for i16 {
540    fn to_value(self) -> Value<I256LE> {
541        Value::new(ethnum::I256::new(self.into()).to_le_bytes())
542    }
543}
544
545impl ToValue<I256LE> for i32 {
546    fn to_value(self) -> Value<I256LE> {
547        Value::new(ethnum::I256::new(self.into()).to_le_bytes())
548    }
549}
550
551impl ToValue<I256LE> for i64 {
552    fn to_value(self) -> Value<I256LE> {
553        Value::new(ethnum::I256::new(self.into()).to_le_bytes())
554    }
555}
556
557impl ToValue<I256LE> for i128 {
558    fn to_value(self) -> Value<I256LE> {
559        Value::new(ethnum::I256::new(self).to_le_bytes())
560    }
561}
562
563impl ToValue<I256BE> for i8 {
564    fn to_value(self) -> Value<I256BE> {
565        Value::new(ethnum::I256::new(self.into()).to_be_bytes())
566    }
567}
568
569impl ToValue<I256BE> for i32 {
570    fn to_value(self) -> Value<I256BE> {
571        Value::new(ethnum::I256::new(self.into()).to_be_bytes())
572    }
573}
574
575impl ToValue<I256BE> for i64 {
576    fn to_value(self) -> Value<I256BE> {
577        Value::new(ethnum::I256::new(self.into()).to_be_bytes())
578    }
579}
580
581impl ToValue<I256BE> for i128 {
582    fn to_value(self) -> Value<I256BE> {
583        Value::new(ethnum::I256::new(self).to_be_bytes())
584    }
585}
586
587// --- Narrowing TryFromValue impls (U256 → native integers) ---
588
589macro_rules! impl_try_from_u256 {
590    ($schema:ty, $wide:ty, $($narrow:ty),+) => {
591        $(
592            impl TryFromValue<'_, $schema> for $narrow {
593                type Error = TryFromIntError;
594                fn try_from_value(v: &Value<$schema>) -> Result<Self, Self::Error> {
595                    let wide: $wide = v.from_value();
596                    <$narrow>::try_from(wide)
597                }
598            }
599        )+
600    };
601}
602
603impl_try_from_u256!(U256BE, ethnum::U256, u8, u16, u32, u64, u128);
604impl_try_from_u256!(U256LE, ethnum::U256, u8, u16, u32, u64, u128);
605impl_try_from_u256!(I256BE, ethnum::I256, i8, i16, i32, i64, i128);
606impl_try_from_u256!(I256LE, ethnum::I256, i8, i16, i32, i64, i128);
607
608#[cfg(test)]
609mod tests {
610    use super::*;
611    use crate::value::{ToValue, TryFromValue};
612    use proptest::prelude::*;
613
614    fn arb_u256() -> impl Strategy<Value = ethnum::U256> {
615        prop::array::uniform32(any::<u8>()).prop_map(ethnum::U256::from_be_bytes)
616    }
617
618    fn arb_i256() -> impl Strategy<Value = ethnum::I256> {
619        prop::array::uniform32(any::<u8>()).prop_map(ethnum::I256::from_be_bytes)
620    }
621
622    // --- U256BE property tests ---
623
624    proptest! {
625        #[test]
626        fn u256be_ethnum_roundtrip(input in arb_u256()) {
627            let value: Value<U256BE> = input.to_value();
628            let output: ethnum::U256 = value.from_value();
629            prop_assert_eq!(input, output);
630        }
631
632        #[test]
633        fn u256be_u128_roundtrip(input: u128) {
634            let value: Value<U256BE> = input.to_value();
635            let output = u128::try_from_value(&value).expect("fits in u128");
636            prop_assert_eq!(input, output);
637        }
638
639        #[test]
640        fn u256be_u64_roundtrip(input: u64) {
641            let value: Value<U256BE> = input.to_value();
642            let output = u64::try_from_value(&value).expect("fits in u64");
643            prop_assert_eq!(input, output);
644        }
645
646        #[test]
647        fn u256be_u32_roundtrip(input: u32) {
648            let value: Value<U256BE> = input.to_value();
649            let output = u32::try_from_value(&value).expect("fits in u32");
650            prop_assert_eq!(input, output);
651        }
652
653        #[test]
654        fn u256be_u16_roundtrip(input: u16) {
655            let value: Value<U256BE> = input.to_value();
656            let output = u16::try_from_value(&value).expect("fits in u16");
657            prop_assert_eq!(input, output);
658        }
659
660        #[test]
661        fn u256be_u8_roundtrip(input: u8) {
662            let value: Value<U256BE> = input.to_value();
663            let output = u8::try_from_value(&value).expect("fits in u8");
664            prop_assert_eq!(input, output);
665        }
666
667        #[test]
668        fn u256be_validates(input in arb_u256()) {
669            let value: Value<U256BE> = input.to_value();
670            prop_assert!(U256BE::validate(value).is_ok());
671        }
672
673        #[test]
674        fn u256be_order_preservation(a in arb_u256(), b in arb_u256()) {
675            let va: Value<U256BE> = a.to_value();
676            let vb: Value<U256BE> = b.to_value();
677            prop_assert_eq!(a.cmp(&b), va.raw.cmp(&vb.raw));
678        }
679
680        #[test]
681        fn u256be_widening_u64_u128(input: u64) {
682            let v64: Value<U256BE> = input.to_value();
683            let v128: Value<U256BE> = (input as u128).to_value();
684            prop_assert_eq!(v64.raw, v128.raw);
685        }
686
687        #[test]
688        fn u256be_widening_u32_u128(input: u32) {
689            let v32: Value<U256BE> = input.to_value();
690            let v128: Value<U256BE> = (input as u128).to_value();
691            prop_assert_eq!(v32.raw, v128.raw);
692        }
693
694        // --- U256LE property tests ---
695
696        #[test]
697        fn u256le_ethnum_roundtrip(input in arb_u256()) {
698            let value: Value<U256LE> = input.to_value();
699            let output: ethnum::U256 = value.from_value();
700            prop_assert_eq!(input, output);
701        }
702
703        #[test]
704        fn u256le_u128_roundtrip(input: u128) {
705            let value: Value<U256LE> = input.to_value();
706            let output = u128::try_from_value(&value).expect("fits in u128");
707            prop_assert_eq!(input, output);
708        }
709
710        #[test]
711        fn u256le_u64_roundtrip(input: u64) {
712            let value: Value<U256LE> = input.to_value();
713            let output = u64::try_from_value(&value).expect("fits in u64");
714            prop_assert_eq!(input, output);
715        }
716
717        #[test]
718        fn u256le_validates(input in arb_u256()) {
719            let value: Value<U256LE> = input.to_value();
720            prop_assert!(U256LE::validate(value).is_ok());
721        }
722
723        #[test]
724        fn u256_le_and_be_differ(input in arb_u256().prop_filter("non-zero", |v| *v != ethnum::U256::ZERO)) {
725            let le_val: Value<U256LE> = input.to_value();
726            let be_val: Value<U256BE> = input.to_value();
727            prop_assert_ne!(le_val.raw, be_val.raw);
728        }
729
730        // --- I256BE property tests ---
731
732        #[test]
733        fn i256be_ethnum_roundtrip(input in arb_i256()) {
734            let value: Value<I256BE> = input.to_value();
735            let output: ethnum::I256 = value.from_value();
736            prop_assert_eq!(input, output);
737        }
738
739        #[test]
740        fn i256be_i128_roundtrip(input: i128) {
741            let value: Value<I256BE> = input.to_value();
742            let output = i128::try_from_value(&value).expect("fits in i128");
743            prop_assert_eq!(input, output);
744        }
745
746        #[test]
747        fn i256be_i64_roundtrip(input: i64) {
748            let value: Value<I256BE> = input.to_value();
749            let output = i64::try_from_value(&value).expect("fits in i64");
750            prop_assert_eq!(input, output);
751        }
752
753        #[test]
754        fn i256be_i32_roundtrip(input: i32) {
755            let value: Value<I256BE> = input.to_value();
756            let output = i32::try_from_value(&value).expect("fits in i32");
757            prop_assert_eq!(input, output);
758        }
759
760        #[test]
761        fn i256be_i8_roundtrip(input: i8) {
762            let value: Value<I256BE> = input.to_value();
763            let output = i8::try_from_value(&value).expect("fits in i8");
764            prop_assert_eq!(input, output);
765        }
766
767        #[test]
768        fn i256be_validates(input in arb_i256()) {
769            let value: Value<I256BE> = input.to_value();
770            prop_assert!(I256BE::validate(value).is_ok());
771        }
772
773        // Note: I256BE uses raw two's complement, so bytewise order does NOT
774        // match signed numeric order (negative values sort after positive).
775
776        // --- I256LE property tests ---
777
778        #[test]
779        fn i256le_ethnum_roundtrip(input in arb_i256()) {
780            let value: Value<I256LE> = input.to_value();
781            let output: ethnum::I256 = value.from_value();
782            prop_assert_eq!(input, output);
783        }
784
785        #[test]
786        fn i256le_i128_roundtrip(input: i128) {
787            let value: Value<I256LE> = input.to_value();
788            let output = i128::try_from_value(&value).expect("fits in i128");
789            prop_assert_eq!(input, output);
790        }
791
792        #[test]
793        fn i256le_i64_roundtrip(input: i64) {
794            let value: Value<I256LE> = input.to_value();
795            let output = i64::try_from_value(&value).expect("fits in i64");
796            prop_assert_eq!(input, output);
797        }
798
799        #[test]
800        fn i256le_validates(input in arb_i256()) {
801            let value: Value<I256LE> = input.to_value();
802            prop_assert!(I256LE::validate(value).is_ok());
803        }
804
805        #[test]
806        fn i256_le_and_be_differ(input in arb_i256().prop_filter("non-zero", |v| *v != ethnum::I256::ZERO)) {
807            let le_val: Value<I256LE> = input.to_value();
808            let be_val: Value<I256BE> = input.to_value();
809            prop_assert_ne!(le_val.raw, be_val.raw);
810        }
811    }
812
813    // --- Narrowing overflow tests (specific invalid inputs) ---
814
815    #[test]
816    fn u256be_narrowing_overflow() {
817        let input = ethnum::U256::from(u128::MAX) + ethnum::U256::ONE;
818        let value: Value<U256BE> = input.to_value();
819        assert!(u128::try_from_value(&value).is_err());
820    }
821
822    #[test]
823    fn i256be_narrowing_overflow_positive() {
824        let input = ethnum::I256::from(i128::MAX) + ethnum::I256::ONE;
825        let value: Value<I256BE> = input.to_value();
826        assert!(i128::try_from_value(&value).is_err());
827    }
828
829    #[test]
830    fn i256be_narrowing_overflow_negative() {
831        let input = ethnum::I256::from(i128::MIN) - ethnum::I256::ONE;
832        let value: Value<I256BE> = input.to_value();
833        assert!(i128::try_from_value(&value).is_err());
834    }
835}