neo_types/traits.rs
1// Copyright (c) 2025-2026 R3E Network
2// Licensed under the MIT License
3
4use crate::error::NeoResult;
5use crate::value::NeoValue;
6
7/// Neo N3 Contract trait
8pub trait NeoContract {
9 fn name() -> &'static str;
10 fn version() -> &'static str;
11 fn author() -> &'static str;
12 fn description() -> &'static str;
13}
14
15/// Neo N3 Contract Entry Point
16pub trait NeoContractEntry {
17 fn deploy() -> NeoResult<()>;
18 fn update() -> NeoResult<()>;
19 fn destroy() -> NeoResult<()>;
20}
21
22/// Neo N3 Contract Method trait
23pub trait NeoContractMethodTrait {
24 fn name() -> &'static str;
25 fn parameters() -> &'static [&'static str];
26 fn return_type() -> &'static str;
27 fn execute(args: &[NeoValue]) -> NeoResult<NeoValue>;
28}
29
30/// A type that can be deserialised from a NeoVM `StackItem` /
31/// `NeoValue`. This is the L9 `IInteroperable` equivalent in
32/// the C# devpack: any contract return type (or argument type)
33/// implements this trait, enabling `NeoContract::call_typed<T>`
34/// to round-trip the on-chain value into a typed Rust value.
35///
36/// The default `from_value` for the common types (`NeoInteger`,
37/// `NeoBoolean`, `NeoByteString`, `NeoString`) is provided;
38/// contract-specific types (e.g. a custom `Token` struct) can
39/// implement it by parsing the underlying `NeoValue` into the
40/// concrete type.
41pub trait FromNeoValue: Sized {
42 /// Convert a `NeoValue` into `Self`. Returns an error if
43 /// the value's runtime type doesn't match `Self`'s expected
44 /// type (e.g. trying to extract an Integer from a String).
45 fn from_value(value: &NeoValue) -> NeoResult<Self>;
46}
47
48/// Trait for static-method-style cross-contract calls. The L9
49/// `call_typed<T>` helper invokes a remote contract's method
50/// and decodes the return value into a typed Rust value via
51/// the `FromNeoValue` trait. The default implementation
52/// delegates to the existing `NeoVMSyscall::contract_call`
53/// path; contract code that wants a custom transport (e.g.
54/// off-chain simulation) can override `call_raw` and route
55/// the value through `call_typed`.
56///
57/// **L9 status**: shipped as a trait + impl. The default
58/// `call_raw` calls `NeoVMSyscall::contract_call`, which on
59/// the wasm32 path panics with a clear "see L6 design" message
60/// (B4 fix). On the host path it returns `NeoValue::Null`
61/// (existing B4 behaviour). The L6 cross-call executor will
62/// upgrade the wasm32 path to a real implementation; `call_typed`
63/// needs no changes for that.
64pub trait ContractCaller {
65 /// Invoke a remote contract's method with the given args
66 /// and return the raw `NeoValue`. The default impl uses
67 /// `NeoVMSyscall::contract_call`.
68 fn call_raw(
69 &self,
70 script_hash: &crate::bytestring::NeoByteString,
71 method: &str,
72 args: &[NeoValue],
73 call_flags: &crate::integer::NeoInteger,
74 ) -> NeoResult<NeoValue>;
75
76 /// Invoke a remote contract's method and decode the return
77 /// value into a typed Rust value via `FromNeoValue`.
78 fn call_typed<T: FromNeoValue>(
79 &self,
80 script_hash: &crate::bytestring::NeoByteString,
81 method: &str,
82 args: &[NeoValue],
83 call_flags: &crate::integer::NeoInteger,
84 ) -> NeoResult<T> {
85 let raw = self.call_raw(script_hash, method, args, call_flags)?;
86 T::from_value(&raw)
87 }
88}
89
90// Default `ContractCaller` impl: routes to the existing
91// `NeoVMSyscall::contract_call`. The host-mode test harness uses this;
92// production wasm32 calls go through the L6 cross-call executor. The
93// default `call_raw` body is defined in `neo-runtime::contract_caller`
94// (which has the dependency on `neo-syscalls`); the trait here just
95// declares the interface. Contract code uses `DefaultContractCaller`
96// via `neo-devpack::prelude::*`.
97
98// Common FromNeoValue impls for the standard Neo N3 types.
99impl FromNeoValue for NeoValue {
100 fn from_value(value: &NeoValue) -> NeoResult<Self> {
101 Ok(value.clone())
102 }
103}
104
105impl FromNeoValue for () {
106 fn from_value(_value: &NeoValue) -> NeoResult<Self> {
107 Ok(())
108 }
109}
110
111impl FromNeoValue for bool {
112 fn from_value(value: &NeoValue) -> NeoResult<Self> {
113 value
114 .as_boolean()
115 .map(|b| b.as_bool())
116 .ok_or_else(|| crate::error::NeoError::new("expected Boolean"))
117 }
118}
119
120impl FromNeoValue for String {
121 fn from_value(value: &NeoValue) -> NeoResult<Self> {
122 let bytes = value
123 .as_byte_string()
124 .map(|bs| bs.as_slice().to_vec())
125 .or_else(|| value.as_string().map(|s| s.as_str().as_bytes().to_vec()))
126 .ok_or_else(|| crate::error::NeoError::new("expected String"))?;
127 String::from_utf8(bytes)
128 .map_err(|e| crate::error::NeoError::new(&format!("invalid UTF-8: {e}")))
129 }
130}
131
132impl FromNeoValue for Vec<u8> {
133 fn from_value(value: &NeoValue) -> NeoResult<Self> {
134 value
135 .as_byte_string()
136 .map(|bs| bs.as_slice().to_vec())
137 .ok_or_else(|| crate::error::NeoError::new("expected ByteArray"))
138 }
139}
140
141// Pull NeoInteger in scope for the impls below.
142use crate::integer::NeoInteger;
143
144impl FromNeoValue for NeoInteger {
145 fn from_value(value: &NeoValue) -> NeoResult<Self> {
146 value
147 .as_integer()
148 .cloned()
149 .ok_or_else(|| crate::error::NeoError::new("expected Integer"))
150 }
151}
152
153impl FromNeoValue for i64 {
154 fn from_value(value: &NeoValue) -> NeoResult<Self> {
155 let n = <NeoInteger as FromNeoValue>::from_value(value)?;
156 Ok(n.as_i64_saturating())
157 }
158}