Skip to content

Commit 67f7fe5

Browse files
committed
Add ABI test vectors for cross-validation
1 parent 1a87864 commit 67f7fe5

1 file changed

Lines changed: 249 additions & 0 deletions

File tree

runtime/wasm/src/rust_abi/entity.rs

Lines changed: 249 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,4 +256,253 @@ mod tests {
256256
assert_eq!(data.get("balance"), recovered.get("balance"));
257257
assert_eq!(data.get("active"), recovered.get("active"));
258258
}
259+
260+
// -------------------------------------------------------------------------
261+
// ABI cross-validation vectors.
262+
//
263+
// These tests encode known byte sequences by hand and assert that
264+
// graph-node's ToRustWasm/FromRustWasm impls produce identical bytes.
265+
// The same raw bytes are validated against the Graphite SDK in
266+
// graphite/src/abi_vectors_tests.rs.
267+
// -------------------------------------------------------------------------
268+
269+
fn le32(n: u32) -> [u8; 4] {
270+
n.to_le_bytes()
271+
}
272+
273+
// -- Null (tag 0x00) --
274+
275+
#[test]
276+
fn abi_vec_null_encodes_to_single_byte() {
277+
let bytes = Value::Null.to_bytes();
278+
assert_eq!(bytes, [0x00u8]);
279+
}
280+
281+
#[test]
282+
fn abi_vec_null_decode() {
283+
let v = Value::from_bytes(&[0x00u8]).unwrap();
284+
assert_eq!(v, Value::Null);
285+
}
286+
287+
// -- String (tag 0x01, len:u32 LE, utf-8 bytes) --
288+
289+
#[test]
290+
fn abi_vec_string_known_bytes() {
291+
// "hi" → [0x01, 0x02 0x00 0x00 0x00, 0x68 0x69]
292+
let v = Value::String("hi".to_string());
293+
let bytes = v.to_bytes();
294+
let mut expected = vec![0x01u8];
295+
expected.extend_from_slice(&le32(2));
296+
expected.extend_from_slice(b"hi");
297+
assert_eq!(bytes, expected);
298+
}
299+
300+
#[test]
301+
fn abi_vec_string_decode_known_bytes() {
302+
let mut raw = vec![0x01u8];
303+
raw.extend_from_slice(&le32(2));
304+
raw.extend_from_slice(b"hi");
305+
let v = Value::from_bytes(&raw).unwrap();
306+
assert_eq!(v, Value::String("hi".to_string()));
307+
}
308+
309+
// -- Int (tag 0x02, i32 LE 4 bytes) --
310+
311+
#[test]
312+
fn abi_vec_int_known_bytes() {
313+
let v = Value::Int(42);
314+
let bytes = v.to_bytes();
315+
let mut expected = vec![0x02u8];
316+
expected.extend_from_slice(&42i32.to_le_bytes());
317+
assert_eq!(bytes, expected);
318+
}
319+
320+
#[test]
321+
fn abi_vec_int_negative() {
322+
let v = Value::Int(-1);
323+
let bytes = v.to_bytes();
324+
let mut expected = vec![0x02u8];
325+
expected.extend_from_slice(&(-1i32).to_le_bytes());
326+
assert_eq!(bytes, expected);
327+
}
328+
329+
// -- Int8 (tag 0x03, i64 LE 8 bytes) --
330+
331+
#[test]
332+
fn abi_vec_int8_known_bytes() {
333+
let v = Value::Int8(i64::MAX);
334+
let bytes = v.to_bytes();
335+
let mut expected = vec![0x03u8];
336+
expected.extend_from_slice(&i64::MAX.to_le_bytes());
337+
assert_eq!(bytes, expected);
338+
}
339+
340+
// -- BigInt (tag 0x04, len:u32 LE, signed-LE bytes) --
341+
342+
#[test]
343+
fn abi_vec_bigint_uses_signed_le() {
344+
// 1000 in signed-LE is [0xe8, 0x03]
345+
let n = BigInt::from(1000u64);
346+
let le = n.to_signed_bytes_le();
347+
assert_eq!(le, vec![0xe8u8, 0x03]);
348+
349+
let v = Value::BigInt(n);
350+
let bytes = v.to_bytes();
351+
assert_eq!(bytes[0], 0x04);
352+
let len = u32::from_le_bytes(bytes[1..5].try_into().unwrap()) as usize;
353+
assert_eq!(len, 2);
354+
assert_eq!(&bytes[5..], &[0xe8u8, 0x03]);
355+
}
356+
357+
#[test]
358+
fn abi_vec_bigint_decode_known_le_bytes() {
359+
// hand-encoded 1000 as signed-LE
360+
let mut raw = vec![0x04u8];
361+
raw.extend_from_slice(&le32(2));
362+
raw.push(0xe8);
363+
raw.push(0x03);
364+
let v = Value::from_bytes(&raw).unwrap();
365+
assert_eq!(v, Value::BigInt(BigInt::from(1000u64)));
366+
}
367+
368+
#[test]
369+
fn abi_vec_bigint_zero_len_zero() {
370+
let v = Value::BigInt(BigInt::from(0i32));
371+
let bytes = v.to_bytes();
372+
// zero may encode as empty or as [0x00]; either is valid as long as decode round-trips
373+
let recovered = Value::from_bytes(&bytes).unwrap();
374+
assert_eq!(recovered, Value::BigInt(BigInt::from(0i32)));
375+
}
376+
377+
// -- BigDecimal (tag 0x05, len:u32 LE, UTF-8 string) --
378+
379+
#[test]
380+
fn abi_vec_bigdecimal_encodes_as_string() {
381+
use std::str::FromStr;
382+
let d = graph::prelude::BigDecimal::from_str("3.14").unwrap();
383+
let v = Value::BigDecimal(d);
384+
let bytes = v.to_bytes();
385+
assert_eq!(bytes[0], 0x05);
386+
let len = u32::from_le_bytes(bytes[1..5].try_into().unwrap()) as usize;
387+
let s = std::str::from_utf8(&bytes[5..5 + len]).unwrap();
388+
// must be a valid decimal string representation
389+
assert!(s.contains('.') || s.chars().all(|c| c.is_ascii_digit() || c == '-' || c == 'E' || c == 'e'),
390+
"expected decimal string, got: {}", s);
391+
}
392+
393+
#[test]
394+
fn abi_vec_bigdecimal_decode_known_bytes() {
395+
use std::str::FromStr;
396+
let s = b"3.14";
397+
let mut raw = vec![0x05u8];
398+
raw.extend_from_slice(&le32(s.len() as u32));
399+
raw.extend_from_slice(s);
400+
let v = Value::from_bytes(&raw).unwrap();
401+
let expected = Value::BigDecimal(graph::prelude::BigDecimal::from_str("3.14").unwrap());
402+
assert_eq!(v, expected);
403+
}
404+
405+
// -- Bool (tag 0x06) --
406+
407+
#[test]
408+
fn abi_vec_bool_true_known_bytes() {
409+
assert_eq!(Value::Bool(true).to_bytes(), [0x06u8, 0x01]);
410+
}
411+
412+
#[test]
413+
fn abi_vec_bool_false_known_bytes() {
414+
assert_eq!(Value::Bool(false).to_bytes(), [0x06u8, 0x00]);
415+
}
416+
417+
// -- Bytes (tag 0x07, len:u32 LE, raw bytes) --
418+
419+
#[test]
420+
fn abi_vec_bytes_known_bytes() {
421+
use graph::data::store::scalar::Bytes as StoreBytes;
422+
let payload = vec![0xde, 0xad, 0xbe, 0xef];
423+
let v = Value::Bytes(StoreBytes::from(payload.clone()));
424+
let encoded = v.to_bytes();
425+
let mut expected = vec![0x07u8];
426+
expected.extend_from_slice(&le32(4));
427+
expected.extend_from_slice(&payload);
428+
assert_eq!(encoded, expected);
429+
}
430+
431+
// -- Address (tag 0x08, 20 raw bytes, NO length prefix) --
432+
433+
#[test]
434+
fn abi_vec_address_decode_tag_0x08() {
435+
// The SDK writes Address as tag 0x08 + 20 raw bytes (no length prefix).
436+
// graph-node decodes tag 0x08 as Value::Bytes(20 bytes).
437+
// Note: graph-node re-encodes Bytes as tag 0x07 (length-prefixed); the
438+
// asymmetry is intentional — 0x08 is a SDK-write/host-read optimisation.
439+
let addr = [0xabu8; 20];
440+
let mut raw = vec![0x08u8];
441+
raw.extend_from_slice(&addr);
442+
assert_eq!(raw.len(), 21); // tag + 20 bytes, no length prefix
443+
444+
// graph-node must decode SDK-written 0x08 as Bytes carrying the 20-byte address
445+
let v = Value::from_bytes(&raw).unwrap();
446+
if let Value::Bytes(b) = &v {
447+
assert_eq!(b.as_slice(), &addr);
448+
} else {
449+
panic!("expected Bytes for Address tag, got {:?}", v);
450+
}
451+
}
452+
453+
// -- Array (tag 0x09, len:u32 LE, tagged Values) --
454+
455+
#[test]
456+
fn abi_vec_array_empty() {
457+
let v = Value::List(vec![]);
458+
let bytes = v.to_bytes();
459+
let mut expected = vec![0x09u8];
460+
expected.extend_from_slice(&le32(0));
461+
assert_eq!(bytes, expected);
462+
}
463+
464+
#[test]
465+
fn abi_vec_array_known_bytes() {
466+
// [Int(1), Bool(true)]
467+
let v = Value::List(vec![Value::Int(1), Value::Bool(true)]);
468+
let bytes = v.to_bytes();
469+
assert_eq!(bytes[0], 0x09);
470+
let count = u32::from_le_bytes(bytes[1..5].try_into().unwrap());
471+
assert_eq!(count, 2);
472+
}
473+
474+
#[test]
475+
fn abi_vec_array_decode_known_bytes() {
476+
let mut raw = vec![0x09u8];
477+
raw.extend_from_slice(&le32(2));
478+
raw.push(0x02);
479+
raw.extend_from_slice(&1i32.to_le_bytes());
480+
raw.push(0x06);
481+
raw.push(0x01);
482+
let v = Value::from_bytes(&raw).unwrap();
483+
assert_eq!(v, Value::List(vec![Value::Int(1), Value::Bool(true)]));
484+
}
485+
486+
// -- Cross-wire: spec worked example --
487+
488+
#[test]
489+
fn abi_vec_spec_worked_example() {
490+
// { id: "tx-1", value: 42, active: true } (spec section 4.6.1)
491+
// field_count: 3, field order unspecified but all fields must survive
492+
let mut data = EntityData::new();
493+
data.insert("id".to_string(), Value::String("tx-1".to_string()));
494+
data.insert("value".to_string(), Value::Int(42));
495+
data.insert("active".to_string(), Value::Bool(true));
496+
497+
let bytes = data.to_bytes();
498+
499+
// field count must be 3
500+
let count = u32::from_le_bytes(bytes[0..4].try_into().unwrap());
501+
assert_eq!(count, 3);
502+
503+
let recovered = EntityData::from_bytes(&bytes).unwrap();
504+
assert_eq!(recovered.get("id"), Some(&Value::String("tx-1".to_string())));
505+
assert_eq!(recovered.get("value"), Some(&Value::Int(42)));
506+
assert_eq!(recovered.get("active"), Some(&Value::Bool(true)));
507+
}
259508
}

0 commit comments

Comments
 (0)