Skip to content

Commit d2f276b

Browse files
committed
graph: replace to_signed_u256 with fallible to_i256
to_signed_u256 panicked on values outside the int256 range, killing the runner thread silently. The replacement to_i256 returns Result and is plumbed through both reachable call sites (declared-call entity triggers in data_source/common.rs, WASM ABI conversion in runtime/wasm) via the existing Deterministic failure path. to_unsigned_u256 had the symmetric latent panic for values >= 2^256 (via ruint's from_le_slice); add an explicit length check there too. Removes the redundant U256-as-signed-transport API now that alloy provides I256 directly.
1 parent 6f47d23 commit d2f276b

4 files changed

Lines changed: 93 additions & 20 deletions

File tree

graph/src/data/store/scalar/bigint.rs

Lines changed: 88 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use alloy::primitives::I256;
12
use anyhow::bail;
23
use num_bigint;
34
use serde::{self, Deserialize, Serialize};
@@ -185,24 +186,27 @@ impl BigInt {
185186
BigInt::from_unsigned_bytes_le(&bytes).unwrap()
186187
}
187188

188-
pub fn from_signed_u256(n: &U256) -> Self {
189-
let bytes: [u8; U256::BYTES] = n.to_le_bytes();
189+
pub fn from_i256(n: &I256) -> Self {
190+
let bytes: [u8; I256::BYTES] = n.to_le_bytes();
191+
// Unwrap: 256 bits is much less than BigInt::MAX_BITS
190192
BigInt::from_signed_bytes_le(&bytes).unwrap()
191193
}
192194

193-
pub fn to_signed_u256(&self) -> U256 {
195+
pub fn to_i256(&self) -> Result<I256, anyhow::Error> {
194196
let bytes = self.to_signed_bytes_le();
195-
if self < &BigInt::from(0) {
196-
assert!(
197-
bytes.len() <= 32,
198-
"BigInt value does not fit into signed U256"
199-
);
200-
let mut i_bytes: [u8; 32] = [255; 32];
201-
i_bytes[..bytes.len()].copy_from_slice(&bytes);
202-
U256::from_le_slice(&i_bytes)
197+
anyhow::ensure!(
198+
bytes.len() <= I256::BYTES,
199+
"BigInt value `{}` does not fit into int256",
200+
self
201+
);
202+
let fill: u8 = if self.sign() == BigIntSign::Minus {
203+
0xFF
203204
} else {
204-
U256::from_le_slice(&bytes)
205-
}
205+
0x00
206+
};
207+
let mut buf = [fill; I256::BYTES];
208+
buf[..bytes.len()].copy_from_slice(&bytes);
209+
Ok(I256::from_le_bytes(buf))
206210
}
207211

208212
pub fn to_unsigned_u256(&self) -> Result<U256, anyhow::Error> {
@@ -213,6 +217,11 @@ impl BigInt {
213217
self
214218
);
215219
}
220+
anyhow::ensure!(
221+
bytes.len() <= U256::BYTES,
222+
"BigInt value `{}` does not fit into uint256",
223+
self
224+
);
216225
Ok(U256::from_le_slice(&bytes))
217226
}
218227

@@ -410,6 +419,72 @@ mod test {
410419

411420
use super::{super::test::same_stable_hash, BigInt};
412421

422+
/// Compute 2^n via repeated doubling so we can build values larger than
423+
/// `BigInt::pow`'s u8 exponent limit.
424+
fn pow2(n: u32) -> BigInt {
425+
let mut acc = BigInt::from(1u64);
426+
for _ in 0..n {
427+
acc = acc * BigInt::from(2u64);
428+
}
429+
acc
430+
}
431+
432+
#[test]
433+
fn to_i256_succeeds_at_boundaries() {
434+
let one = BigInt::from(1u64);
435+
let i256_max = pow2(255) - one.clone();
436+
let i256_min = BigInt::from(0) - pow2(255);
437+
438+
for v in &[
439+
BigInt::from(0),
440+
BigInt::from(1),
441+
BigInt::from(-1),
442+
i256_max.clone(),
443+
i256_min.clone(),
444+
] {
445+
let i = v.to_i256().expect("in-range value should convert");
446+
let back = BigInt::from_i256(&i);
447+
assert_eq!(&back, v, "round-trip failed for {}", v);
448+
}
449+
}
450+
451+
#[test]
452+
fn to_i256_errors_outside_range() {
453+
let one = BigInt::from(1u64);
454+
let just_above_max = pow2(255);
455+
let just_below_min = BigInt::from(0) - pow2(255) - one;
456+
let way_above = pow2(300);
457+
let way_below = BigInt::from(0) - pow2(300);
458+
459+
for v in &[just_above_max, just_below_min, way_above, way_below] {
460+
assert!(
461+
v.to_i256().is_err(),
462+
"out-of-range value {} should error, not panic",
463+
v
464+
);
465+
}
466+
}
467+
468+
#[test]
469+
fn to_unsigned_u256_errors_outside_range() {
470+
let just_above_max = pow2(256);
471+
let way_above = pow2(300);
472+
473+
for v in &[just_above_max, way_above] {
474+
assert!(
475+
v.to_unsigned_u256().is_err(),
476+
"value {} above u256::MAX should error, not panic",
477+
v
478+
);
479+
}
480+
481+
assert!(
482+
BigInt::from(-1).to_unsigned_u256().is_err(),
483+
"negative value should error"
484+
);
485+
}
486+
487+
413488
#[test]
414489
fn bigint_to_from_u64() {
415490
for n in 0..100 {

graph/src/data_source/common.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -762,8 +762,7 @@ impl CallDecl {
762762
Ok(DynSolValue::Int(x, x.bits() as usize))
763763
}
764764
(DynSolType::Int(_), Value::BigInt(i)) => {
765-
let x =
766-
abi::I256::from_le_bytes(i.to_signed_u256().to_le_bytes::<{ U256::BYTES }>());
765+
let x = i.to_i256()?;
767766
Ok(DynSolValue::Int(x, x.bits() as usize))
768767
}
769768
(DynSolType::Uint(_), Value::Int(i)) if *i >= 0 => {

runtime/test/src/test/abi.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -525,8 +525,8 @@ async fn test_abi_big_int(api_version: Version) {
525525
let new_uint_obj: AscPtr<AscBigInt> = module.invoke_export1("test_uint", &old_uint).await;
526526
let new_uint: BigInt = module.asc_get(new_uint_obj).unwrap();
527527
assert_eq!(new_uint, BigInt::from(-49_i32));
528-
let new_uint_from_u256 = BigInt::from_signed_u256(&new_uint.to_signed_u256());
529-
assert_eq!(new_uint, new_uint_from_u256);
528+
let new_uint_from_i256 = BigInt::from_i256(&new_uint.to_i256().unwrap());
529+
assert_eq!(new_uint, new_uint_from_i256);
530530
}
531531

532532
#[graph::test]

runtime/wasm/src/to_from/external.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use graph::runtime::{
1212
};
1313
use graph::{data::store, runtime::DeterministicHostError};
1414
use graph::{
15-
prelude::{alloy::primitives::U256, serde_json},
15+
prelude::serde_json,
1616
runtime::FromAscObj,
1717
};
1818

@@ -235,8 +235,7 @@ impl FromAscObj<AscEnum<EthereumValueKind>> for abi::DynSolValue {
235235
EthereumValueKind::Int => {
236236
let ptr: AscPtr<AscBigInt> = AscPtr::from(payload);
237237
let n: BigInt = asc_get(heap, ptr, gas, depth)?;
238-
let x =
239-
abi::I256::from_le_bytes(n.to_signed_u256().to_le_bytes::<{ U256::BYTES }>());
238+
let x = n.to_i256().map_err(DeterministicHostError::Other)?;
240239

241240
Self::Int(x, x.bits() as usize)
242241
}

0 commit comments

Comments
 (0)