Skip to content

Commit 12c3cda

Browse files
committed
Implement CAP-82 checked {U,I}256 arith functions
1 parent 0e3eb16 commit 12c3cda

19 files changed

+1371
-0
lines changed

Cargo.lock

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

soroban-sdk/src/num.rs

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,58 @@ impl U256 {
311311
val,
312312
}
313313
}
314+
315+
/// Performs checked addition. Returns `None` if overflow occurred.
316+
pub fn checked_add(&self, other: &U256) -> Option<U256> {
317+
let val = self
318+
.env
319+
.u256_checked_add(self.val, other.val)
320+
.unwrap_infallible();
321+
if val.is_void() {
322+
None
323+
} else {
324+
Some(U256::try_from_val(&self.env, &val).unwrap_optimized())
325+
}
326+
}
327+
328+
/// Performs checked subtraction. Returns `None` if overflow occurred.
329+
pub fn checked_sub(&self, other: &U256) -> Option<U256> {
330+
let val = self
331+
.env
332+
.u256_checked_sub(self.val, other.val)
333+
.unwrap_infallible();
334+
if val.is_void() {
335+
None
336+
} else {
337+
Some(U256::try_from_val(&self.env, &val).unwrap_optimized())
338+
}
339+
}
340+
341+
/// Performs checked multiplication. Returns `None` if overflow occurred.
342+
pub fn checked_mul(&self, other: &U256) -> Option<U256> {
343+
let val = self
344+
.env
345+
.u256_checked_mul(self.val, other.val)
346+
.unwrap_infallible();
347+
if val.is_void() {
348+
None
349+
} else {
350+
Some(U256::try_from_val(&self.env, &val).unwrap_optimized())
351+
}
352+
}
353+
354+
/// Performs checked exponentiation. Returns `None` if overflow occurred.
355+
pub fn checked_pow(&self, pow: u32) -> Option<U256> {
356+
let val = self
357+
.env
358+
.u256_checked_pow(self.val, pow.into())
359+
.unwrap_infallible();
360+
if val.is_void() {
361+
None
362+
} else {
363+
Some(U256::try_from_val(&self.env, &val).unwrap_optimized())
364+
}
365+
}
314366
}
315367

316368
/// I256 holds a 256-bit signed integer.
@@ -462,6 +514,58 @@ impl I256 {
462514
val,
463515
}
464516
}
517+
518+
/// Performs checked addition. Returns `None` if overflow occurred.
519+
pub fn checked_add(&self, other: &I256) -> Option<I256> {
520+
let val = self
521+
.env
522+
.i256_checked_add(self.val, other.val)
523+
.unwrap_infallible();
524+
if val.is_void() {
525+
None
526+
} else {
527+
Some(I256::try_from_val(&self.env, &val).unwrap_optimized())
528+
}
529+
}
530+
531+
/// Performs checked subtraction. Returns `None` if overflow occurred.
532+
pub fn checked_sub(&self, other: &I256) -> Option<I256> {
533+
let val = self
534+
.env
535+
.i256_checked_sub(self.val, other.val)
536+
.unwrap_infallible();
537+
if val.is_void() {
538+
None
539+
} else {
540+
Some(I256::try_from_val(&self.env, &val).unwrap_optimized())
541+
}
542+
}
543+
544+
/// Performs checked multiplication. Returns `None` if overflow occurred.
545+
pub fn checked_mul(&self, other: &I256) -> Option<I256> {
546+
let val = self
547+
.env
548+
.i256_checked_mul(self.val, other.val)
549+
.unwrap_infallible();
550+
if val.is_void() {
551+
None
552+
} else {
553+
Some(I256::try_from_val(&self.env, &val).unwrap_optimized())
554+
}
555+
}
556+
557+
/// Performs checked exponentiation. Returns `None` if overflow occurred.
558+
pub fn checked_pow(&self, pow: u32) -> Option<I256> {
559+
let val = self
560+
.env
561+
.i256_checked_pow(self.val, pow.into())
562+
.unwrap_infallible();
563+
if val.is_void() {
564+
None
565+
} else {
566+
Some(I256::try_from_val(&self.env, &val).unwrap_optimized())
567+
}
568+
}
465569
}
466570

467571
#[doc = "Timepoint holds a 64-bit unsigned integer."]

soroban-sdk/src/tests.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ mod crypto_sha256;
4444
mod env;
4545
mod max_ttl;
4646
mod muxed_address;
47+
mod num_checked_arith;
4748
mod prng;
4849
mod prng_range;
4950
mod proptest_scval_cmp;
Lines changed: 248 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,248 @@
1+
use crate::{Env, I256, U256};
2+
3+
// ============================================================
4+
// U256 checked arithmetic
5+
// ============================================================
6+
7+
#[test]
8+
fn test_u256_checked_add_success() {
9+
let env = Env::default();
10+
let a = U256::from_u32(&env, 6);
11+
let b = U256::from_u32(&env, 3);
12+
assert_eq!(a.checked_add(&b), Some(U256::from_u32(&env, 9)));
13+
}
14+
15+
#[test]
16+
fn test_u256_checked_add_zero() {
17+
let env = Env::default();
18+
let a = U256::from_u32(&env, 42);
19+
let zero = U256::from_u32(&env, 0);
20+
assert_eq!(a.checked_add(&zero), Some(U256::from_u32(&env, 42)));
21+
}
22+
23+
#[test]
24+
fn test_u256_checked_add_overflow() {
25+
let env = Env::default();
26+
let max = U256::from_parts(&env, u64::MAX, u64::MAX, u64::MAX, u64::MAX);
27+
let one = U256::from_u32(&env, 1);
28+
assert_eq!(max.checked_add(&one), None);
29+
}
30+
31+
#[test]
32+
fn test_u256_checked_sub_success() {
33+
let env = Env::default();
34+
let a = U256::from_u32(&env, 10);
35+
let b = U256::from_u32(&env, 3);
36+
assert_eq!(a.checked_sub(&b), Some(U256::from_u32(&env, 7)));
37+
}
38+
39+
#[test]
40+
fn test_u256_checked_sub_zero_result() {
41+
let env = Env::default();
42+
let a = U256::from_u32(&env, 5);
43+
assert_eq!(a.checked_sub(&a), Some(U256::from_u32(&env, 0)));
44+
}
45+
46+
#[test]
47+
fn test_u256_checked_sub_underflow() {
48+
let env = Env::default();
49+
let a = U256::from_u32(&env, 3);
50+
let b = U256::from_u32(&env, 10);
51+
assert_eq!(a.checked_sub(&b), None);
52+
}
53+
54+
#[test]
55+
fn test_u256_checked_mul_success() {
56+
let env = Env::default();
57+
let a = U256::from_u32(&env, 6);
58+
let b = U256::from_u32(&env, 7);
59+
assert_eq!(a.checked_mul(&b), Some(U256::from_u32(&env, 42)));
60+
}
61+
62+
#[test]
63+
fn test_u256_checked_mul_zero() {
64+
let env = Env::default();
65+
let a = U256::from_parts(&env, u64::MAX, u64::MAX, u64::MAX, u64::MAX);
66+
let zero = U256::from_u32(&env, 0);
67+
assert_eq!(a.checked_mul(&zero), Some(U256::from_u32(&env, 0)));
68+
}
69+
70+
#[test]
71+
fn test_u256_checked_mul_overflow() {
72+
let env = Env::default();
73+
let max = U256::from_parts(&env, u64::MAX, u64::MAX, u64::MAX, u64::MAX);
74+
let two = U256::from_u32(&env, 2);
75+
assert_eq!(max.checked_mul(&two), None);
76+
}
77+
78+
#[test]
79+
fn test_u256_checked_pow_success() {
80+
let env = Env::default();
81+
let base = U256::from_u32(&env, 3);
82+
assert_eq!(base.checked_pow(4), Some(U256::from_u32(&env, 81)));
83+
}
84+
85+
#[test]
86+
fn test_u256_checked_pow_zero_exponent() {
87+
let env = Env::default();
88+
let base = U256::from_u32(&env, 100);
89+
assert_eq!(base.checked_pow(0), Some(U256::from_u32(&env, 1)));
90+
}
91+
92+
#[test]
93+
fn test_u256_checked_pow_overflow() {
94+
let env = Env::default();
95+
let base = U256::from_parts(&env, 0, 0, 0, u64::MAX);
96+
assert_eq!(base.checked_pow(256), None);
97+
}
98+
99+
// ============================================================
100+
// I256 checked arithmetic
101+
// ============================================================
102+
103+
#[test]
104+
fn test_i256_checked_add_success() {
105+
let env = Env::default();
106+
let a = I256::from_i32(&env, -6);
107+
let b = I256::from_i32(&env, 3);
108+
assert_eq!(a.checked_add(&b), Some(I256::from_i32(&env, -3)));
109+
}
110+
111+
#[test]
112+
fn test_i256_checked_add_zero() {
113+
let env = Env::default();
114+
let a = I256::from_i32(&env, -42);
115+
let zero = I256::from_i32(&env, 0);
116+
assert_eq!(a.checked_add(&zero), Some(I256::from_i32(&env, -42)));
117+
}
118+
119+
#[test]
120+
fn test_i256_checked_add_overflow_positive() {
121+
let env = Env::default();
122+
// I256::MAX = 2^255 - 1
123+
let max = I256::from_parts(&env, i64::MAX, u64::MAX, u64::MAX, u64::MAX);
124+
let one = I256::from_i32(&env, 1);
125+
assert_eq!(max.checked_add(&one), None);
126+
}
127+
128+
#[test]
129+
fn test_i256_checked_add_overflow_negative() {
130+
let env = Env::default();
131+
// I256::MIN = -2^255
132+
let min = I256::from_parts(&env, i64::MIN, 0, 0, 0);
133+
let neg_one = I256::from_i32(&env, -1);
134+
assert_eq!(min.checked_add(&neg_one), None);
135+
}
136+
137+
#[test]
138+
fn test_i256_checked_sub_success() {
139+
let env = Env::default();
140+
let a = I256::from_i32(&env, 10);
141+
let b = I256::from_i32(&env, 3);
142+
assert_eq!(a.checked_sub(&b), Some(I256::from_i32(&env, 7)));
143+
}
144+
145+
#[test]
146+
fn test_i256_checked_sub_negative_result() {
147+
let env = Env::default();
148+
let a = I256::from_i32(&env, 3);
149+
let b = I256::from_i32(&env, 10);
150+
assert_eq!(a.checked_sub(&b), Some(I256::from_i32(&env, -7)));
151+
}
152+
153+
#[test]
154+
fn test_i256_checked_sub_overflow() {
155+
let env = Env::default();
156+
let min = I256::from_parts(&env, i64::MIN, 0, 0, 0);
157+
let one = I256::from_i32(&env, 1);
158+
assert_eq!(min.checked_sub(&one), None);
159+
}
160+
161+
#[test]
162+
fn test_i256_checked_mul_success() {
163+
let env = Env::default();
164+
let a = I256::from_i32(&env, -6);
165+
let b = I256::from_i32(&env, 7);
166+
assert_eq!(a.checked_mul(&b), Some(I256::from_i32(&env, -42)));
167+
}
168+
169+
#[test]
170+
fn test_i256_checked_mul_zero() {
171+
let env = Env::default();
172+
let a = I256::from_parts(&env, i64::MAX, u64::MAX, u64::MAX, u64::MAX);
173+
let zero = I256::from_i32(&env, 0);
174+
assert_eq!(a.checked_mul(&zero), Some(I256::from_i32(&env, 0)));
175+
}
176+
177+
#[test]
178+
fn test_i256_checked_mul_overflow_positive() {
179+
let env = Env::default();
180+
let max = I256::from_parts(&env, i64::MAX, u64::MAX, u64::MAX, u64::MAX);
181+
let two = I256::from_i32(&env, 2);
182+
assert_eq!(max.checked_mul(&two), None);
183+
}
184+
185+
#[test]
186+
fn test_i256_checked_mul_overflow_negative() {
187+
let env = Env::default();
188+
// I256::MIN * -1 overflows because |I256::MIN| > I256::MAX
189+
let min = I256::from_parts(&env, i64::MIN, 0, 0, 0);
190+
let neg_one = I256::from_i32(&env, -1);
191+
assert_eq!(min.checked_mul(&neg_one), None);
192+
}
193+
194+
#[test]
195+
fn test_i256_checked_pow_success() {
196+
let env = Env::default();
197+
let base = I256::from_i32(&env, -3);
198+
assert_eq!(base.checked_pow(3), Some(I256::from_i32(&env, -27)));
199+
}
200+
201+
#[test]
202+
fn test_i256_checked_pow_even_exponent() {
203+
let env = Env::default();
204+
let base = I256::from_i32(&env, -2);
205+
assert_eq!(base.checked_pow(4), Some(I256::from_i32(&env, 16)));
206+
}
207+
208+
#[test]
209+
fn test_i256_checked_pow_zero_exponent() {
210+
let env = Env::default();
211+
let base = I256::from_i32(&env, -100);
212+
assert_eq!(base.checked_pow(0), Some(I256::from_i32(&env, 1)));
213+
}
214+
215+
#[test]
216+
fn test_i256_checked_pow_overflow() {
217+
let env = Env::default();
218+
let base = I256::from_parts(&env, 0, 0, 0, i64::MAX as u64);
219+
assert_eq!(base.checked_pow(256), None);
220+
}
221+
222+
// ============================================================
223+
// Consistency: checked vs unchecked produce same result on success
224+
// ============================================================
225+
226+
#[test]
227+
fn test_u256_checked_matches_unchecked() {
228+
let env = Env::default();
229+
let a = U256::from_u32(&env, 100);
230+
let b = U256::from_u32(&env, 37);
231+
232+
assert_eq!(a.checked_add(&b).unwrap(), a.add(&b));
233+
assert_eq!(a.checked_sub(&b).unwrap(), a.sub(&b));
234+
assert_eq!(a.checked_mul(&b).unwrap(), a.mul(&b));
235+
assert_eq!(a.checked_pow(3).unwrap(), a.pow(3));
236+
}
237+
238+
#[test]
239+
fn test_i256_checked_matches_unchecked() {
240+
let env = Env::default();
241+
let a = I256::from_i32(&env, -50);
242+
let b = I256::from_i32(&env, 13);
243+
244+
assert_eq!(a.checked_add(&b).unwrap(), a.add(&b));
245+
assert_eq!(a.checked_sub(&b).unwrap(), a.sub(&b));
246+
assert_eq!(a.checked_mul(&b).unwrap(), a.mul(&b));
247+
assert_eq!(a.checked_pow(2).unwrap(), a.pow(2));
248+
}

0 commit comments

Comments
 (0)