Skip to main content

triblespace_core/inline/encodings/
iu256.rs

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