Skip to main content

neo_syscalls/
lib.rs

1// Copyright (c) 2025 R3E Network
2// Licensed under the MIT License
3
4//! Neo N3 System Calls
5//!
6//! This crate provides bindings to Neo N3 system calls for smart contract development.
7
8use neo_types::*;
9use once_cell::sync::Lazy;
10use std::collections::HashMap;
11use std::slice::Iter;
12use std::sync::atomic::{AtomicU32, Ordering};
13use std::sync::{Arc, RwLock};
14
15mod syscalls;
16
17pub use syscalls::SYSCALLS;
18
19/// Neo N3 System Call Registry
20pub struct NeoVMSyscallRegistry {
21    syscalls: &'static [NeoVMSyscallInfo],
22}
23
24impl NeoVMSyscallRegistry {
25    pub const fn new(syscalls: &'static [NeoVMSyscallInfo]) -> Self {
26        Self { syscalls }
27    }
28
29    pub fn get_syscall(&self, name: &str) -> Option<&NeoVMSyscallInfo> {
30        self.syscalls.iter().find(|s| s.name == name)
31    }
32
33    pub fn get_syscall_by_hash(&self, hash: u32) -> Option<&NeoVMSyscallInfo> {
34        self.syscalls.iter().find(|s| s.hash == hash)
35    }
36
37    pub fn has_syscall(&self, name: &str) -> bool {
38        self.get_syscall(name).is_some()
39    }
40
41    pub fn get_instance() -> Self {
42        Self::new(SYSCALLS)
43    }
44
45    pub fn iter(&self) -> Iter<'static, NeoVMSyscallInfo> {
46        self.syscalls.iter()
47    }
48
49    pub fn names(&self) -> impl Iterator<Item = &'static str> {
50        self.syscalls.iter().map(|info| info.name)
51    }
52}
53
54/// Neo N3 System Call Information
55#[derive(Debug, Clone, PartialEq, Eq)]
56pub struct NeoVMSyscallInfo {
57    pub name: &'static str,
58    pub hash: u32,
59    pub parameters: &'static [&'static str],
60    pub return_type: &'static str,
61    pub gas_cost: u32,
62    pub description: &'static str,
63}
64
65/// Neo N3 System Call Lowering
66pub struct NeoVMSyscallLowering;
67
68impl Default for NeoVMSyscallLowering {
69    fn default() -> Self {
70        Self::new()
71    }
72}
73
74impl NeoVMSyscallLowering {
75    pub fn new() -> Self {
76        Self
77    }
78
79    pub fn lower_syscall(&self, name: &str) -> NeoResult<u32> {
80        let registry = NeoVMSyscallRegistry::get_instance();
81        if let Some(syscall) = registry.get_syscall(name) {
82            Ok(syscall.hash)
83        } else {
84            Err(NeoError::new(&format!("Unknown syscall: {}", name)))
85        }
86    }
87
88    pub fn can_lower(&self, name: &str) -> bool {
89        let registry = NeoVMSyscallRegistry::get_instance();
90        registry.has_syscall(name)
91    }
92}
93
94/// Neo N3 System Call Registry Instance
95pub static SYSCALL_REGISTRY: NeoVMSyscallRegistry = NeoVMSyscallRegistry::new(SYSCALLS);
96
97const DEFAULT_CONTRACT_HASH: [u8; 20] = [0u8; 20];
98
99type ContractStore = Arc<RwLock<HashMap<Vec<u8>, Vec<u8>>>>;
100
101#[derive(Clone)]
102struct ContextHandle {
103    read_only: bool,
104    contract: [u8; 20],
105    store: ContractStore,
106}
107
108#[allow(clippy::type_complexity)]
109struct StorageState {
110    next_context: AtomicU32,
111    contexts: RwLock<HashMap<u32, ContextHandle>>,
112    contract_stores: RwLock<HashMap<[u8; 20], ContractStore>>,
113}
114
115impl StorageState {
116    fn new() -> Self {
117        Self {
118            next_context: AtomicU32::new(1),
119            contexts: RwLock::new(HashMap::new()),
120            contract_stores: RwLock::new(HashMap::new()),
121        }
122    }
123
124    fn create_context(&self, contract: [u8; 20], read_only: bool) -> NeoResult<NeoStorageContext> {
125        let store = self.get_or_create_store(contract);
126        let id = self.next_context.fetch_add(1, Ordering::SeqCst);
127        let handle = ContextHandle {
128            read_only,
129            contract,
130            store,
131        };
132        self.contexts
133            .write()
134            .map_err(|_| NeoError::InvalidState)?
135            .insert(id, handle);
136        Ok(if read_only {
137            NeoStorageContext::read_only(id)
138        } else {
139            NeoStorageContext::new(id)
140        })
141    }
142
143    fn clone_as_read_only(&self, context: &NeoStorageContext) -> NeoResult<NeoStorageContext> {
144        let handle = self
145            .contexts
146            .read()
147            .map_err(|_| NeoError::InvalidState)?
148            .get(&context.id())
149            .cloned()
150            .ok_or(NeoError::InvalidState)?;
151        let id = self.next_context.fetch_add(1, Ordering::SeqCst);
152        let ro_handle = ContextHandle {
153            read_only: true,
154            contract: handle.contract,
155            store: handle.store,
156        };
157        self.contexts
158            .write()
159            .map_err(|_| NeoError::InvalidState)?
160            .insert(id, ro_handle);
161        Ok(NeoStorageContext::read_only(id))
162    }
163
164    fn get_handle(&self, context: &NeoStorageContext) -> NeoResult<ContextHandle> {
165        self.contexts
166            .read()
167            .map_err(|_| NeoError::InvalidState)?
168            .get(&context.id())
169            .cloned()
170            .ok_or(NeoError::InvalidState)
171    }
172
173    fn get_or_create_store(&self, contract: [u8; 20]) -> ContractStore {
174        let mut stores = self
175            .contract_stores
176            .write()
177            .expect("storage contract lock poisoned");
178        stores
179            .entry(contract)
180            .or_insert_with(|| Arc::new(RwLock::new(HashMap::new())))
181            .clone()
182    }
183}
184
185static STORAGE_STATE: Lazy<StorageState> = Lazy::new(StorageState::new);
186
187fn find_syscall(name: &str) -> Option<&'static NeoVMSyscallInfo> {
188    SYSCALLS.iter().find(|info| info.name == name)
189}
190
191fn syscall_hash(name: &str) -> u32 {
192    find_syscall(name).expect("unknown syscall").hash
193}
194
195fn default_value_for(return_type: &str) -> NeoValue {
196    match return_type {
197        "Void" => NeoValue::Null,
198        "Boolean" => NeoBoolean::TRUE.into(),
199        "Integer" => NeoInteger::new(0).into(),
200        "Hash160" => NeoByteString::new(vec![0u8; 20]).into(),
201        "ByteString" => NeoByteString::new(vec![0u8; 1]).into(),
202        "String" => NeoString::from_str("Neo N3").into(),
203        "Array" => NeoArray::<NeoValue>::new().into(),
204        "Iterator" => NeoArray::<NeoValue>::new().into(),
205        "StackItem" => NeoArray::<NeoValue>::new().into(),
206        "StorageContext" => NeoValue::Null,
207        _ => NeoValue::Null,
208    }
209}
210
211/// Neo N3 System Call Function
212pub fn neovm_syscall(hash: u32, _args: &[NeoValue]) -> NeoResult<NeoValue> {
213    let registry = NeoVMSyscallRegistry::get_instance();
214    if let Some(info) = registry.get_syscall_by_hash(hash) {
215        Ok(default_value_for(info.return_type))
216    } else {
217        Ok(NeoValue::Null)
218    }
219}
220
221/// Neo N3 System Call Wrapper
222pub struct NeoVMSyscall;
223
224impl NeoVMSyscall {
225    fn call_integer(name: &str) -> NeoResult<NeoInteger> {
226        let value = neovm_syscall(syscall_hash(name), &[])?;
227        value.as_integer().ok_or(NeoError::InvalidType)
228    }
229
230    fn call_boolean(name: &str, args: &[NeoValue]) -> NeoResult<NeoBoolean> {
231        let value = neovm_syscall(syscall_hash(name), args)?;
232        value.as_boolean().ok_or(NeoError::InvalidType)
233    }
234
235    fn call_bytes(name: &str) -> NeoResult<NeoByteString> {
236        let value = neovm_syscall(syscall_hash(name), &[])?;
237        value.as_byte_string().cloned().ok_or(NeoError::InvalidType)
238    }
239
240    fn call_string(name: &str) -> NeoResult<NeoString> {
241        let value = neovm_syscall(syscall_hash(name), &[])?;
242        value.as_string().cloned().ok_or(NeoError::InvalidType)
243    }
244
245    fn call_array(name: &str, args: &[NeoValue]) -> NeoResult<NeoArray<NeoValue>> {
246        let value = neovm_syscall(syscall_hash(name), args)?;
247        value.as_array().cloned().ok_or(NeoError::InvalidType)
248    }
249
250    /// Get current timestamp
251    pub fn get_time() -> NeoResult<NeoInteger> {
252        Self::call_integer("System.Runtime.GetTime")
253    }
254
255    /// Check if the specified account is a witness
256    pub fn check_witness(account: &NeoByteString) -> NeoResult<NeoBoolean> {
257        // Note: Clone required here as NeoValue takes ownership
258        let args = [NeoValue::from(account.clone())];
259        Self::call_boolean("System.Runtime.CheckWitness", &args)
260    }
261
262    /// Send notification to the runtime.
263    ///
264    /// # Arguments
265    /// * `event` - The event name
266    /// * `state` - The event state as an array of values
267    ///
268    /// # Note
269    /// This function clones the event and state arguments as NeoValue requires owned data.
270    pub fn notify(event: &NeoString, state: &NeoArray<NeoValue>) -> NeoResult<()> {
271        let args = [NeoValue::from(event.clone()), NeoValue::from(state.clone())];
272        neovm_syscall(syscall_hash("System.Runtime.Notify"), &args)?;
273        Ok(())
274    }
275
276    /// Log message to the runtime.
277    ///
278    /// # Note
279    /// This function clones the message as NeoValue requires owned data.
280    pub fn log(message: &NeoString) -> NeoResult<()> {
281        let args = [NeoValue::from(message.clone())];
282        neovm_syscall(syscall_hash("System.Runtime.Log"), &args)?;
283        Ok(())
284    }
285
286    /// Platform identifier
287    pub fn platform() -> NeoResult<NeoString> {
288        Self::call_string("System.Runtime.Platform")
289    }
290
291    pub fn get_trigger() -> NeoResult<NeoInteger> {
292        Self::call_integer("System.Runtime.GetTrigger")
293    }
294
295    pub fn get_invocation_counter() -> NeoResult<NeoInteger> {
296        Self::call_integer("System.Runtime.GetInvocationCounter")
297    }
298
299    pub fn get_random() -> NeoResult<NeoInteger> {
300        Self::call_integer("System.Runtime.GetRandom")
301    }
302
303    pub fn get_network() -> NeoResult<NeoInteger> {
304        Self::call_integer("System.Runtime.GetNetwork")
305    }
306
307    pub fn get_address_version() -> NeoResult<NeoInteger> {
308        Self::call_integer("System.Runtime.GetAddressVersion")
309    }
310
311    pub fn get_gas_left() -> NeoResult<NeoInteger> {
312        Self::call_integer("System.Runtime.GasLeft")
313    }
314
315    pub fn get_calling_script_hash() -> NeoResult<NeoByteString> {
316        Self::call_bytes("System.Runtime.GetCallingScriptHash")
317    }
318
319    pub fn get_entry_script_hash() -> NeoResult<NeoByteString> {
320        Self::call_bytes("System.Runtime.GetEntryScriptHash")
321    }
322
323    pub fn get_executing_script_hash() -> NeoResult<NeoByteString> {
324        Self::call_bytes("System.Runtime.GetExecutingScriptHash")
325    }
326
327    /// Get notifications for the specified script hash, or all notifications if None.
328    pub fn get_notifications(script_hash: Option<&NeoByteString>) -> NeoResult<NeoArray<NeoValue>> {
329        let args: Vec<NeoValue> = script_hash
330            .map(|hash| vec![NeoValue::from(hash.clone())])
331            .unwrap_or_default();
332        Self::call_array("System.Runtime.GetNotifications", &args)
333    }
334
335    pub fn get_script_container() -> NeoResult<NeoArray<NeoValue>> {
336        Self::call_array("System.Runtime.GetScriptContainer", &[])
337    }
338
339    pub fn storage_get_context() -> NeoResult<NeoStorageContext> {
340        STORAGE_STATE.create_context(DEFAULT_CONTRACT_HASH, false)
341    }
342
343    pub fn storage_get_read_only_context() -> NeoResult<NeoStorageContext> {
344        STORAGE_STATE.create_context(DEFAULT_CONTRACT_HASH, true)
345    }
346
347    pub fn storage_as_read_only(context: &NeoStorageContext) -> NeoResult<NeoStorageContext> {
348        STORAGE_STATE.clone_as_read_only(context)
349    }
350
351    pub fn storage_get(
352        context: &NeoStorageContext,
353        key: &NeoByteString,
354    ) -> NeoResult<NeoByteString> {
355        let handle = STORAGE_STATE.get_handle(context)?;
356        let store = handle.store.read().map_err(|_| NeoError::InvalidState)?;
357        let value = store.get(key.as_slice()).cloned().unwrap_or_else(Vec::new);
358        Ok(NeoByteString::new(value))
359    }
360
361    pub fn storage_put(
362        context: &NeoStorageContext,
363        key: &NeoByteString,
364        value: &NeoByteString,
365    ) -> NeoResult<()> {
366        let handle = STORAGE_STATE.get_handle(context)?;
367        if handle.read_only {
368            return Err(NeoError::InvalidOperation);
369        }
370        let mut store = handle.store.write().map_err(|_| NeoError::InvalidState)?;
371        store.insert(key.as_slice().to_vec(), value.as_slice().to_vec());
372        Ok(())
373    }
374
375    pub fn storage_delete(context: &NeoStorageContext, key: &NeoByteString) -> NeoResult<()> {
376        let handle = STORAGE_STATE.get_handle(context)?;
377        if handle.read_only {
378            return Err(NeoError::InvalidOperation);
379        }
380        let mut store = handle.store.write().map_err(|_| NeoError::InvalidState)?;
381        store.remove(key.as_slice());
382        Ok(())
383    }
384
385    pub fn storage_find(
386        context: &NeoStorageContext,
387        prefix: &NeoByteString,
388    ) -> NeoResult<NeoIterator<NeoValue>> {
389        let handle = STORAGE_STATE.get_handle(context)?;
390        let prefix_bytes = prefix.as_slice();
391        let store = handle.store.read().map_err(|_| NeoError::InvalidState)?;
392        let matches: Vec<NeoValue> = store
393            .iter()
394            .filter_map(|(key_bytes, value)| {
395                if key_bytes.starts_with(prefix_bytes) {
396                    let mut entry = NeoStruct::new();
397                    entry.set_field("key", NeoValue::from(NeoByteString::from_slice(key_bytes)));
398                    entry.set_field("value", NeoValue::from(NeoByteString::from_slice(value)));
399                    Some(NeoValue::from(entry))
400                } else {
401                    None
402                }
403            })
404            .collect();
405        Ok(NeoIterator::new(matches))
406    }
407}