1use neo_types::*;
10use std::collections::HashMap;
11
12#[derive(Debug, Clone, Default)]
14pub struct MockStorage {
15 data: HashMap<Vec<u8>, Vec<u8>>,
16}
17
18impl MockStorage {
19 pub fn new() -> Self {
20 Self::default()
21 }
22
23 pub fn get(&self, key: &[u8]) -> Option<Vec<u8>> {
24 self.data.get(key).cloned()
25 }
26
27 pub fn put(&mut self, key: &[u8], value: &[u8]) {
28 self.data.insert(key.to_vec(), value.to_vec());
29 }
30
31 pub fn delete(&mut self, key: &[u8]) {
32 self.data.remove(key);
33 }
34
35 pub fn contains(&self, key: &[u8]) -> bool {
36 self.data.contains_key(key)
37 }
38
39 pub fn clear(&mut self) {
40 self.data.clear();
41 }
42
43 pub fn len(&self) -> usize {
44 self.data.len()
45 }
46
47 pub fn is_empty(&self) -> bool {
48 self.data.is_empty()
49 }
50
51 pub fn keys(&self) -> Vec<Vec<u8>> {
52 self.data.keys().cloned().collect()
53 }
54
55 pub fn find(&self, prefix: &[u8]) -> Vec<(Vec<u8>, Vec<u8>)> {
56 self.data
57 .iter()
58 .filter(|(k, _)| k.starts_with(prefix))
59 .map(|(k, v)| (k.clone(), v.clone()))
60 .collect()
61 }
62}
63
64#[derive(Debug, Clone)]
66pub struct MockStorageContext {
67 pub id: u32,
68 is_read_only: bool,
69}
70
71impl MockStorageContext {
72 pub fn new(id: u32) -> Self {
73 Self {
74 id,
75 is_read_only: false,
76 }
77 }
78
79 pub fn read_only(id: u32) -> Self {
80 Self {
81 id,
82 is_read_only: true,
83 }
84 }
85
86 pub fn is_read_only(&self) -> bool {
87 self.is_read_only
88 }
89}
90
91#[derive(Debug, Clone)]
96pub struct MockRuntime {
97 pub storage: MockStorage,
98 pub trigger: i32,
99 pub time: i64,
100 pub network: i64,
101 pub address_version: i32,
102 witnesses: Vec<NeoByteString>,
103 pub notifications: Vec<(NeoString, NeoArray<NeoValue>)>,
104 pub logs: Vec<String>,
105 pub script_container: Option<NeoArray<NeoValue>>,
106 pub calling_script_hash: Option<NeoByteString>,
107 pub executing_script_hash: Option<NeoByteString>,
108 pub entry_script_hash: Option<NeoByteString>,
109 pub gas_left: i64,
110 pub invocation_counter: i32,
111 storage_contexts: Vec<MockStorageContext>,
112}
113
114impl Default for MockRuntime {
115 fn default() -> Self {
116 Self::new()
117 }
118}
119
120impl MockRuntime {
121 pub fn new() -> Self {
122 Self {
123 storage: MockStorage::new(),
124 trigger: 0,
125 time: 0,
126 network: 860905102, address_version: 53,
128 witnesses: Vec::new(),
129 notifications: Vec::new(),
130 logs: Vec::new(),
131 script_container: None,
132 calling_script_hash: None,
133 executing_script_hash: None,
134 entry_script_hash: None,
135 gas_left: 100_000_000, invocation_counter: 0,
137 storage_contexts: vec![MockStorageContext::new(0)],
138 }
139 }
140
141 pub fn with_storage(mut self, storage: MockStorage) -> Self {
142 self.storage = storage;
143 self
144 }
145
146 pub fn with_trigger(mut self, trigger: i32) -> Self {
147 self.trigger = trigger;
148 self
149 }
150
151 pub fn with_time(mut self, time: i64) -> Self {
152 self.time = time;
153 self
154 }
155
156 pub fn with_network(mut self, network: i64) -> Self {
157 self.network = network;
158 self
159 }
160
161 pub fn with_witness(mut self, address: &[u8]) -> Self {
162 self.witnesses.push(NeoByteString::from_slice(address));
163 self
164 }
165
166 pub fn with_script_container(mut self, container: NeoArray<NeoValue>) -> Self {
167 self.script_container = Some(container);
168 self
169 }
170
171 pub fn with_calling_script_hash(mut self, hash: &[u8]) -> Self {
172 self.calling_script_hash = Some(NeoByteString::from_slice(hash));
173 self
174 }
175
176 pub fn with_executing_script_hash(mut self, hash: &[u8]) -> Self {
177 self.executing_script_hash = Some(NeoByteString::from_slice(hash));
178 self
179 }
180
181 pub fn with_entry_script_hash(mut self, hash: &[u8]) -> Self {
182 self.entry_script_hash = Some(NeoByteString::from_slice(hash));
183 self
184 }
185
186 pub fn with_gas_left(mut self, gas: i64) -> Self {
187 self.gas_left = gas;
188 self
189 }
190
191 pub fn storage_ref(&self) -> &MockStorage {
192 &self.storage
193 }
194
195 pub fn storage_mut(&mut self) -> &mut MockStorage {
196 &mut self.storage
197 }
198
199 pub fn trigger_value(&self) -> i32 {
200 self.trigger
201 }
202
203 pub fn time_value(&self) -> i64 {
204 self.time
205 }
206
207 pub fn network_value(&self) -> i64 {
208 self.network
209 }
210
211 pub fn address_version_value(&self) -> i32 {
212 self.address_version
213 }
214
215 pub fn witnesses(&self) -> &[NeoByteString] {
216 &self.witnesses
217 }
218
219 pub fn witnesses_mut(&mut self) -> &mut Vec<NeoByteString> {
220 &mut self.witnesses
221 }
222
223 pub fn notifications(&self) -> &[(NeoString, NeoArray<NeoValue>)] {
224 &self.notifications
225 }
226
227 pub fn logs(&self) -> &[String] {
228 &self.logs
229 }
230
231 pub fn clear_notifications(&mut self) {
232 self.notifications.clear();
233 }
234
235 pub fn clear_logs(&mut self) {
236 self.logs.clear();
237 }
238
239 pub fn add_log(&mut self, message: &str) {
240 self.logs.push(message.to_string());
241 }
242
243 pub fn add_notification(&mut self, event: NeoString, state: NeoArray<NeoValue>) {
244 self.notifications.push((event, state));
245 }
246
247 pub fn check_witness(&self, hash: &[u8]) -> bool {
248 self.witnesses.iter().any(|w| w.as_slice() == hash)
249 }
250
251 pub fn calling_script_hash(&self) -> Option<&NeoByteString> {
252 self.calling_script_hash.as_ref()
253 }
254
255 pub fn executing_script_hash(&self) -> Option<&NeoByteString> {
256 self.executing_script_hash.as_ref()
257 }
258
259 pub fn entry_script_hash(&self) -> Option<&NeoByteString> {
260 self.entry_script_hash.as_ref()
261 }
262
263 pub fn gas_left(&self) -> i64 {
264 self.gas_left
265 }
266
267 pub fn consume_gas(&mut self, amount: i64) {
268 if amount <= 0 {
269 return;
270 }
271
272 self.gas_left = self.gas_left.checked_sub(amount).unwrap_or(0).max(0);
273 }
274
275 pub fn invocation_counter(&self) -> i32 {
276 self.invocation_counter
277 }
278
279 pub fn increment_invocation_counter(&mut self) {
280 self.invocation_counter += 1;
281 }
282
283 pub fn get_storage_context(&mut self) -> MockStorageContext {
284 let id = self.storage_contexts.len() as u32;
285 self.storage_contexts.push(MockStorageContext::new(id));
286 MockStorageContext::new(id)
287 }
288
289 pub fn get_read_only_storage_context(&mut self) -> MockStorageContext {
290 let id = self.storage_contexts.len() as u32;
291 self.storage_contexts
292 .push(MockStorageContext::read_only(id));
293 MockStorageContext::read_only(id)
294 }
295
296 pub fn storage_get(&self, key: &[u8]) -> Option<Vec<u8>> {
298 self.storage.get(key)
299 }
300
301 pub fn storage_put(&mut self, key: &[u8], value: &[u8]) {
303 self.storage.put(key, value);
304 }
305
306 pub fn storage_put_with_context(
308 &mut self,
309 context: &MockStorageContext,
310 key: &[u8],
311 value: &[u8],
312 ) -> NeoResult<()> {
313 self.ensure_context_valid(context)?;
314 if context.is_read_only() {
315 return Err(NeoError::InvalidOperation);
316 }
317
318 self.storage.put(key, value);
319 Ok(())
320 }
321
322 pub fn storage_delete(&mut self, key: &[u8]) {
324 self.storage.delete(key);
325 }
326
327 pub fn storage_delete_with_context(
329 &mut self,
330 context: &MockStorageContext,
331 key: &[u8],
332 ) -> NeoResult<()> {
333 self.ensure_context_valid(context)?;
334 if context.is_read_only() {
335 return Err(NeoError::InvalidOperation);
336 }
337
338 self.storage.delete(key);
339 Ok(())
340 }
341
342 pub fn storage_find(&self, prefix: &[u8]) -> Vec<(Vec<u8>, Vec<u8>)> {
344 self.storage.find(prefix)
345 }
346
347 pub fn reset(&mut self) {
349 self.notifications.clear();
350 self.logs.clear();
351 self.invocation_counter = 0;
352 }
353
354 pub fn clear_storage_contexts(&mut self) {
356 self.storage_contexts.clear();
357 self.storage_contexts.push(MockStorageContext::new(0));
358 }
359
360 fn ensure_context_valid(&self, context: &MockStorageContext) -> NeoResult<()> {
361 let is_known_context = self
362 .storage_contexts
363 .iter()
364 .any(|ctx| ctx.id == context.id && ctx.is_read_only == context.is_read_only);
365 if !is_known_context {
366 return Err(NeoError::InvalidArgument);
367 }
368
369 Ok(())
370 }
371}
372
373pub struct MockRuntimeBuilder {
375 runtime: MockRuntime,
376}
377
378impl MockRuntimeBuilder {
379 pub fn new() -> Self {
380 Self {
381 runtime: MockRuntime::new(),
382 }
383 }
384
385 pub fn storage(mut self, storage: MockStorage) -> Self {
386 self.runtime.storage = storage;
387 self
388 }
389
390 pub fn trigger(mut self, trigger: i32) -> Self {
391 self.runtime.trigger = trigger;
392 self
393 }
394
395 pub fn time(mut self, time: i64) -> Self {
396 self.runtime.time = time;
397 self
398 }
399
400 pub fn network(mut self, network: i64) -> Self {
401 self.runtime.network = network;
402 self
403 }
404
405 pub fn witness(mut self, address: &[u8]) -> Self {
406 self.runtime
407 .witnesses
408 .push(NeoByteString::from_slice(address));
409 self
410 }
411
412 pub fn script_hash(mut self, hash: &[u8]) -> Self {
413 self.runtime.executing_script_hash = Some(NeoByteString::from_slice(hash));
414 self
415 }
416
417 pub fn gas(mut self, gas: i64) -> Self {
418 self.runtime.gas_left = gas;
419 self
420 }
421
422 pub fn build(self) -> MockRuntime {
423 self.runtime
424 }
425}
426
427impl Default for MockRuntimeBuilder {
428 fn default() -> Self {
429 Self::new()
430 }
431}