1use 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
19pub 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#[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
65pub 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
94pub 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
211pub 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
221pub 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 pub fn get_time() -> NeoResult<NeoInteger> {
252 Self::call_integer("System.Runtime.GetTime")
253 }
254
255 pub fn check_witness(account: &NeoByteString) -> NeoResult<NeoBoolean> {
257 let args = [NeoValue::from(account.clone())];
259 Self::call_boolean("System.Runtime.CheckWitness", &args)
260 }
261
262 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 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 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 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}