Skip to main content

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}