1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
use super::*;
impl ExecutionContext {
pub(crate) fn convert_item(
&self,
item: StackItem,
target_code: u8,
) -> Result<StackItem, RuntimeError> {
match target_code {
0x00 => Ok(item), // Any/no-op
0x20 => Ok(StackItem::Boolean(item.is_truthy())),
0x21 | 0x22 => {
// NeoVM CONVERT→Integer is only valid from Boolean / Integer /
// ByteString / Buffer; a compound (Array/Map) operand throws
// ("not supported"). Faulting here matches real NeoVM instead of
// silently producing a degenerate integer.
if matches!(item, StackItem::Array(_) | StackItem::Map(_)) {
return Err(RuntimeError::ExecutionError {
message: "CONVERT: cannot convert a compound type (Array/Map) to Integer"
.to_string(),
});
}
// NeoVM CONVERT→Integer: interpret the byte buffer as a signed
// little-endian arbitrary-precision integer. Narrow results
// (≤ 8 bytes) fit in `StackItem::Integer(i64)`; wider results
// are preserved as signed-LE `StackItem::ByteArray` so the
// downstream BigInt arithmetic path (`coerce_item_to_bigint`,
// `bigint_to_stack_item`) keeps full precision for values that
// exceed 64 bits (e.g. `uint256(bytes32(...))` with high bytes
// populated). Truncating to the first 8 bytes here was Task
// #111's root cause — for a 32-byte LE buffer whose magnitude
// lives above byte 7, the naive `from_le_bytes([..8])` path
// returned 0.
let bytes = Self::stack_item_to_bytes(item);
if bytes.is_empty() {
return Ok(StackItem::Integer(0));
}
if bytes.len() <= 8 {
// Sign-extend from the high bit of the last byte so signed
// LE encodings shorter than 8 bytes round-trip correctly.
let mut buf = [0u8; 8];
buf[..bytes.len()].copy_from_slice(&bytes);
let sign = *bytes.last().unwrap() & 0x80;
if sign != 0 {
for byte in buf.iter_mut().skip(bytes.len()) {
*byte = 0xFF;
}
}
Ok(StackItem::Integer(i64::from_le_bytes(buf)))
} else {
// Preserve the signed-LE encoding verbatim — the wide
// arithmetic path (Task #30) already decodes ByteArray via
// `BigInt::from_signed_bytes_le`.
Ok(StackItem::byte_array(bytes))
}
}
0x28 | 0x30 => {
// NeoVM CONVERT→ByteString/Buffer of an Integer yields the
// MINIMAL two's-complement little-endian encoding (zero ⇒ empty
// span), NOT a fixed 8-byte word. Match a real node here so a
// contract that converts an integer to bytes (and inspects its
// length, hashes it, or concatenates it) sees on-chain widths.
match item {
StackItem::Integer(_) | StackItem::UnsignedInteger(_) => {
let n = self.coerce_item_to_bigint(&item).unwrap_or_default();
let bytes = if n.sign() == num_bigint::Sign::NoSign {
Vec::new()
} else {
n.to_signed_bytes_le()
};
Ok(StackItem::byte_array(bytes))
}
_ => Ok(StackItem::byte_array(Self::stack_item_to_bytes(item))),
}
}
0x40 | 0x41 => match item {
StackItem::Array(items) => Ok(StackItem::Array(items)),
StackItem::Map(map) => Ok(StackItem::array(
map.borrow()
.iter()
.map(|(k, v)| {
StackItem::array(vec![StackItem::byte_array(k.clone()), v.clone()])
})
.collect(),
)),
other => Ok(StackItem::array(vec![other])),
},
0x48 => match item {
StackItem::Map(map) => Ok(StackItem::Map(map)),
StackItem::Array(items) => {
let mut map = std::collections::HashMap::new();
for pair in items.borrow().iter() {
let StackItem::Array(kv) = pair else {
continue;
};
let kv = kv.borrow();
if kv.len() < 2 {
continue;
}
let key = kv.first().cloned().unwrap_or(StackItem::Null);
let value = kv.last().cloned().unwrap_or(StackItem::Null);
map.insert(Self::stack_item_to_bytes(key), value);
}
Ok(StackItem::map(map))
}
other => {
let mut map = std::collections::HashMap::new();
map.insert(Vec::new(), other);
Ok(StackItem::map(map))
}
},
0x80 => Ok(item), // iterator tokens already byte arrays; leave untouched
_ => Ok(item),
}
}
pub(crate) fn is_iterator_token(&self, item: &StackItem) -> bool {
if let Some(id) = Self::iterator_id_from_item(item) {
return self.iterators.contains_key(&id);
}
false
}
}