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
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
use super::*;
impl ExecutionContext {
pub(crate) fn execute_collection_instruction(
&mut self,
opcode: u8,
) -> Result<bool, RuntimeError> {
match opcode {
0xBE => {
// PACKMAP
self.pack_map()?;
self.instruction_pointer += 1;
}
0xBF => {
// PACKSTRUCT (array-backed)
self.pack_items()?;
self.instruction_pointer += 1;
}
0xC0 => {
// PACK
self.pack_items()?;
self.instruction_pointer += 1;
}
0xC1 => {
// UNPACK
self.unpack()?;
self.instruction_pointer += 1;
}
0xC2 => {
// NEWARRAY0
self.new_array0()?;
self.instruction_pointer += 1;
}
0xC3 => {
// NEWARRAY
self.new_array()?;
self.instruction_pointer += 1;
}
0xC4 => {
// NEWARRAY_T
self.new_array()?;
self.instruction_pointer += 1;
}
0xC5 => {
// NEWSTRUCT0
self.new_struct0()?;
self.instruction_pointer += 1;
}
0xC6 => {
// NEWSTRUCT
self.new_struct()?;
self.instruction_pointer += 1;
}
0xC8 => {
// NEWMAP
self.new_map()?;
self.instruction_pointer += 1;
}
0xCA => {
// SIZE
self.size_of()?;
self.instruction_pointer += 1;
}
0xCB => {
// HASKEY
self.haskey()?;
self.instruction_pointer += 1;
}
0xCC => {
// KEYS
self.keys()?;
self.instruction_pointer += 1;
}
0xCD => {
// VALUES
self.values()?;
self.instruction_pointer += 1;
}
0xCE => {
// PICKITEM
self.pick_item()?;
self.instruction_pointer += 1;
}
0xCF => {
// APPEND
self.append_item()?;
self.instruction_pointer += 1;
}
0xD0 => {
// SETITEM
self.set_item()?;
self.instruction_pointer += 1;
}
0xD1 => {
// REVERSEITEMS
self.reverse_items()?;
self.instruction_pointer += 1;
}
0xD2 => {
// REMOVE
self.remove_item()?;
self.instruction_pointer += 1;
}
0xD3 => {
// CLEARITEMS
self.clear_items()?;
self.instruction_pointer += 1;
}
0xD4 => {
// POPITEM
self.pop_item_from_collection()?;
self.instruction_pointer += 1;
}
0xD8 => {
// ISNULL
let item = self.pop_stack()?;
self.push_stack(StackItem::Boolean(matches!(item, StackItem::Null)))?;
self.instruction_pointer += 1;
}
0xD9 => {
// ISTYPE (best-effort): NeoVM takes a 1-byte immediate operand (StackItemType).
let operand_index = self.instruction_pointer as usize + 1;
if operand_index >= self.bytecode.len() {
return Err(RuntimeError::ExecutionError {
message: "ISTYPE: insufficient bytecode for operand".to_string(),
});
}
let expected = self.bytecode[operand_index];
let item = self.pop_stack()?;
let matches = match expected {
0x00 => true, // Any
0x20 => matches!(item, StackItem::Boolean(_)),
0x21 | 0x22 => {
matches!(item, StackItem::Integer(_) | StackItem::UnsignedInteger(_))
}
0x28 | 0x30 => matches!(item, StackItem::ByteArray(_)),
0x40 | 0x41 => matches!(item, StackItem::Array(_)),
0x48 => matches!(item, StackItem::Map(_)),
0x60 => matches!(item, StackItem::ByteArray(_)), // interop handles as byte tokens
0x80 => self.is_iterator_token(&item),
_ => false,
};
self.push_stack(StackItem::Boolean(matches))?;
self.instruction_pointer += 2;
}
0xDB => {
// CONVERT (best-effort coercion): NeoVM takes a 1-byte immediate operand (StackItemType).
let operand_index = self.instruction_pointer as usize + 1;
if operand_index >= self.bytecode.len() {
return Err(RuntimeError::ExecutionError {
message: "CONVERT: insufficient bytecode for operand".to_string(),
});
}
let target_code = self.bytecode[operand_index];
let item = self.pop_stack()?;
let converted = self.convert_item(item, target_code)?;
self.push_stack(converted)?;
self.instruction_pointer += 2;
}
0xE0 => {
// ABORTMSG
let message = Self::stack_item_to_bytes(self.pop_stack()?);
return Err(RuntimeError::ExecutionError {
message: format!("ABORTMSG: {}", String::from_utf8_lossy(&message)),
});
}
0xE1 => {
// ASSERTMSG
let message = Self::stack_item_to_bytes(self.pop_stack()?);
let condition = self.pop_stack()?;
if condition.is_truthy() {
self.instruction_pointer += 1;
} else {
return Err(RuntimeError::ExecutionError {
message: format!("ASSERTMSG failed: {}", String::from_utf8_lossy(&message)),
});
}
}
_ => return Ok(false),
}
Ok(true)
}
}