Skip to main content

neo_decompiler/decompiler/analysis/
types.rs

1//! Lightweight type inference for lifted Neo N3 bytecode.
2//!
3//! The Neo VM is dynamically typed and most syscalls do not encode argument
4//! signatures in the bytecode. The goal of this module is therefore to provide
5//! a best-effort type recovery pass that is:
6//!
7//! - conservative (falls back to `unknown`/`any` rather than guessing)
8//! - useful for collection recovery and readability improvements
9//! - deterministic and panic-free on malformed input
10
11// Stack depth → i64 and type-tag byte reinterpretation casts are structurally
12// safe: stack depth fits in i64, and the i8→u8 cast is intentional.
13#![allow(
14    clippy::cast_possible_truncation,
15    clippy::cast_possible_wrap,
16    clippy::cast_sign_loss
17)]
18
19use serde::Serialize;
20
21use crate::instruction::{Instruction, OpCode, Operand};
22use crate::manifest::ContractManifest;
23
24use super::{MethodRef, MethodTable};
25
26/// Primitive/value types inferred from the instruction stream.
27#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize)]
28#[non_exhaustive]
29pub enum ValueType {
30    /// Unknown or not yet inferred.
31    #[serde(rename = "unknown")]
32    Unknown,
33    /// Dynamic `any` value.
34    #[serde(rename = "any")]
35    Any,
36    /// Null literal.
37    #[serde(rename = "null")]
38    Null,
39    /// Boolean.
40    #[serde(rename = "bool")]
41    Boolean,
42    /// Integer.
43    #[serde(rename = "integer")]
44    Integer,
45    /// ByteString.
46    #[serde(rename = "bytestring")]
47    ByteString,
48    /// Buffer.
49    #[serde(rename = "buffer")]
50    Buffer,
51    /// Array.
52    #[serde(rename = "array")]
53    Array,
54    /// Struct.
55    #[serde(rename = "struct")]
56    Struct,
57    /// Map.
58    #[serde(rename = "map")]
59    Map,
60    /// Interop interface.
61    #[serde(rename = "interopinterface")]
62    InteropInterface,
63    /// Pointer.
64    #[serde(rename = "pointer")]
65    Pointer,
66}
67
68impl ValueType {
69    fn join(self, other: Self) -> Self {
70        use ValueType::*;
71        if self == other {
72            return self;
73        }
74        match (self, other) {
75            (Unknown, x) | (x, Unknown) => x,
76            (Null, _) | (_, Null) => Any,
77            _ => Any,
78        }
79    }
80}
81
82#[derive(Debug, Clone, Copy)]
83struct StackValue {
84    ty: ValueType,
85    int_literal: Option<i64>,
86}
87
88impl StackValue {
89    fn unknown() -> Self {
90        Self {
91            ty: ValueType::Unknown,
92            int_literal: None,
93        }
94    }
95
96    fn with_type(ty: ValueType) -> Self {
97        Self {
98            ty,
99            int_literal: None,
100        }
101    }
102
103    fn integer_literal(value: i64) -> Self {
104        Self {
105            ty: ValueType::Integer,
106            int_literal: Some(value),
107        }
108    }
109}
110
111/// Per-method inferred types.
112#[derive(Debug, Clone, Serialize)]
113pub struct MethodTypes {
114    /// Method whose slots were analyzed.
115    pub method: MethodRef,
116    /// Inferred argument types indexed by argument slot.
117    pub arguments: Vec<ValueType>,
118    /// Inferred local types indexed by local slot.
119    pub locals: Vec<ValueType>,
120}
121
122/// Aggregated type inference results.
123#[derive(Debug, Clone, Default, Serialize)]
124pub struct TypeInfo {
125    /// Per-method inferred locals/arguments.
126    pub methods: Vec<MethodTypes>,
127    /// Inferred static slot types indexed by static slot.
128    pub statics: Vec<ValueType>,
129}
130
131/// Infer primitive types and collection kinds from the instruction stream.
132#[must_use]
133pub fn infer_types(instructions: &[Instruction], manifest: Option<&ContractManifest>) -> TypeInfo {
134    let table = MethodTable::new(instructions, manifest);
135    let static_count = scan_static_slot_count(instructions).unwrap_or(0);
136    let mut statics = vec![ValueType::Unknown; static_count];
137
138    let mut methods = Vec::new();
139    for span in table.spans() {
140        let slice: Vec<&Instruction> = instructions
141            .iter()
142            .filter(|ins| ins.offset >= span.start && ins.offset < span.end)
143            .collect();
144
145        let (locals_count, args_count) = scan_slot_counts(&slice).unwrap_or((0, 0));
146        let mut locals = vec![ValueType::Unknown; locals_count];
147        let mut arguments = vec![ValueType::Unknown; args_count];
148
149        if let Some(manifest) = manifest {
150            if let Some(index) = table.manifest_index_for_start(span.start) {
151                if let Some(method) = manifest.abi.methods.get(index) {
152                    for (idx, param) in method.parameters.iter().enumerate() {
153                        if idx < arguments.len() {
154                            arguments[idx] = arguments[idx].join(type_from_manifest(&param.kind));
155                        }
156                    }
157                }
158            }
159        }
160
161        infer_types_in_slice(&slice, &mut locals, &mut arguments, &mut statics);
162
163        methods.push(MethodTypes {
164            method: span.method.clone(),
165            arguments,
166            locals,
167        });
168    }
169
170    TypeInfo { methods, statics }
171}
172
173fn infer_types_in_slice(
174    instructions: &[&Instruction],
175    locals: &mut Vec<ValueType>,
176    arguments: &mut Vec<ValueType>,
177    statics: &mut Vec<ValueType>,
178) {
179    let mut stack: Vec<StackValue> = Vec::new();
180
181    for instr in instructions {
182        match instr.opcode {
183            OpCode::Initsslot => {
184                if let Some(Operand::U8(count)) = &instr.operand {
185                    let need = *count as usize;
186                    if statics.len() < need {
187                        statics.resize(need, ValueType::Unknown);
188                    }
189                }
190            }
191            OpCode::Initslot => {
192                // Operand is 2 bytes: locals, args.
193                if let Some(Operand::Bytes(bytes)) = &instr.operand {
194                    if bytes.len() == 2 {
195                        let locals_count = bytes[0] as usize;
196                        let args_count = bytes[1] as usize;
197                        if locals.len() < locals_count {
198                            locals.resize(locals_count, ValueType::Unknown);
199                        }
200                        if arguments.len() < args_count {
201                            arguments.resize(args_count, ValueType::Unknown);
202                        }
203                    }
204                }
205            }
206
207            // Literals
208            OpCode::PushNull => stack.push(StackValue::with_type(ValueType::Null)),
209            OpCode::PushT | OpCode::PushF => stack.push(StackValue::with_type(ValueType::Boolean)),
210            OpCode::Pushdata1 | OpCode::Pushdata2 | OpCode::Pushdata4 => {
211                stack.push(StackValue::with_type(ValueType::ByteString));
212            }
213            OpCode::Pushint8
214            | OpCode::Pushint16
215            | OpCode::Pushint32
216            | OpCode::Pushint64
217            | OpCode::Pushint128
218            | OpCode::Pushint256
219            | OpCode::PushM1
220            | OpCode::Push0
221            | OpCode::Push1
222            | OpCode::Push2
223            | OpCode::Push3
224            | OpCode::Push4
225            | OpCode::Push5
226            | OpCode::Push6
227            | OpCode::Push7
228            | OpCode::Push8
229            | OpCode::Push9
230            | OpCode::Push10
231            | OpCode::Push11
232            | OpCode::Push12
233            | OpCode::Push13
234            | OpCode::Push14
235            | OpCode::Push15
236            | OpCode::Push16 => {
237                if let Some(lit) = int_literal_from_operand(instr.operand.as_ref()) {
238                    stack.push(StackValue::integer_literal(lit));
239                } else {
240                    stack.push(StackValue::with_type(ValueType::Integer));
241                }
242            }
243            OpCode::PushA => stack.push(StackValue::with_type(ValueType::Pointer)),
244
245            // Stack manipulation
246            OpCode::Clear => stack.clear(),
247            OpCode::Depth => stack.push(StackValue::integer_literal(stack.len() as i64)),
248            OpCode::Drop => {
249                let _ = pop_or_unknown(&mut stack);
250            }
251            OpCode::Dup => {
252                let value = stack.last().copied().unwrap_or_else(StackValue::unknown);
253                stack.push(value);
254            }
255            OpCode::Swap => {
256                if stack.len() >= 2 {
257                    let len = stack.len();
258                    stack.swap(len - 1, len - 2);
259                }
260            }
261            OpCode::Over => {
262                let value = stack
263                    .get(stack.len().saturating_sub(2))
264                    .copied()
265                    .unwrap_or_else(StackValue::unknown);
266                stack.push(value);
267            }
268            OpCode::Nip => {
269                if stack.len() >= 2 {
270                    let len = stack.len();
271                    stack.remove(len - 2);
272                }
273            }
274            OpCode::Rot => {
275                if let (Some(top), Some(mid), Some(bottom)) =
276                    (stack.pop(), stack.pop(), stack.pop())
277                {
278                    stack.push(mid);
279                    stack.push(top);
280                    stack.push(bottom);
281                }
282            }
283            OpCode::Tuck => {
284                if let (Some(top), Some(second)) = (stack.pop(), stack.pop()) {
285                    stack.push(top);
286                    stack.push(second);
287                    stack.push(top);
288                }
289            }
290            OpCode::Pick => {
291                let index = pop_or_unknown(&mut stack);
292                if let Some(depth) = index.int_literal.and_then(|v| usize::try_from(v).ok()) {
293                    let pos = stack.len().checked_sub(1 + depth);
294                    if let Some(pos) = pos {
295                        if let Some(value) = stack.get(pos).copied() {
296                            stack.push(value);
297                            continue;
298                        }
299                    }
300                }
301                stack.push(StackValue::unknown());
302            }
303            OpCode::Roll => {
304                let index = pop_or_unknown(&mut stack);
305                if let Some(depth) = index.int_literal.and_then(|v| usize::try_from(v).ok()) {
306                    if depth < stack.len() {
307                        let pos = stack.len() - 1 - depth;
308                        let value = stack.remove(pos);
309                        stack.push(value);
310                    }
311                }
312            }
313            OpCode::Xdrop => {
314                let index = pop_or_unknown(&mut stack);
315                if let Some(depth) = index.int_literal.and_then(|v| usize::try_from(v).ok()) {
316                    if depth < stack.len() {
317                        let pos = stack.len() - 1 - depth;
318                        stack.remove(pos);
319                        continue;
320                    }
321                }
322                let _ = pop_or_unknown(&mut stack);
323            }
324            OpCode::Reverse3 => reverse_top(&mut stack, 3),
325            OpCode::Reverse4 => reverse_top(&mut stack, 4),
326            OpCode::Reversen => {
327                let count = pop_or_unknown(&mut stack);
328                if let Some(depth) = count.int_literal.and_then(|v| usize::try_from(v).ok()) {
329                    reverse_top(&mut stack, depth);
330                }
331            }
332
333            // Slot ops
334            OpCode::Ldloc0 => push_slot(&mut stack, locals.first().copied()),
335            OpCode::Ldloc1 => push_slot(&mut stack, locals.get(1).copied()),
336            OpCode::Ldloc2 => push_slot(&mut stack, locals.get(2).copied()),
337            OpCode::Ldloc3 => push_slot(&mut stack, locals.get(3).copied()),
338            OpCode::Ldloc4 => push_slot(&mut stack, locals.get(4).copied()),
339            OpCode::Ldloc5 => push_slot(&mut stack, locals.get(5).copied()),
340            OpCode::Ldloc6 => push_slot(&mut stack, locals.get(6).copied()),
341            OpCode::Ldloc => push_indexed_slot(&mut stack, locals, instr.operand.as_ref()),
342            OpCode::Stloc0 => store_slot(&mut stack, locals, 0),
343            OpCode::Stloc1 => store_slot(&mut stack, locals, 1),
344            OpCode::Stloc2 => store_slot(&mut stack, locals, 2),
345            OpCode::Stloc3 => store_slot(&mut stack, locals, 3),
346            OpCode::Stloc4 => store_slot(&mut stack, locals, 4),
347            OpCode::Stloc5 => store_slot(&mut stack, locals, 5),
348            OpCode::Stloc6 => store_slot(&mut stack, locals, 6),
349            OpCode::Stloc => store_indexed_slot(&mut stack, locals, instr.operand.as_ref()),
350
351            OpCode::Ldarg0 => push_slot(&mut stack, arguments.first().copied()),
352            OpCode::Ldarg1 => push_slot(&mut stack, arguments.get(1).copied()),
353            OpCode::Ldarg2 => push_slot(&mut stack, arguments.get(2).copied()),
354            OpCode::Ldarg3 => push_slot(&mut stack, arguments.get(3).copied()),
355            OpCode::Ldarg4 => push_slot(&mut stack, arguments.get(4).copied()),
356            OpCode::Ldarg5 => push_slot(&mut stack, arguments.get(5).copied()),
357            OpCode::Ldarg6 => push_slot(&mut stack, arguments.get(6).copied()),
358            OpCode::Ldarg => push_indexed_slot(&mut stack, arguments, instr.operand.as_ref()),
359            OpCode::Starg0 => store_slot(&mut stack, arguments, 0),
360            OpCode::Starg1 => store_slot(&mut stack, arguments, 1),
361            OpCode::Starg2 => store_slot(&mut stack, arguments, 2),
362            OpCode::Starg3 => store_slot(&mut stack, arguments, 3),
363            OpCode::Starg4 => store_slot(&mut stack, arguments, 4),
364            OpCode::Starg5 => store_slot(&mut stack, arguments, 5),
365            OpCode::Starg6 => store_slot(&mut stack, arguments, 6),
366            OpCode::Starg => store_indexed_slot(&mut stack, arguments, instr.operand.as_ref()),
367
368            OpCode::Ldsfld0 => push_slot(&mut stack, statics.first().copied()),
369            OpCode::Ldsfld1 => push_slot(&mut stack, statics.get(1).copied()),
370            OpCode::Ldsfld2 => push_slot(&mut stack, statics.get(2).copied()),
371            OpCode::Ldsfld3 => push_slot(&mut stack, statics.get(3).copied()),
372            OpCode::Ldsfld4 => push_slot(&mut stack, statics.get(4).copied()),
373            OpCode::Ldsfld5 => push_slot(&mut stack, statics.get(5).copied()),
374            OpCode::Ldsfld6 => push_slot(&mut stack, statics.get(6).copied()),
375            OpCode::Ldsfld => push_indexed_slot(&mut stack, statics, instr.operand.as_ref()),
376            OpCode::Stsfld0 => store_slot(&mut stack, statics, 0),
377            OpCode::Stsfld1 => store_slot(&mut stack, statics, 1),
378            OpCode::Stsfld2 => store_slot(&mut stack, statics, 2),
379            OpCode::Stsfld3 => store_slot(&mut stack, statics, 3),
380            OpCode::Stsfld4 => store_slot(&mut stack, statics, 4),
381            OpCode::Stsfld5 => store_slot(&mut stack, statics, 5),
382            OpCode::Stsfld6 => store_slot(&mut stack, statics, 6),
383            OpCode::Stsfld => store_indexed_slot(&mut stack, statics, instr.operand.as_ref()),
384
385            // Collections
386            OpCode::Newarray0 => stack.push(StackValue::with_type(ValueType::Array)),
387            OpCode::Newarray => {
388                let _ = pop_or_unknown(&mut stack); // count
389                stack.push(StackValue::with_type(ValueType::Array));
390            }
391            OpCode::Newmap => stack.push(StackValue::with_type(ValueType::Map)),
392            OpCode::Newstruct0 => stack.push(StackValue::with_type(ValueType::Struct)),
393            OpCode::Newstruct => {
394                let _ = pop_or_unknown(&mut stack); // count
395                stack.push(StackValue::with_type(ValueType::Struct));
396            }
397            OpCode::Newbuffer => {
398                let _ = pop_or_unknown(&mut stack); // length
399                stack.push(StackValue::with_type(ValueType::Buffer));
400            }
401            OpCode::Pack => {
402                let count = pop_or_unknown(&mut stack);
403                if let Some(count) = count.int_literal.and_then(|v| usize::try_from(v).ok()) {
404                    for _ in 0..count {
405                        let _ = pop_or_unknown(&mut stack);
406                    }
407                }
408                stack.push(StackValue::with_type(ValueType::Array));
409            }
410            OpCode::Packmap => {
411                let count = pop_or_unknown(&mut stack);
412                if let Some(count) = count.int_literal.and_then(|v| usize::try_from(v).ok()) {
413                    for _ in 0..count {
414                        let _ = pop_or_unknown(&mut stack);
415                    }
416                }
417                stack.push(StackValue::with_type(ValueType::Map));
418            }
419            OpCode::Packstruct => {
420                let count = pop_or_unknown(&mut stack);
421                if let Some(count) = count.int_literal.and_then(|v| usize::try_from(v).ok()) {
422                    for _ in 0..count {
423                        let _ = pop_or_unknown(&mut stack);
424                    }
425                }
426                stack.push(StackValue::with_type(ValueType::Struct));
427            }
428            OpCode::Unpack => {
429                let _ = pop_or_unknown(&mut stack);
430                stack.push(StackValue::unknown());
431            }
432            OpCode::Pickitem => {
433                let _ = pop_or_unknown(&mut stack);
434                let _ = pop_or_unknown(&mut stack);
435                stack.push(StackValue::unknown());
436            }
437            OpCode::Setitem => {
438                let _ = pop_or_unknown(&mut stack);
439                let _ = pop_or_unknown(&mut stack);
440                let _ = pop_or_unknown(&mut stack);
441            }
442            OpCode::Append => {
443                let _ = pop_or_unknown(&mut stack);
444                let _ = pop_or_unknown(&mut stack);
445            }
446            OpCode::Remove => {
447                let _ = pop_or_unknown(&mut stack);
448                let _ = pop_or_unknown(&mut stack);
449            }
450            OpCode::Clearitems => {
451                let _ = pop_or_unknown(&mut stack);
452            }
453            OpCode::Popitem => {
454                let _ = pop_or_unknown(&mut stack);
455                let _ = pop_or_unknown(&mut stack);
456                stack.push(StackValue::unknown());
457            }
458            OpCode::Size => {
459                let _ = pop_or_unknown(&mut stack);
460                stack.push(StackValue::with_type(ValueType::Integer));
461            }
462            OpCode::Haskey => {
463                let _ = pop_or_unknown(&mut stack);
464                let _ = pop_or_unknown(&mut stack);
465                stack.push(StackValue::with_type(ValueType::Boolean));
466            }
467            OpCode::Isnull => {
468                let _ = pop_or_unknown(&mut stack);
469                stack.push(StackValue::with_type(ValueType::Boolean));
470            }
471            OpCode::Istype => {
472                let _ = pop_or_unknown(&mut stack);
473                let _ = pop_or_unknown(&mut stack);
474                stack.push(StackValue::with_type(ValueType::Boolean));
475            }
476            OpCode::Convert => {
477                let value = pop_or_unknown(&mut stack);
478                let target = instr
479                    .operand
480                    .as_ref()
481                    .and_then(convert_target_type)
482                    .unwrap_or(ValueType::Any);
483                stack.push(StackValue::with_type(target.join(value.ty)));
484            }
485
486            // Arithmetic + comparisons (subset)
487            OpCode::Add
488            | OpCode::Sub
489            | OpCode::Mul
490            | OpCode::Div
491            | OpCode::Mod
492            | OpCode::Pow
493            | OpCode::Min
494            | OpCode::Max
495            | OpCode::Shl
496            | OpCode::Shr => {
497                let _ = pop_or_unknown(&mut stack);
498                let _ = pop_or_unknown(&mut stack);
499                stack.push(StackValue::with_type(ValueType::Integer));
500            }
501            OpCode::Modmul | OpCode::Modpow => {
502                let _ = pop_or_unknown(&mut stack);
503                let _ = pop_or_unknown(&mut stack);
504                let _ = pop_or_unknown(&mut stack);
505                stack.push(StackValue::with_type(ValueType::Integer));
506            }
507            OpCode::Within => {
508                let _ = pop_or_unknown(&mut stack);
509                let _ = pop_or_unknown(&mut stack);
510                let _ = pop_or_unknown(&mut stack);
511                stack.push(StackValue::with_type(ValueType::Boolean));
512            }
513            OpCode::Sqrt
514            | OpCode::Abs
515            | OpCode::Sign
516            | OpCode::Inc
517            | OpCode::Dec
518            | OpCode::Negate => {
519                let _ = pop_or_unknown(&mut stack);
520                stack.push(StackValue::with_type(ValueType::Integer));
521            }
522            OpCode::And | OpCode::Or | OpCode::Xor => {
523                let _ = pop_or_unknown(&mut stack);
524                let _ = pop_or_unknown(&mut stack);
525                stack.push(StackValue::with_type(ValueType::Integer));
526            }
527            OpCode::Invert => {
528                let _ = pop_or_unknown(&mut stack);
529                stack.push(StackValue::with_type(ValueType::Integer));
530            }
531            OpCode::Not => {
532                let _ = pop_or_unknown(&mut stack);
533                stack.push(StackValue::with_type(ValueType::Boolean));
534            }
535            OpCode::Booland | OpCode::Boolor => {
536                let _ = pop_or_unknown(&mut stack);
537                let _ = pop_or_unknown(&mut stack);
538                stack.push(StackValue::with_type(ValueType::Boolean));
539            }
540            OpCode::Equal
541            | OpCode::Numequal
542            | OpCode::Notequal
543            | OpCode::Numnotequal
544            | OpCode::Gt
545            | OpCode::Ge
546            | OpCode::Lt
547            | OpCode::Le
548            | OpCode::Nz => {
549                // NZ is unary, but treating it as "pop 1" is enough for type recovery.
550                let _ = pop_or_unknown(&mut stack);
551                if !matches!(instr.opcode, OpCode::Nz) {
552                    let _ = pop_or_unknown(&mut stack);
553                }
554                stack.push(StackValue::with_type(ValueType::Boolean));
555            }
556
557            // Most remaining opcodes are treated as unknown/no-op for typing purposes.
558            _ => {}
559        }
560    }
561}
562
563fn reverse_top(stack: &mut [StackValue], count: usize) {
564    if count == 0 || stack.len() < count {
565        return;
566    }
567    let start = stack.len() - count;
568    stack[start..].reverse();
569}
570
571fn pop_or_unknown(stack: &mut Vec<StackValue>) -> StackValue {
572    stack.pop().unwrap_or_else(StackValue::unknown)
573}
574
575fn push_slot(stack: &mut Vec<StackValue>, ty: Option<ValueType>) {
576    stack.push(StackValue::with_type(ty.unwrap_or(ValueType::Unknown)));
577}
578
579fn push_indexed_slot(
580    stack: &mut Vec<StackValue>,
581    slots: &mut Vec<ValueType>,
582    operand: Option<&Operand>,
583) {
584    let Some(Operand::U8(index)) = operand else {
585        stack.push(StackValue::unknown());
586        return;
587    };
588    let idx = *index as usize;
589    if idx >= slots.len() {
590        slots.resize(idx + 1, ValueType::Unknown);
591    }
592    push_slot(stack, slots.get(idx).copied());
593}
594
595fn store_slot(stack: &mut Vec<StackValue>, slots: &mut Vec<ValueType>, index: usize) {
596    let value = pop_or_unknown(stack);
597    if index >= slots.len() {
598        slots.resize(index + 1, ValueType::Unknown);
599    }
600    let current = slots[index];
601    slots[index] = current.join(value.ty);
602}
603
604fn store_indexed_slot(
605    stack: &mut Vec<StackValue>,
606    slots: &mut Vec<ValueType>,
607    operand: Option<&Operand>,
608) {
609    let Some(Operand::U8(index)) = operand else {
610        let _ = pop_or_unknown(stack);
611        return;
612    };
613    store_slot(stack, slots, *index as usize);
614}
615
616fn scan_slot_counts(instructions: &[&Instruction]) -> Option<(usize, usize)> {
617    for instr in instructions {
618        if instr.opcode != OpCode::Initslot {
619            continue;
620        }
621        if let Some(Operand::Bytes(bytes)) = &instr.operand {
622            if bytes.len() == 2 {
623                return Some((bytes[0] as usize, bytes[1] as usize));
624            }
625        }
626    }
627    None
628}
629
630fn scan_static_slot_count(instructions: &[Instruction]) -> Option<usize> {
631    for instr in instructions {
632        if instr.opcode != OpCode::Initsslot {
633            continue;
634        }
635        if let Some(Operand::U8(count)) = &instr.operand {
636            return Some(*count as usize);
637        }
638    }
639    None
640}
641
642fn int_literal_from_operand(operand: Option<&Operand>) -> Option<i64> {
643    match operand {
644        Some(Operand::I8(v)) => Some(*v as i64),
645        Some(Operand::I16(v)) => Some(*v as i64),
646        Some(Operand::I32(v)) => Some(*v as i64),
647        Some(Operand::I64(v)) => Some(*v),
648        Some(Operand::U8(v)) => Some(*v as i64),
649        Some(Operand::U16(v)) => Some(*v as i64),
650        Some(Operand::U32(v)) => Some(*v as i64),
651        _ => None,
652    }
653}
654
655fn type_from_manifest(kind: &str) -> ValueType {
656    match kind.to_ascii_lowercase().as_str() {
657        "any" => ValueType::Any,
658        "boolean" => ValueType::Boolean,
659        "integer" => ValueType::Integer,
660        "string" => ValueType::ByteString,
661        "bytearray" => ValueType::ByteString,
662        "signature" => ValueType::ByteString,
663        "hash160" => ValueType::ByteString,
664        "hash256" => ValueType::ByteString,
665        "array" => ValueType::Array,
666        "map" => ValueType::Map,
667        "interopinterface" => ValueType::InteropInterface,
668        _ => ValueType::Unknown,
669    }
670}
671
672fn convert_target_type(operand: &Operand) -> Option<ValueType> {
673    let byte = match operand {
674        Operand::U8(v) => *v,
675        Operand::I8(v) => *v as u8,
676        _ => return None,
677    };
678    match byte {
679        0x00 => Some(ValueType::Any),
680        0x10 => Some(ValueType::Pointer),
681        0x20 => Some(ValueType::Boolean),
682        0x21 => Some(ValueType::Integer),
683        0x28 => Some(ValueType::ByteString),
684        0x30 => Some(ValueType::Buffer),
685        0x40 => Some(ValueType::Array),
686        0x41 => Some(ValueType::Struct),
687        0x48 => Some(ValueType::Map),
688        0x60 => Some(ValueType::InteropInterface),
689        _ => None,
690    }
691}