zexus 1.8.0 → 1.8.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/README.md +34 -6
  2. package/bin/zexus +12 -2
  3. package/bin/zpics +12 -2
  4. package/bin/zpm +12 -2
  5. package/bin/zx +12 -2
  6. package/bin/zx-deploy +12 -2
  7. package/bin/zx-dev +12 -2
  8. package/bin/zx-run +12 -2
  9. package/package.json +2 -1
  10. package/rust_core/Cargo.lock +603 -0
  11. package/rust_core/Cargo.toml +26 -0
  12. package/rust_core/README.md +15 -0
  13. package/rust_core/pyproject.toml +25 -0
  14. package/rust_core/src/binary_bytecode.rs +543 -0
  15. package/rust_core/src/contract_vm.rs +643 -0
  16. package/rust_core/src/executor.rs +847 -0
  17. package/rust_core/src/hasher.rs +90 -0
  18. package/rust_core/src/lib.rs +71 -0
  19. package/rust_core/src/merkle.rs +128 -0
  20. package/rust_core/src/rust_vm.rs +2313 -0
  21. package/rust_core/src/signature.rs +79 -0
  22. package/rust_core/src/state_adapter.rs +281 -0
  23. package/rust_core/src/validator.rs +116 -0
  24. package/scripts/postinstall.js +204 -21
  25. package/src/zexus/__init__.py +1 -1
  26. package/src/zexus/cli/main.py +1 -1
  27. package/src/zexus/cli/zpm.py +1 -1
  28. package/src/zexus/evaluator/bytecode_compiler.py +150 -52
  29. package/src/zexus/evaluator/core.py +151 -809
  30. package/src/zexus/evaluator/expressions.py +27 -22
  31. package/src/zexus/evaluator/functions.py +171 -126
  32. package/src/zexus/evaluator/statements.py +55 -112
  33. package/src/zexus/module_cache.py +20 -9
  34. package/src/zexus/object.py +330 -38
  35. package/src/zexus/parser/parser.py +103 -23
  36. package/src/zexus/parser/strategy_context.py +318 -6
  37. package/src/zexus/parser/strategy_structural.py +2 -2
  38. package/src/zexus/persistence.py +46 -17
  39. package/src/zexus/security.py +140 -234
  40. package/src/zexus/type_checker.py +44 -5
  41. package/src/zexus/vm/binary_bytecode.py +7 -3
  42. package/src/zexus/vm/bytecode.py +6 -0
  43. package/src/zexus/vm/cache.py +24 -46
  44. package/src/zexus/vm/compiler.py +549 -68
  45. package/src/zexus/vm/memory_pool.py +21 -9
  46. package/src/zexus/vm/vm.py +609 -95
  47. package/src/zexus/zpm/package_manager.py +1 -1
  48. package/src/zexus.egg-info/PKG-INFO +56 -12
  49. package/src/zexus.egg-info/SOURCES.txt +14 -0
  50. package/src/zexus.egg-info/entry_points.txt +5 -1
  51. package/src/zexus.egg-info/requires.txt +26 -0
@@ -0,0 +1,2313 @@
1
+ // ─────────────────────────────────────────────────────────────────────
2
+ // Zexus Blockchain — Rust Bytecode Interpreter (Phase 2 + Phase 6)
3
+ // ─────────────────────────────────────────────────────────────────────
4
+ //
5
+ // A complete stack-machine bytecode interpreter written in Rust that
6
+ // executes .zxc binary bytecode **without GIL overhead**.
7
+ //
8
+ // Architecture:
9
+ //
10
+ // 1. Deserialize .zxc bytes → ZxcModule (Phase 1, binary_bytecode.rs)
11
+ // 2. Convert ZxcModule into VM-internal representation (VmProgram)
12
+ // 3. Execute VmProgram on the RustVM stack machine
13
+ // 4. Return result to Python via PyO3
14
+ //
15
+ // Phase 6: Contract builtins (keccak256, verify_sig, emit, get_balance,
16
+ // transfer, block_number, block_timestamp) execute in pure Rust.
17
+ // Cross-contract calls (contract_call, static_call, delegate_call)
18
+ // still fall back to Python when needed.
19
+ //
20
+ // Value system:
21
+ // The VM operates on `ZxValue` — a Rust enum covering all Zexus
22
+ // runtime types including Null, Bool, Int, Float, String, List, Map.
23
+ // Python objects that cannot be represented natively (e.g. callables,
24
+ // AST nodes) are stored as `ZxValue::PyObject(PyObject)` and require
25
+ // GIL acquisition only when accessed.
26
+ //
27
+ // Gas metering:
28
+ // Each opcode has an associated gas cost (matching the Python VM).
29
+ // The gas budget is checked before every instruction. When gas is
30
+ // exhausted an `OutOfGasError` is raised.
31
+ //
32
+ // Blockchain state:
33
+ // STATE_READ / STATE_WRITE operate on a `HashMap<String, ZxValue>`
34
+ // that is passed in from Python and returned after execution.
35
+ // TX_BEGIN / TX_COMMIT / TX_REVERT provide snapshot-based
36
+ // transactions.
37
+
38
+ use pyo3::prelude::*;
39
+ use pyo3::types::{PyBool, PyBytes, PyDict, PyList};
40
+ use pyo3::ToPyObject;
41
+ use std::collections::HashMap;
42
+ use std::fmt;
43
+
44
+ use crate::binary_bytecode::{self, Operand, ZxcModule, ZxcValue};
45
+ use crate::signature::verify_single;
46
+ use sha2::{Digest, Sha256};
47
+ use tiny_keccak::{Hasher as TinyHasher, Keccak};
48
+
49
+ // ── Opcode constants (mirrors Python Opcode IntEnum) ─────────────────
50
+
51
+ #[allow(non_camel_case_types, dead_code)]
52
+ #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
53
+ #[repr(u16)]
54
+ pub enum Op {
55
+ // Stack
56
+ LOAD_CONST = 1,
57
+ LOAD_NAME = 2,
58
+ STORE_NAME = 3,
59
+ STORE_FUNC = 4,
60
+ POP = 5,
61
+ DUP = 6,
62
+
63
+ // Arithmetic
64
+ ADD = 10,
65
+ SUB = 11,
66
+ MUL = 12,
67
+ DIV = 13,
68
+ MOD = 14,
69
+ POW = 15,
70
+ NEG = 16,
71
+
72
+ // Comparison
73
+ EQ = 20,
74
+ NEQ = 21,
75
+ LT = 22,
76
+ GT = 23,
77
+ LTE = 24,
78
+ GTE = 25,
79
+
80
+ // Logical
81
+ AND = 30,
82
+ OR = 31,
83
+ NOT = 32,
84
+
85
+ // Control flow
86
+ JUMP = 40,
87
+ JUMP_IF_FALSE = 41,
88
+ JUMP_IF_TRUE = 42,
89
+ RETURN = 43,
90
+
91
+ // Calls
92
+ CALL_NAME = 50,
93
+ CALL_FUNC_CONST = 51,
94
+ CALL_TOP = 52,
95
+ CALL_BUILTIN = 53,
96
+ CALL_METHOD = 54,
97
+
98
+ // Collections
99
+ BUILD_LIST = 60,
100
+ BUILD_MAP = 61,
101
+ BUILD_SET = 62,
102
+ INDEX = 63,
103
+ SLICE = 64,
104
+ GET_ATTR = 65,
105
+
106
+ // Async
107
+ SPAWN = 70,
108
+ AWAIT = 71,
109
+ SPAWN_CALL = 72,
110
+
111
+ // Events
112
+ REGISTER_EVENT = 80,
113
+ EMIT_EVENT = 81,
114
+
115
+ // Modules
116
+ IMPORT = 90,
117
+ EXPORT = 91,
118
+
119
+ // Blockchain
120
+ HASH_BLOCK = 110,
121
+ VERIFY_SIGNATURE = 111,
122
+ MERKLE_ROOT = 112,
123
+ STATE_READ = 113,
124
+ STATE_WRITE = 114,
125
+ TX_BEGIN = 115,
126
+ TX_COMMIT = 116,
127
+ TX_REVERT = 117,
128
+ GAS_CHARGE = 118,
129
+ LEDGER_APPEND = 119,
130
+
131
+ // Security & Contracts
132
+ REQUIRE = 130,
133
+ DEFINE_CONTRACT = 131,
134
+ DEFINE_ENTITY = 132,
135
+ AUDIT_LOG = 136,
136
+ RESTRICT_ACCESS = 137,
137
+
138
+ // Exception handling
139
+ SETUP_TRY = 140,
140
+ POP_TRY = 141,
141
+ THROW = 142,
142
+
143
+ // Iteration
144
+ FOR_ITER = 150,
145
+
146
+ // System
147
+ ENABLE_ERROR_MODE = 160,
148
+
149
+ // I/O
150
+ PRINT = 250,
151
+
152
+ // Parallel (no-op markers)
153
+ PARALLEL_START = 300,
154
+ PARALLEL_END = 301,
155
+
156
+ // NOP
157
+ NOP = 255,
158
+
159
+ // Unknown — anything not recognised
160
+ UNKNOWN = 0xFFFF,
161
+ }
162
+
163
+ impl Op {
164
+ fn from_u16(v: u16) -> Self {
165
+ match v {
166
+ 1 => Op::LOAD_CONST,
167
+ 2 => Op::LOAD_NAME,
168
+ 3 => Op::STORE_NAME,
169
+ 4 => Op::STORE_FUNC,
170
+ 5 => Op::POP,
171
+ 6 => Op::DUP,
172
+ 10 => Op::ADD,
173
+ 11 => Op::SUB,
174
+ 12 => Op::MUL,
175
+ 13 => Op::DIV,
176
+ 14 => Op::MOD,
177
+ 15 => Op::POW,
178
+ 16 => Op::NEG,
179
+ 20 => Op::EQ,
180
+ 21 => Op::NEQ,
181
+ 22 => Op::LT,
182
+ 23 => Op::GT,
183
+ 24 => Op::LTE,
184
+ 25 => Op::GTE,
185
+ 30 => Op::AND,
186
+ 31 => Op::OR,
187
+ 32 => Op::NOT,
188
+ 40 => Op::JUMP,
189
+ 41 => Op::JUMP_IF_FALSE,
190
+ 42 => Op::JUMP_IF_TRUE,
191
+ 43 => Op::RETURN,
192
+ 50 => Op::CALL_NAME,
193
+ 51 => Op::CALL_FUNC_CONST,
194
+ 52 => Op::CALL_TOP,
195
+ 53 => Op::CALL_BUILTIN,
196
+ 54 => Op::CALL_METHOD,
197
+ 60 => Op::BUILD_LIST,
198
+ 61 => Op::BUILD_MAP,
199
+ 62 => Op::BUILD_SET,
200
+ 63 => Op::INDEX,
201
+ 64 => Op::SLICE,
202
+ 65 => Op::GET_ATTR,
203
+ 70 => Op::SPAWN,
204
+ 71 => Op::AWAIT,
205
+ 72 => Op::SPAWN_CALL,
206
+ 80 => Op::REGISTER_EVENT,
207
+ 81 => Op::EMIT_EVENT,
208
+ 90 => Op::IMPORT,
209
+ 91 => Op::EXPORT,
210
+ 110 => Op::HASH_BLOCK,
211
+ 111 => Op::VERIFY_SIGNATURE,
212
+ 112 => Op::MERKLE_ROOT,
213
+ 113 => Op::STATE_READ,
214
+ 114 => Op::STATE_WRITE,
215
+ 115 => Op::TX_BEGIN,
216
+ 116 => Op::TX_COMMIT,
217
+ 117 => Op::TX_REVERT,
218
+ 118 => Op::GAS_CHARGE,
219
+ 119 => Op::LEDGER_APPEND,
220
+ 130 => Op::REQUIRE,
221
+ 131 => Op::DEFINE_CONTRACT,
222
+ 132 => Op::DEFINE_ENTITY,
223
+ 136 => Op::AUDIT_LOG,
224
+ 137 => Op::RESTRICT_ACCESS,
225
+ 140 => Op::SETUP_TRY,
226
+ 141 => Op::POP_TRY,
227
+ 142 => Op::THROW,
228
+ 150 => Op::FOR_ITER,
229
+ 160 => Op::ENABLE_ERROR_MODE,
230
+ 250 => Op::PRINT,
231
+ 255 => Op::NOP,
232
+ 300 => Op::PARALLEL_START,
233
+ 301 => Op::PARALLEL_END,
234
+ _ => Op::UNKNOWN,
235
+ }
236
+ }
237
+
238
+ /// Base gas cost for this opcode (matches Python GasCost IntEnum).
239
+ fn gas_cost(self) -> u64 {
240
+ match self {
241
+ Op::NOP => 0,
242
+ Op::LOAD_CONST => 1,
243
+ Op::LOAD_NAME => 2,
244
+ Op::STORE_NAME | Op::STORE_FUNC => 3,
245
+ Op::POP | Op::DUP => 1,
246
+ Op::ADD | Op::SUB => 3,
247
+ Op::MUL => 5,
248
+ Op::DIV | Op::MOD => 10,
249
+ Op::POW => 20,
250
+ Op::NEG => 2,
251
+ Op::EQ | Op::NEQ | Op::LT | Op::GT | Op::LTE | Op::GTE => 2,
252
+ Op::NOT | Op::AND | Op::OR => 2,
253
+ Op::JUMP => 2,
254
+ Op::JUMP_IF_FALSE | Op::JUMP_IF_TRUE => 3,
255
+ Op::RETURN => 2,
256
+ Op::BUILD_LIST | Op::BUILD_MAP | Op::BUILD_SET => 5, // base; dynamic added in consume_gas_dynamic
257
+ Op::INDEX | Op::GET_ATTR => 3,
258
+ Op::SLICE => 5,
259
+ Op::CALL_NAME | Op::CALL_TOP | Op::CALL_METHOD | Op::CALL_FUNC_CONST => 10, // base; + 2*argc
260
+ Op::CALL_BUILTIN => 8, // base; + 2*argc
261
+ Op::SPAWN | Op::SPAWN_CALL => 15,
262
+ Op::AWAIT => 10,
263
+ Op::HASH_BLOCK => 50,
264
+ Op::VERIFY_SIGNATURE => 100,
265
+ Op::MERKLE_ROOT => 30, // base; + 5*leaves
266
+ Op::STATE_READ => 20,
267
+ Op::STATE_WRITE => 30, // Reduced: RustStateAdapter caching makes writes memory-local
268
+ Op::TX_BEGIN => 20,
269
+ Op::TX_COMMIT => 30,
270
+ Op::TX_REVERT => 20,
271
+ Op::GAS_CHARGE => 2,
272
+ Op::LEDGER_APPEND => 40,
273
+ Op::REQUIRE => 5,
274
+ Op::AUDIT_LOG => 15,
275
+ Op::RESTRICT_ACCESS => 5,
276
+ Op::DEFINE_CONTRACT | Op::DEFINE_ENTITY => 50,
277
+ Op::EMIT_EVENT | Op::REGISTER_EVENT => 10,
278
+ Op::SETUP_TRY => 3,
279
+ Op::POP_TRY => 2,
280
+ Op::THROW => 5,
281
+ Op::ENABLE_ERROR_MODE => 2,
282
+ Op::PRINT => 10,
283
+ Op::IMPORT | Op::EXPORT => 10,
284
+ Op::PARALLEL_START => 15,
285
+ Op::PARALLEL_END => 10,
286
+ Op::FOR_ITER => 3,
287
+ Op::UNKNOWN => 5,
288
+ }
289
+ }
290
+ }
291
+
292
+ // ── VM Value type ─────────────────────────────────────────────────────
293
+
294
+ /// Runtime value for the Rust VM stack machine.
295
+ /// Covers all Zexus primitive types plus Python interop.
296
+ #[derive(Debug)]
297
+ pub enum ZxValue {
298
+ Null,
299
+ Bool(bool),
300
+ Int(i64),
301
+ Float(f64),
302
+ Str(String),
303
+ List(Vec<ZxValue>),
304
+ Map(Vec<(String, ZxValue)>),
305
+ /// Python object that cannot be represented natively.
306
+ /// Requires GIL for access — used for callables, AST nodes, etc.
307
+ PyObj(PyObject),
308
+ }
309
+
310
+ impl Clone for ZxValue {
311
+ fn clone(&self) -> Self {
312
+ match self {
313
+ ZxValue::Null => ZxValue::Null,
314
+ ZxValue::Bool(b) => ZxValue::Bool(*b),
315
+ ZxValue::Int(i) => ZxValue::Int(*i),
316
+ ZxValue::Float(f) => ZxValue::Float(*f),
317
+ ZxValue::Str(s) => ZxValue::Str(s.clone()),
318
+ ZxValue::List(v) => ZxValue::List(v.clone()),
319
+ ZxValue::Map(v) => ZxValue::Map(v.clone()),
320
+ ZxValue::PyObj(obj) => {
321
+ Python::with_gil(|py| ZxValue::PyObj(obj.clone_ref(py)))
322
+ }
323
+ }
324
+ }
325
+ }
326
+
327
+ impl ZxValue {
328
+ /// Truthiness (matches Python semantics).
329
+ pub fn is_truthy(&self) -> bool {
330
+ match self {
331
+ ZxValue::Null => false,
332
+ ZxValue::Bool(b) => *b,
333
+ ZxValue::Int(i) => *i != 0,
334
+ ZxValue::Float(f) => *f != 0.0,
335
+ ZxValue::Str(s) => !s.is_empty(),
336
+ ZxValue::List(v) => !v.is_empty(),
337
+ ZxValue::Map(v) => !v.is_empty(),
338
+ ZxValue::PyObj(_) => true,
339
+ }
340
+ }
341
+
342
+ /// Convert to i64, defaulting to 0 for non-numeric types.
343
+ pub fn as_int(&self) -> i64 {
344
+ match self {
345
+ ZxValue::Int(i) => *i,
346
+ ZxValue::Float(f) => *f as i64,
347
+ ZxValue::Bool(b) => if *b { 1 } else { 0 },
348
+ _ => 0,
349
+ }
350
+ }
351
+
352
+ /// Convert to f64, defaulting to 0.0 for non-numeric types.
353
+ pub fn as_float(&self) -> f64 {
354
+ match self {
355
+ ZxValue::Float(f) => *f,
356
+ ZxValue::Int(i) => *i as f64,
357
+ ZxValue::Bool(b) => if *b { 1.0 } else { 0.0 },
358
+ _ => 0.0,
359
+ }
360
+ }
361
+
362
+ /// String representation for PRINT and error messages.
363
+ pub fn display_str(&self) -> String {
364
+ match self {
365
+ ZxValue::Null => "null".to_string(),
366
+ ZxValue::Bool(b) => b.to_string(),
367
+ ZxValue::Int(i) => i.to_string(),
368
+ ZxValue::Float(f) => format!("{}", f),
369
+ ZxValue::Str(s) => s.clone(),
370
+ ZxValue::List(items) => {
371
+ let inner: Vec<String> = items.iter().map(|v| v.display_str()).collect();
372
+ format!("[{}]", inner.join(", "))
373
+ }
374
+ ZxValue::Map(pairs) => {
375
+ let inner: Vec<String> = pairs
376
+ .iter()
377
+ .map(|(k, v)| format!("{}: {}", k, v.display_str()))
378
+ .collect();
379
+ format!("{{{}}}", inner.join(", "))
380
+ }
381
+ ZxValue::PyObj(_) => "<python object>".to_string(),
382
+ }
383
+ }
384
+
385
+ /// Check if this is a numeric type (Int or Float).
386
+ fn is_numeric(&self) -> bool {
387
+ matches!(self, ZxValue::Int(_) | ZxValue::Float(_))
388
+ }
389
+
390
+ /// Check if either operand is float (for promotion).
391
+ fn needs_float_promotion(a: &ZxValue, b: &ZxValue) -> bool {
392
+ matches!(a, ZxValue::Float(_)) || matches!(b, ZxValue::Float(_))
393
+ }
394
+ }
395
+
396
+ impl PartialEq for ZxValue {
397
+ fn eq(&self, other: &Self) -> bool {
398
+ match (self, other) {
399
+ (ZxValue::Null, ZxValue::Null) => true,
400
+ (ZxValue::Bool(a), ZxValue::Bool(b)) => a == b,
401
+ (ZxValue::Int(a), ZxValue::Int(b)) => a == b,
402
+ (ZxValue::Float(a), ZxValue::Float(b)) => a == b,
403
+ (ZxValue::Int(a), ZxValue::Float(b)) => (*a as f64) == *b,
404
+ (ZxValue::Float(a), ZxValue::Int(b)) => *a == (*b as f64),
405
+ (ZxValue::Str(a), ZxValue::Str(b)) => a == b,
406
+ (ZxValue::Bool(a), ZxValue::Int(b)) => (if *a { 1i64 } else { 0 }) == *b,
407
+ (ZxValue::Int(a), ZxValue::Bool(b)) => *a == (if *b { 1i64 } else { 0 }),
408
+ _ => false,
409
+ }
410
+ }
411
+ }
412
+
413
+ impl PartialOrd for ZxValue {
414
+ fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
415
+ match (self, other) {
416
+ (ZxValue::Int(a), ZxValue::Int(b)) => a.partial_cmp(b),
417
+ (ZxValue::Float(a), ZxValue::Float(b)) => a.partial_cmp(b),
418
+ (ZxValue::Int(a), ZxValue::Float(b)) => (*a as f64).partial_cmp(b),
419
+ (ZxValue::Float(a), ZxValue::Int(b)) => a.partial_cmp(&(*b as f64)),
420
+ (ZxValue::Str(a), ZxValue::Str(b)) => a.partial_cmp(b),
421
+ (ZxValue::Bool(a), ZxValue::Bool(b)) => a.partial_cmp(b),
422
+ _ => None,
423
+ }
424
+ }
425
+ }
426
+
427
+ // ── ZxcValue → ZxValue conversion ────────────────────────────────────
428
+
429
+ fn zxc_to_zx(v: &ZxcValue) -> ZxValue {
430
+ match v {
431
+ ZxcValue::Null => ZxValue::Null,
432
+ ZxcValue::Bool(b) => ZxValue::Bool(*b),
433
+ ZxcValue::Int(i) => ZxValue::Int(*i),
434
+ ZxcValue::Float(f) => ZxValue::Float(*f),
435
+ ZxcValue::String(s) => ZxValue::Str(s.clone()),
436
+ ZxcValue::FuncDesc(s) => ZxValue::Str(s.clone()), // treated as string for now
437
+ ZxcValue::List(items) => ZxValue::List(items.iter().map(zxc_to_zx).collect()),
438
+ ZxcValue::Map(pairs) => {
439
+ let converted: Vec<(String, ZxValue)> = pairs
440
+ .iter()
441
+ .map(|(k, v)| {
442
+ let key = match k {
443
+ ZxcValue::String(s) => s.clone(),
444
+ other => format!("{}", other),
445
+ };
446
+ (key, zxc_to_zx(v))
447
+ })
448
+ .collect();
449
+ ZxValue::Map(converted)
450
+ }
451
+ ZxcValue::Opaque(_) => ZxValue::Null, // Opaque data not usable in Rust
452
+ }
453
+ }
454
+
455
+ // ── ZxValue → PyObject conversion ────────────────────────────────────
456
+
457
+ pub fn zx_to_py(py: Python<'_>, val: &ZxValue) -> PyObject {
458
+ match val {
459
+ ZxValue::Null => py.None(),
460
+ ZxValue::Bool(b) => b.to_object(py),
461
+ ZxValue::Int(i) => i.to_object(py),
462
+ ZxValue::Float(f) => f.to_object(py),
463
+ ZxValue::Str(s) => s.to_object(py),
464
+ ZxValue::List(items) => {
465
+ let py_list = PyList::empty_bound(py);
466
+ for item in items {
467
+ let _ = py_list.append(zx_to_py(py, item));
468
+ }
469
+ py_list.to_object(py)
470
+ }
471
+ ZxValue::Map(pairs) => {
472
+ let py_dict = PyDict::new_bound(py);
473
+ for (k, v) in pairs {
474
+ let _ = py_dict.set_item(k, zx_to_py(py, v));
475
+ }
476
+ py_dict.to_object(py)
477
+ }
478
+ ZxValue::PyObj(obj) => obj.clone_ref(py),
479
+ }
480
+ }
481
+
482
+ /// Convert a PyObject to ZxValue.
483
+ pub fn py_to_zx(py: Python<'_>, obj: &PyObject) -> ZxValue {
484
+ let bound = obj.bind(py);
485
+
486
+ if bound.is_none() {
487
+ return ZxValue::Null;
488
+ }
489
+ if let Ok(b) = bound.downcast::<PyBool>() {
490
+ return ZxValue::Bool(b.is_true());
491
+ }
492
+ if let Ok(i) = bound.extract::<i64>() {
493
+ return ZxValue::Int(i);
494
+ }
495
+ if let Ok(f) = bound.extract::<f64>() {
496
+ return ZxValue::Float(f);
497
+ }
498
+ if let Ok(s) = bound.extract::<String>() {
499
+ return ZxValue::Str(s);
500
+ }
501
+ if let Ok(list) = bound.downcast::<PyList>() {
502
+ let items: Vec<ZxValue> = list
503
+ .iter()
504
+ .map(|item| py_to_zx(py, &item.to_object(py)))
505
+ .collect();
506
+ return ZxValue::List(items);
507
+ }
508
+ if let Ok(dict) = bound.downcast::<PyDict>() {
509
+ let pairs: Vec<(String, ZxValue)> = dict
510
+ .iter()
511
+ .map(|(k, v)| {
512
+ let key = k.extract::<String>().unwrap_or_else(|_| format!("{}", k));
513
+ let val = py_to_zx(py, &v.to_object(py));
514
+ (key, val)
515
+ })
516
+ .collect();
517
+ return ZxValue::Map(pairs);
518
+ }
519
+
520
+ // Fallback: keep as PyObject
521
+ ZxValue::PyObj(obj.clone_ref(py))
522
+ }
523
+
524
+ // ── VM Errors ─────────────────────────────────────────────────────────
525
+
526
+ #[derive(Debug, Clone)]
527
+ pub enum VmError {
528
+ OutOfGas { used: u64, limit: u64, opcode: String },
529
+ RequireFailed(String),
530
+ RuntimeError(String),
531
+ UnsupportedOpcode(u16),
532
+ /// Opcode requires Python fallback (e.g. CALL_NAME, CALL_METHOD).
533
+ NeedsPythonFallback,
534
+ }
535
+
536
+ impl fmt::Display for VmError {
537
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
538
+ match self {
539
+ VmError::OutOfGas { used, limit, opcode } => {
540
+ write!(f, "Out of gas: used={}, limit={}, opcode={}", used, limit, opcode)
541
+ }
542
+ VmError::RequireFailed(msg) => write!(f, "Requirement failed: {}", msg),
543
+ VmError::RuntimeError(msg) => write!(f, "Runtime error: {}", msg),
544
+ VmError::UnsupportedOpcode(op) => write!(f, "Unsupported opcode: {}", op),
545
+ VmError::NeedsPythonFallback => write!(f, "Opcode requires Python fallback"),
546
+ }
547
+ }
548
+ }
549
+
550
+ // ── Transaction snapshot ──────────────────────────────────────────────
551
+
552
+ #[derive(Debug, Clone)]
553
+ struct TxSnapshot {
554
+ state: HashMap<String, ZxValue>,
555
+ pending: HashMap<String, ZxValue>,
556
+ }
557
+
558
+ // ── The Rust VM ───────────────────────────────────────────────────────
559
+
560
+ /// Stack-based bytecode interpreter for Zexus.
561
+ pub struct RustVM {
562
+ // Program
563
+ constants: Vec<ZxValue>,
564
+ instructions: Vec<(Op, Operand)>,
565
+
566
+ // Execution state
567
+ stack: Vec<ZxValue>,
568
+ ip: usize,
569
+
570
+ // Environment
571
+ env: HashMap<String, ZxValue>,
572
+ blockchain_state: HashMap<String, ZxValue>,
573
+
574
+ // Gas metering
575
+ gas_limit: u64,
576
+ gas_used: u64,
577
+ gas_enabled: bool,
578
+ /// Discount factor for Rust execution (0.0–1.0).
579
+ /// Default 0.6 → Rust charges 60% of base gas (40% discount).
580
+ gas_discount: f64,
581
+
582
+ // Transaction state
583
+ in_transaction: bool,
584
+ tx_snapshot: Option<HashMap<String, ZxValue>>,
585
+ tx_pending: HashMap<String, ZxValue>,
586
+ tx_stack: Vec<TxSnapshot>,
587
+
588
+ // Exception handling
589
+ try_stack: Vec<usize>,
590
+
591
+ // Ledger
592
+ ledger: Vec<ZxValue>,
593
+
594
+ // Audit log
595
+ audit_log: Vec<(String, ZxValue)>,
596
+
597
+ // Contract events (Phase 6) — collected by emit() builtin and EMIT_EVENT opcode
598
+ events: Vec<(String, ZxValue)>,
599
+
600
+ // Output capture (for PRINT)
601
+ output: Vec<String>,
602
+
603
+ // Stats
604
+ instructions_executed: u64,
605
+ }
606
+
607
+ impl RustVM {
608
+ /// Create a new VM from a deserialized ZxcModule.
609
+ pub fn from_module(module: &ZxcModule) -> Self {
610
+ let constants: Vec<ZxValue> = module.constants.iter().map(zxc_to_zx).collect();
611
+ let instructions: Vec<(Op, Operand)> = module
612
+ .instructions
613
+ .iter()
614
+ .map(|i| (Op::from_u16(i.opcode), i.operand.clone()))
615
+ .collect();
616
+
617
+ RustVM {
618
+ constants,
619
+ instructions,
620
+ stack: Vec::with_capacity(256),
621
+ ip: 0,
622
+ env: HashMap::new(),
623
+ blockchain_state: HashMap::new(),
624
+ gas_limit: 100_000_000,
625
+ gas_used: 0,
626
+ gas_enabled: false,
627
+ gas_discount: 0.6, // 40% cheaper than Python VM
628
+ in_transaction: false,
629
+ tx_snapshot: None,
630
+ tx_pending: HashMap::new(),
631
+ tx_stack: Vec::new(),
632
+ try_stack: Vec::new(),
633
+ ledger: Vec::new(),
634
+ audit_log: Vec::new(),
635
+ events: Vec::new(),
636
+ output: Vec::new(),
637
+ instructions_executed: 0,
638
+ }
639
+ }
640
+
641
+ /// Set gas limit and enable gas metering.
642
+ pub fn set_gas_limit(&mut self, limit: u64) {
643
+ self.gas_limit = limit;
644
+ self.gas_enabled = true;
645
+ }
646
+
647
+ /// Set initial environment variables.
648
+ pub fn set_env(&mut self, env: HashMap<String, ZxValue>) {
649
+ self.env = env;
650
+ }
651
+
652
+ /// Set initial blockchain state.
653
+ pub fn set_blockchain_state(&mut self, state: HashMap<String, ZxValue>) {
654
+ self.blockchain_state = state;
655
+ }
656
+
657
+ // ── Helpers ───────────────────────────────────────────────────────
658
+
659
+ #[inline(always)]
660
+ fn pop(&mut self) -> ZxValue {
661
+ self.stack.pop().unwrap_or(ZxValue::Null)
662
+ }
663
+
664
+ #[inline(always)]
665
+ fn push(&mut self, v: ZxValue) {
666
+ self.stack.push(v);
667
+ }
668
+
669
+ #[inline(always)]
670
+ fn peek(&self) -> &ZxValue {
671
+ self.stack.last().unwrap_or(&ZxValue::Null)
672
+ }
673
+
674
+ #[inline(always)]
675
+ fn const_val(&self, idx: u32) -> ZxValue {
676
+ self.constants
677
+ .get(idx as usize)
678
+ .cloned()
679
+ .unwrap_or(ZxValue::Null)
680
+ }
681
+
682
+ fn const_str(&self, idx: u32) -> String {
683
+ match self.constants.get(idx as usize) {
684
+ Some(ZxValue::Str(s)) => s.clone(),
685
+ Some(other) => other.display_str(),
686
+ None => String::new(),
687
+ }
688
+ }
689
+
690
+ #[inline(always)]
691
+ fn consume_gas(&mut self, op: Op) -> Result<(), VmError> {
692
+ if !self.gas_enabled {
693
+ return Ok(());
694
+ }
695
+ let base_cost = op.gas_cost();
696
+ let cost = ((base_cost as f64) * self.gas_discount).ceil() as u64;
697
+ self.gas_used += cost;
698
+ if self.gas_used > self.gas_limit {
699
+ return Err(VmError::OutOfGas {
700
+ used: self.gas_used,
701
+ limit: self.gas_limit,
702
+ opcode: format!("{:?}", op),
703
+ });
704
+ }
705
+ Ok(())
706
+ }
707
+
708
+ /// Consume gas with dynamic per-element scaling (for BUILD_LIST, CALL_*, etc.)
709
+ #[inline(always)]
710
+ fn consume_gas_dynamic(&mut self, op: Op, extra: u64) -> Result<(), VmError> {
711
+ if !self.gas_enabled {
712
+ return Ok(());
713
+ }
714
+ let base_cost = op.gas_cost();
715
+ let total = base_cost + extra;
716
+ let cost = ((total as f64) * self.gas_discount).ceil() as u64;
717
+ self.gas_used += cost;
718
+ if self.gas_used > self.gas_limit {
719
+ return Err(VmError::OutOfGas {
720
+ used: self.gas_used,
721
+ limit: self.gas_limit,
722
+ opcode: format!("{:?}", op),
723
+ });
724
+ }
725
+ Ok(())
726
+ }
727
+
728
+ /// Set gas discount factor (0.0–1.0, default 0.6).
729
+ pub fn set_gas_discount(&mut self, discount: f64) {
730
+ self.gas_discount = discount.clamp(0.01, 1.0);
731
+ }
732
+
733
+ fn revert_on_failure(&mut self) {
734
+ if self.in_transaction {
735
+ if let Some(snapshot) = self.tx_snapshot.take() {
736
+ self.blockchain_state = snapshot;
737
+ }
738
+ self.in_transaction = false;
739
+ self.tx_pending.clear();
740
+ }
741
+ }
742
+
743
+ // ── Arithmetic helpers (with type promotion) ─────────────────────
744
+
745
+ fn arith_add(a: ZxValue, b: ZxValue) -> ZxValue {
746
+ match (&a, &b) {
747
+ (ZxValue::Int(x), ZxValue::Int(y)) => ZxValue::Int(x.wrapping_add(*y)),
748
+ (ZxValue::Float(x), ZxValue::Float(y)) => ZxValue::Float(x + y),
749
+ (ZxValue::Int(x), ZxValue::Float(y)) => ZxValue::Float(*x as f64 + y),
750
+ (ZxValue::Float(x), ZxValue::Int(y)) => ZxValue::Float(x + *y as f64),
751
+ (ZxValue::Str(x), ZxValue::Str(y)) => {
752
+ let mut s = x.clone();
753
+ s.push_str(y);
754
+ ZxValue::Str(s)
755
+ }
756
+ (ZxValue::Str(x), _) => {
757
+ let mut s = x.clone();
758
+ s.push_str(&b.display_str());
759
+ ZxValue::Str(s)
760
+ }
761
+ (_, ZxValue::Str(y)) => {
762
+ let mut s = a.display_str();
763
+ s.push_str(y);
764
+ ZxValue::Str(s)
765
+ }
766
+ (ZxValue::List(x), ZxValue::List(y)) => {
767
+ let mut result = x.clone();
768
+ result.extend(y.iter().cloned());
769
+ ZxValue::List(result)
770
+ }
771
+ _ => ZxValue::Int(a.as_int().wrapping_add(b.as_int())),
772
+ }
773
+ }
774
+
775
+ fn arith_sub(a: ZxValue, b: ZxValue) -> ZxValue {
776
+ if ZxValue::needs_float_promotion(&a, &b) {
777
+ ZxValue::Float(a.as_float() - b.as_float())
778
+ } else {
779
+ ZxValue::Int(a.as_int().wrapping_sub(b.as_int()))
780
+ }
781
+ }
782
+
783
+ fn arith_mul(a: ZxValue, b: ZxValue) -> ZxValue {
784
+ match (&a, &b) {
785
+ (ZxValue::Str(s), ZxValue::Int(n)) | (ZxValue::Int(n), ZxValue::Str(s)) => {
786
+ ZxValue::Str(s.repeat((*n).max(0) as usize))
787
+ }
788
+ _ if ZxValue::needs_float_promotion(&a, &b) => {
789
+ ZxValue::Float(a.as_float() * b.as_float())
790
+ }
791
+ _ => ZxValue::Int(a.as_int().wrapping_mul(b.as_int())),
792
+ }
793
+ }
794
+
795
+ fn arith_div(a: ZxValue, b: ZxValue) -> ZxValue {
796
+ let bv = b.as_float();
797
+ if bv == 0.0 {
798
+ return ZxValue::Int(0);
799
+ }
800
+ if ZxValue::needs_float_promotion(&a, &b) {
801
+ ZxValue::Float(a.as_float() / bv)
802
+ } else {
803
+ let ai = a.as_int();
804
+ let bi = b.as_int();
805
+ if bi == 0 {
806
+ ZxValue::Int(0)
807
+ } else {
808
+ ZxValue::Int(ai / bi)
809
+ }
810
+ }
811
+ }
812
+
813
+ fn arith_mod(a: ZxValue, b: ZxValue) -> ZxValue {
814
+ let bi = b.as_int();
815
+ if bi == 0 {
816
+ ZxValue::Int(0)
817
+ } else {
818
+ ZxValue::Int(a.as_int() % bi)
819
+ }
820
+ }
821
+
822
+ fn arith_pow(a: ZxValue, b: ZxValue) -> ZxValue {
823
+ if ZxValue::needs_float_promotion(&a, &b) {
824
+ ZxValue::Float(a.as_float().powf(b.as_float()))
825
+ } else {
826
+ let base = a.as_int();
827
+ let exp = b.as_int();
828
+ if exp < 0 {
829
+ ZxValue::Float((base as f64).powf(exp as f64))
830
+ } else {
831
+ ZxValue::Int(base.wrapping_pow(exp as u32))
832
+ }
833
+ }
834
+ }
835
+
836
+ // ── Main execution loop ──────────────────────────────────────────
837
+
838
+ /// Execute the loaded bytecode program.
839
+ /// Returns the final value (from RETURN or top of stack).
840
+ pub fn execute(&mut self) -> Result<ZxValue, VmError> {
841
+ let n_instrs = self.instructions.len();
842
+
843
+ while self.ip < n_instrs {
844
+ let (op, operand) = self.instructions[self.ip].clone();
845
+ self.ip += 1;
846
+ self.instructions_executed += 1;
847
+
848
+ // Gas check
849
+ self.consume_gas(op)?;
850
+
851
+ match op {
852
+ // ── Stack operations ─────────────────────────────
853
+ Op::LOAD_CONST => {
854
+ if let Operand::U32(idx) = &operand {
855
+ self.push(self.const_val(*idx));
856
+ }
857
+ }
858
+
859
+ Op::LOAD_NAME => {
860
+ let name = match &operand {
861
+ Operand::U32(idx) => self.const_str(*idx),
862
+ _ => String::new(),
863
+ };
864
+ let val = self
865
+ .env
866
+ .get(&name)
867
+ .cloned()
868
+ .unwrap_or(ZxValue::Null);
869
+ self.push(val);
870
+ }
871
+
872
+ Op::STORE_NAME => {
873
+ let name = match &operand {
874
+ Operand::U32(idx) => self.const_str(*idx),
875
+ _ => String::new(),
876
+ };
877
+ let val = self.pop();
878
+ self.env.insert(name, val);
879
+ }
880
+
881
+ Op::STORE_FUNC => {
882
+ let name = match &operand {
883
+ Operand::U32(idx) => self.const_str(*idx),
884
+ _ => String::new(),
885
+ };
886
+ let val = self.pop();
887
+ self.env.insert(name, val);
888
+ }
889
+
890
+ Op::POP => {
891
+ self.pop();
892
+ }
893
+
894
+ Op::DUP => {
895
+ let v = self.peek().clone();
896
+ self.push(v);
897
+ }
898
+
899
+ // ── Arithmetic ───────────────────────────────────
900
+ Op::ADD => {
901
+ let b = self.pop();
902
+ let a = self.pop();
903
+ self.push(Self::arith_add(a, b));
904
+ }
905
+
906
+ Op::SUB => {
907
+ let b = self.pop();
908
+ let a = self.pop();
909
+ self.push(Self::arith_sub(a, b));
910
+ }
911
+
912
+ Op::MUL => {
913
+ let b = self.pop();
914
+ let a = self.pop();
915
+ self.push(Self::arith_mul(a, b));
916
+ }
917
+
918
+ Op::DIV => {
919
+ let b = self.pop();
920
+ let a = self.pop();
921
+ self.push(Self::arith_div(a, b));
922
+ }
923
+
924
+ Op::MOD => {
925
+ let b = self.pop();
926
+ let a = self.pop();
927
+ self.push(Self::arith_mod(a, b));
928
+ }
929
+
930
+ Op::POW => {
931
+ let b = self.pop();
932
+ let a = self.pop();
933
+ self.push(Self::arith_pow(a, b));
934
+ }
935
+
936
+ Op::NEG => {
937
+ let a = self.pop();
938
+ match a {
939
+ ZxValue::Int(i) => self.push(ZxValue::Int(-i)),
940
+ ZxValue::Float(f) => self.push(ZxValue::Float(-f)),
941
+ _ => self.push(ZxValue::Int(0)),
942
+ }
943
+ }
944
+
945
+ // ── Comparison ───────────────────────────────────
946
+ Op::EQ => {
947
+ let b = self.pop();
948
+ let a = self.pop();
949
+ self.push(ZxValue::Bool(a == b));
950
+ }
951
+
952
+ Op::NEQ => {
953
+ let b = self.pop();
954
+ let a = self.pop();
955
+ self.push(ZxValue::Bool(a != b));
956
+ }
957
+
958
+ Op::LT => {
959
+ let b = self.pop();
960
+ let a = self.pop();
961
+ self.push(ZxValue::Bool(a.partial_cmp(&b) == Some(std::cmp::Ordering::Less)));
962
+ }
963
+
964
+ Op::GT => {
965
+ let b = self.pop();
966
+ let a = self.pop();
967
+ self.push(ZxValue::Bool(a.partial_cmp(&b) == Some(std::cmp::Ordering::Greater)));
968
+ }
969
+
970
+ Op::LTE => {
971
+ let b = self.pop();
972
+ let a = self.pop();
973
+ let cmp = a.partial_cmp(&b);
974
+ self.push(ZxValue::Bool(
975
+ cmp == Some(std::cmp::Ordering::Less) || cmp == Some(std::cmp::Ordering::Equal),
976
+ ));
977
+ }
978
+
979
+ Op::GTE => {
980
+ let b = self.pop();
981
+ let a = self.pop();
982
+ let cmp = a.partial_cmp(&b);
983
+ self.push(ZxValue::Bool(
984
+ cmp == Some(std::cmp::Ordering::Greater) || cmp == Some(std::cmp::Ordering::Equal),
985
+ ));
986
+ }
987
+
988
+ // ── Logical ──────────────────────────────────────
989
+ Op::AND => {
990
+ let b = self.pop();
991
+ let a = self.pop();
992
+ self.push(ZxValue::Bool(a.is_truthy() && b.is_truthy()));
993
+ }
994
+
995
+ Op::OR => {
996
+ let b = self.pop();
997
+ let a = self.pop();
998
+ self.push(ZxValue::Bool(a.is_truthy() || b.is_truthy()));
999
+ }
1000
+
1001
+ Op::NOT => {
1002
+ let a = self.pop();
1003
+ self.push(ZxValue::Bool(!a.is_truthy()));
1004
+ }
1005
+
1006
+ // ── Control flow ─────────────────────────────────
1007
+ Op::JUMP => {
1008
+ if let Operand::U32(target) = &operand {
1009
+ self.ip = *target as usize;
1010
+ }
1011
+ }
1012
+
1013
+ Op::JUMP_IF_FALSE => {
1014
+ let cond = self.pop();
1015
+ if !cond.is_truthy() {
1016
+ if let Operand::U32(target) = &operand {
1017
+ self.ip = *target as usize;
1018
+ }
1019
+ }
1020
+ }
1021
+
1022
+ Op::JUMP_IF_TRUE => {
1023
+ let cond = self.pop();
1024
+ if cond.is_truthy() {
1025
+ if let Operand::U32(target) = &operand {
1026
+ self.ip = *target as usize;
1027
+ }
1028
+ }
1029
+ }
1030
+
1031
+ Op::RETURN => {
1032
+ return Ok(self.pop());
1033
+ }
1034
+
1035
+ // ── Collections ──────────────────────────────────
1036
+ Op::BUILD_LIST => {
1037
+ let count = match &operand {
1038
+ Operand::U32(n) => *n as usize,
1039
+ _ => 0,
1040
+ };
1041
+ // Dynamic scaling: +1 gas per element (matches Python)
1042
+ if count > 0 {
1043
+ self.consume_gas_dynamic(Op::NOP, count as u64)?;
1044
+ }
1045
+ let mut items = Vec::with_capacity(count);
1046
+ for _ in 0..count {
1047
+ items.push(self.pop());
1048
+ }
1049
+ items.reverse();
1050
+ self.push(ZxValue::List(items));
1051
+ }
1052
+
1053
+ Op::BUILD_MAP => {
1054
+ let count = match &operand {
1055
+ Operand::U32(n) => *n as usize,
1056
+ _ => 0,
1057
+ };
1058
+ // Dynamic scaling: +2 gas per pair (matches Python)
1059
+ if count > 0 {
1060
+ self.consume_gas_dynamic(Op::NOP, count as u64 * 2)?;
1061
+ }
1062
+ let mut pairs = Vec::with_capacity(count);
1063
+ for _ in 0..count {
1064
+ let val = self.pop();
1065
+ let key = self.pop();
1066
+ let key_str = match &key {
1067
+ ZxValue::Str(s) => s.clone(),
1068
+ other => other.display_str(),
1069
+ };
1070
+ pairs.push((key_str, val));
1071
+ }
1072
+ // Reverse to maintain insertion order
1073
+ pairs.reverse();
1074
+ self.push(ZxValue::Map(pairs));
1075
+ }
1076
+
1077
+ Op::BUILD_SET => {
1078
+ // Treat sets as lists (Zexus doesn't have a native set type in VM)
1079
+ let count = match &operand {
1080
+ Operand::U32(n) => *n as usize,
1081
+ _ => 0,
1082
+ };
1083
+ // Dynamic scaling: +1 gas per element (matches Python BUILD_LIST)
1084
+ if count > 0 {
1085
+ self.consume_gas_dynamic(Op::NOP, count as u64)?;
1086
+ }
1087
+ let mut items = Vec::with_capacity(count);
1088
+ for _ in 0..count {
1089
+ items.push(self.pop());
1090
+ }
1091
+ items.reverse();
1092
+ self.push(ZxValue::List(items));
1093
+ }
1094
+
1095
+ Op::INDEX => {
1096
+ let idx = self.pop();
1097
+ let obj = self.pop();
1098
+ match (&obj, &idx) {
1099
+ (ZxValue::List(items), ZxValue::Int(i)) => {
1100
+ let i = *i as usize;
1101
+ self.push(items.get(i).cloned().unwrap_or(ZxValue::Null));
1102
+ }
1103
+ (ZxValue::Map(pairs), ZxValue::Str(key)) => {
1104
+ let found = pairs.iter().find(|(k, _)| k == key);
1105
+ self.push(found.map(|(_, v)| v.clone()).unwrap_or(ZxValue::Null));
1106
+ }
1107
+ (ZxValue::Str(s), ZxValue::Int(i)) => {
1108
+ let i = *i as usize;
1109
+ self.push(
1110
+ s.chars()
1111
+ .nth(i)
1112
+ .map(|c| ZxValue::Str(c.to_string()))
1113
+ .unwrap_or(ZxValue::Null),
1114
+ );
1115
+ }
1116
+ _ => self.push(ZxValue::Null),
1117
+ }
1118
+ }
1119
+
1120
+ Op::SLICE => {
1121
+ let end = self.pop();
1122
+ let start = self.pop();
1123
+ let obj = self.pop();
1124
+ match &obj {
1125
+ ZxValue::List(items) => {
1126
+ let s = start.as_int().max(0) as usize;
1127
+ let e = end.as_int().max(0) as usize;
1128
+ let e = e.min(items.len());
1129
+ let s = s.min(e);
1130
+ self.push(ZxValue::List(items[s..e].to_vec()));
1131
+ }
1132
+ ZxValue::Str(string) => {
1133
+ let s = start.as_int().max(0) as usize;
1134
+ let e = end.as_int().max(0) as usize;
1135
+ let chars: Vec<char> = string.chars().collect();
1136
+ let e = e.min(chars.len());
1137
+ let s = s.min(e);
1138
+ self.push(ZxValue::Str(chars[s..e].iter().collect()));
1139
+ }
1140
+ _ => self.push(ZxValue::Null),
1141
+ }
1142
+ }
1143
+
1144
+ Op::GET_ATTR => {
1145
+ let attr = self.pop();
1146
+ let obj = self.pop();
1147
+ let attr_name = match &attr {
1148
+ ZxValue::Str(s) => s.clone(),
1149
+ other => other.display_str(),
1150
+ };
1151
+ match &obj {
1152
+ ZxValue::Map(pairs) => {
1153
+ let found = pairs.iter().find(|(k, _)| k == &attr_name);
1154
+ self.push(found.map(|(_, v)| v.clone()).unwrap_or(ZxValue::Null));
1155
+ }
1156
+ _ => self.push(ZxValue::Null),
1157
+ }
1158
+ }
1159
+
1160
+ // ── Function calls ───────────────────────────────
1161
+ // CALL_BUILTIN dispatches to Rust-native builtins (Phase 6).
1162
+ // CALL_NAME also checks builtins before falling back.
1163
+ // Other call types still require Python interop.
1164
+
1165
+ Op::CALL_BUILTIN => {
1166
+ let (name_idx, argc) = match &operand {
1167
+ Operand::Pair(a, b) => (*a, *b as usize),
1168
+ Operand::U32(idx) => (*idx, 0usize),
1169
+ _ => {
1170
+ return Err(VmError::RuntimeError(
1171
+ "Invalid CALL_BUILTIN operand".into(),
1172
+ ));
1173
+ }
1174
+ };
1175
+ let name = self.const_str(name_idx);
1176
+ // Dynamic gas: base 8 + 2*argc
1177
+ if argc > 0 {
1178
+ self.consume_gas_dynamic(Op::NOP, argc as u64 * 2)?;
1179
+ }
1180
+ let mut args = Vec::with_capacity(argc);
1181
+ for _ in 0..argc {
1182
+ args.push(self.pop());
1183
+ }
1184
+ args.reverse();
1185
+ let result = self.dispatch_builtin(&name, &args)?;
1186
+ self.push(result);
1187
+ }
1188
+
1189
+ Op::CALL_NAME => {
1190
+ let (name_idx, argc) = match &operand {
1191
+ Operand::Pair(a, b) => (*a, *b as usize),
1192
+ Operand::U32(idx) => (*idx, 0usize),
1193
+ _ => {
1194
+ return Err(VmError::RuntimeError(
1195
+ "Invalid CALL_NAME operand".into(),
1196
+ ));
1197
+ }
1198
+ };
1199
+ let name = self.const_str(name_idx);
1200
+ // Dynamic gas: base 10 + 2*argc
1201
+ if argc > 0 {
1202
+ self.consume_gas_dynamic(Op::NOP, argc as u64 * 2)?;
1203
+ }
1204
+ let mut args = Vec::with_capacity(argc);
1205
+ for _ in 0..argc {
1206
+ args.push(self.pop());
1207
+ }
1208
+ args.reverse();
1209
+
1210
+ // Check if it's a known builtin first
1211
+ if Self::is_known_builtin(&name) {
1212
+ let result = self.dispatch_builtin(&name, &args)?;
1213
+ self.push(result);
1214
+ } else {
1215
+ // Check env for user-defined functions
1216
+ // For now, fall back to Python for non-builtin calls
1217
+ return Err(VmError::NeedsPythonFallback);
1218
+ }
1219
+ }
1220
+
1221
+ Op::CALL_TOP | Op::CALL_METHOD | Op::CALL_FUNC_CONST => {
1222
+ // These require Python interop for general callables
1223
+ return Err(VmError::NeedsPythonFallback);
1224
+ }
1225
+
1226
+ // ── I/O ──────────────────────────────────────────
1227
+ Op::PRINT => {
1228
+ let val = self.pop();
1229
+ self.output.push(val.display_str());
1230
+ }
1231
+
1232
+ // ── Blockchain opcodes ───────────────────────────
1233
+ Op::STATE_READ => {
1234
+ let key = match &operand {
1235
+ Operand::U32(idx) => self.const_str(*idx),
1236
+ _ => {
1237
+ let k = self.pop();
1238
+ match k {
1239
+ ZxValue::Str(s) => s,
1240
+ other => other.display_str(),
1241
+ }
1242
+ }
1243
+ };
1244
+ let val = self
1245
+ .blockchain_state
1246
+ .get(&key)
1247
+ .cloned()
1248
+ .unwrap_or(ZxValue::Null);
1249
+ self.push(val);
1250
+ }
1251
+
1252
+ Op::STATE_WRITE => {
1253
+ let val = self.pop();
1254
+ let key = match &operand {
1255
+ Operand::U32(idx) => self.const_str(*idx),
1256
+ _ => {
1257
+ let k = self.pop();
1258
+ match k {
1259
+ ZxValue::Str(s) => s,
1260
+ other => other.display_str(),
1261
+ }
1262
+ }
1263
+ };
1264
+ if self.in_transaction {
1265
+ self.tx_pending.insert(key, val);
1266
+ } else {
1267
+ self.blockchain_state.insert(key, val);
1268
+ }
1269
+ }
1270
+
1271
+ Op::TX_BEGIN => {
1272
+ self.tx_stack.push(TxSnapshot {
1273
+ state: self.blockchain_state.clone(),
1274
+ pending: self.tx_pending.clone(),
1275
+ });
1276
+ self.in_transaction = true;
1277
+ self.tx_snapshot = Some(self.blockchain_state.clone());
1278
+ self.tx_pending.clear();
1279
+ }
1280
+
1281
+ Op::TX_COMMIT => {
1282
+ if self.in_transaction {
1283
+ // Merge pending writes into blockchain state
1284
+ for (k, v) in self.tx_pending.drain() {
1285
+ self.blockchain_state.insert(k, v);
1286
+ }
1287
+ self.tx_snapshot = None;
1288
+ // Restore outer transaction's pending writes
1289
+ if let Some(outer) = self.tx_stack.pop() {
1290
+ self.tx_pending = outer.pending;
1291
+ }
1292
+ self.in_transaction = !self.tx_stack.is_empty();
1293
+ }
1294
+ }
1295
+
1296
+ Op::TX_REVERT => {
1297
+ if self.in_transaction {
1298
+ if let Some(snapshot) = self.tx_snapshot.take() {
1299
+ self.blockchain_state = snapshot;
1300
+ }
1301
+ self.tx_pending.clear();
1302
+ if let Some(outer) = self.tx_stack.pop() {
1303
+ if !self.tx_stack.is_empty() {
1304
+ self.tx_snapshot = Some(outer.state);
1305
+ self.tx_pending = outer.pending;
1306
+ }
1307
+ }
1308
+ self.in_transaction = !self.tx_stack.is_empty();
1309
+ }
1310
+ }
1311
+
1312
+ Op::GAS_CHARGE => {
1313
+ let amount = match &operand {
1314
+ Operand::U32(n) => *n as u64,
1315
+ _ => 0,
1316
+ };
1317
+ if self.gas_enabled {
1318
+ self.gas_used += amount;
1319
+ if self.gas_used > self.gas_limit {
1320
+ self.revert_on_failure();
1321
+ return Err(VmError::OutOfGas {
1322
+ used: self.gas_used,
1323
+ limit: self.gas_limit,
1324
+ opcode: "GAS_CHARGE".to_string(),
1325
+ });
1326
+ }
1327
+ }
1328
+ }
1329
+
1330
+ Op::REQUIRE => {
1331
+ let message = self.pop();
1332
+ let condition = self.pop();
1333
+ if !condition.is_truthy() {
1334
+ self.revert_on_failure();
1335
+ return Err(VmError::RequireFailed(message.display_str()));
1336
+ }
1337
+ }
1338
+
1339
+ Op::LEDGER_APPEND => {
1340
+ let entry = self.pop();
1341
+ if self.ledger.len() < 10000 {
1342
+ self.ledger.push(entry);
1343
+ }
1344
+ }
1345
+
1346
+ Op::HASH_BLOCK => {
1347
+ let data = self.pop();
1348
+ let data_str = data.display_str();
1349
+ let data_bytes = data_str.as_bytes();
1350
+ use sha2::{Digest, Sha256};
1351
+ let hash = Sha256::digest(data_bytes);
1352
+ self.push(ZxValue::Str(hex::encode(hash)));
1353
+ }
1354
+
1355
+ Op::VERIFY_SIGNATURE => {
1356
+ // Full ECDSA-secp256k1 verification via RustSignature
1357
+ if self.stack.len() >= 3 {
1358
+ let pk = self.pop();
1359
+ let msg = self.pop();
1360
+ let sig = self.pop();
1361
+ let result = Self::builtin_verify_sig(&sig, &msg, &pk);
1362
+ self.push(ZxValue::Bool(result));
1363
+ } else {
1364
+ self.push(ZxValue::Bool(false));
1365
+ }
1366
+ }
1367
+
1368
+ Op::MERKLE_ROOT => {
1369
+ let leaf_count = match &operand {
1370
+ Operand::U32(n) => *n as usize,
1371
+ _ => 0,
1372
+ };
1373
+ if leaf_count == 0 || self.stack.len() < leaf_count {
1374
+ self.push(ZxValue::Str(String::new()));
1375
+ } else {
1376
+ use sha2::{Digest, Sha256};
1377
+ let mut leaves: Vec<ZxValue> = Vec::with_capacity(leaf_count);
1378
+ for _ in 0..leaf_count {
1379
+ leaves.push(self.pop());
1380
+ }
1381
+ leaves.reverse();
1382
+ let mut hashes: Vec<String> = leaves
1383
+ .iter()
1384
+ .map(|leaf| {
1385
+ let s = leaf.display_str();
1386
+ hex::encode(Sha256::digest(s.as_bytes()))
1387
+ })
1388
+ .collect();
1389
+ while hashes.len() > 1 {
1390
+ if hashes.len() % 2 != 0 {
1391
+ let last = hashes.last().unwrap().clone();
1392
+ hashes.push(last);
1393
+ }
1394
+ let mut new_hashes = Vec::new();
1395
+ for i in (0..hashes.len()).step_by(2) {
1396
+ let combined = format!("{}{}", hashes[i], hashes[i + 1]);
1397
+ new_hashes
1398
+ .push(hex::encode(Sha256::digest(combined.as_bytes())));
1399
+ }
1400
+ hashes = new_hashes;
1401
+ }
1402
+ self.push(ZxValue::Str(
1403
+ hashes.into_iter().next().unwrap_or_default(),
1404
+ ));
1405
+ }
1406
+ }
1407
+
1408
+ Op::EMIT_EVENT => {
1409
+ // Pop event data and name, store in events list
1410
+ let data = self.pop();
1411
+ let name_val = match &operand {
1412
+ Operand::U32(idx) => self.const_str(*idx),
1413
+ _ => {
1414
+ let n = self.pop();
1415
+ n.display_str()
1416
+ }
1417
+ };
1418
+ self.events.push((name_val, data));
1419
+ }
1420
+
1421
+ Op::REGISTER_EVENT => {
1422
+ // Event registration is a no-op at runtime — schema is compile-time
1423
+ let _data = self.pop();
1424
+ }
1425
+
1426
+ Op::AUDIT_LOG => {
1427
+ let data = self.pop();
1428
+ let action = self.pop();
1429
+ self.audit_log.push((action.display_str(), data));
1430
+ }
1431
+
1432
+ Op::RESTRICT_ACCESS => {
1433
+ let _restriction = self.pop();
1434
+ let _prop = self.pop();
1435
+ let _obj = self.pop();
1436
+ // Access control enforcement happens at contract level
1437
+ }
1438
+
1439
+ // ── Exception handling ───────────────────────────
1440
+ Op::SETUP_TRY => {
1441
+ let handler_ip = match &operand {
1442
+ Operand::U32(target) => *target as usize,
1443
+ _ => self.ip,
1444
+ };
1445
+ self.try_stack.push(handler_ip);
1446
+ }
1447
+
1448
+ Op::POP_TRY => {
1449
+ self.try_stack.pop();
1450
+ }
1451
+
1452
+ Op::THROW => {
1453
+ let exc = self.pop();
1454
+ if let Some(handler_ip) = self.try_stack.pop() {
1455
+ self.push(exc);
1456
+ self.ip = handler_ip;
1457
+ } else {
1458
+ return Err(VmError::RuntimeError(exc.display_str()));
1459
+ }
1460
+ }
1461
+
1462
+ Op::ENABLE_ERROR_MODE => {
1463
+ // No-op — error mode is a Python evaluator concept
1464
+ }
1465
+
1466
+ // ── Marker ops ───────────────────────────────────
1467
+ Op::PARALLEL_START | Op::PARALLEL_END | Op::NOP => {}
1468
+
1469
+ // ── Module / Contract ops (fallback to Python) ───
1470
+ Op::IMPORT | Op::EXPORT | Op::DEFINE_CONTRACT | Op::DEFINE_ENTITY
1471
+ | Op::SPAWN | Op::AWAIT | Op::SPAWN_CALL | Op::FOR_ITER => {
1472
+ return Err(VmError::NeedsPythonFallback);
1473
+ }
1474
+
1475
+ Op::UNKNOWN => {
1476
+ return Err(VmError::UnsupportedOpcode(
1477
+ self.instructions[self.ip - 1].0 as u16,
1478
+ ));
1479
+ }
1480
+ }
1481
+ }
1482
+
1483
+ // End of instructions — return top of stack or Null
1484
+ Ok(if self.stack.is_empty() {
1485
+ ZxValue::Null
1486
+ } else {
1487
+ self.pop()
1488
+ })
1489
+ }
1490
+
1491
+ // ── Accessors for Python bridge ──────────────────────────────────
1492
+
1493
+ /// Get the blockchain state after execution.
1494
+ pub fn get_blockchain_state(&self) -> &HashMap<String, ZxValue> {
1495
+ &self.blockchain_state
1496
+ }
1497
+
1498
+ /// Get the execution stats.
1499
+ pub fn get_stats(&self) -> (u64, u64, u64) {
1500
+ (self.instructions_executed, self.gas_used, self.gas_limit)
1501
+ }
1502
+
1503
+ /// Get captured output (from PRINT opcodes).
1504
+ pub fn get_output(&self) -> &[String] {
1505
+ &self.output
1506
+ }
1507
+
1508
+ /// Get the environment after execution.
1509
+ pub fn get_env(&self) -> &HashMap<String, ZxValue> {
1510
+ &self.env
1511
+ }
1512
+
1513
+ /// Set a single environment variable.
1514
+ pub fn env_set(&mut self, key: &str, val: ZxValue) {
1515
+ self.env.insert(key.to_string(), val);
1516
+ }
1517
+
1518
+ /// Get the ledger entries.
1519
+ pub fn get_ledger(&self) -> &[ZxValue] {
1520
+ &self.ledger
1521
+ }
1522
+
1523
+ /// Get collected events (from emit() builtin and EMIT_EVENT opcode).
1524
+ pub fn get_events(&self) -> &[(String, ZxValue)] {
1525
+ &self.events
1526
+ }
1527
+
1528
+ // ── Phase 6: Contract Builtins ──────────────────────────────────
1529
+
1530
+ /// List of builtin names handled in pure Rust.
1531
+ const KNOWN_BUILTINS: &'static [&'static str] = &[
1532
+ "keccak256",
1533
+ "verify_sig",
1534
+ "emit",
1535
+ "get_balance",
1536
+ "transfer",
1537
+ "block_number",
1538
+ "block_timestamp",
1539
+ "sha256",
1540
+ "print",
1541
+ "len",
1542
+ "str",
1543
+ "int",
1544
+ "float",
1545
+ "type",
1546
+ "abs",
1547
+ "min",
1548
+ "max",
1549
+ "to_string",
1550
+ "to_int",
1551
+ "to_float",
1552
+ "keys",
1553
+ "values",
1554
+ "push",
1555
+ "pop",
1556
+ "contains",
1557
+ "index_of",
1558
+ "slice",
1559
+ "reverse",
1560
+ "sort",
1561
+ "concat",
1562
+ "split",
1563
+ "join",
1564
+ "trim",
1565
+ "upper",
1566
+ "lower",
1567
+ "starts_with",
1568
+ "ends_with",
1569
+ "replace",
1570
+ "substring",
1571
+ "char_at",
1572
+ ];
1573
+
1574
+ /// Check if a name refers to a known Rust-native builtin.
1575
+ fn is_known_builtin(name: &str) -> bool {
1576
+ Self::KNOWN_BUILTINS.contains(&name)
1577
+ }
1578
+
1579
+ /// Dispatch a builtin call by name. Returns the result or an error.
1580
+ fn dispatch_builtin(&mut self, name: &str, args: &[ZxValue]) -> Result<ZxValue, VmError> {
1581
+ match name {
1582
+ // ── Crypto ───────────────────────────────────────
1583
+ "keccak256" => {
1584
+ let data = args.first().cloned().unwrap_or(ZxValue::Str(String::new()));
1585
+ Ok(Self::builtin_keccak256(&data))
1586
+ }
1587
+ "sha256" => {
1588
+ let data = args.first().cloned().unwrap_or(ZxValue::Str(String::new()));
1589
+ Ok(Self::builtin_sha256(&data))
1590
+ }
1591
+ "verify_sig" => {
1592
+ if args.len() < 3 {
1593
+ return Ok(ZxValue::Bool(false));
1594
+ }
1595
+ Ok(ZxValue::Bool(Self::builtin_verify_sig(
1596
+ &args[0], &args[1], &args[2],
1597
+ )))
1598
+ }
1599
+
1600
+ // ── Events ───────────────────────────────────────
1601
+ "emit" => {
1602
+ let event_name = args
1603
+ .first()
1604
+ .map(|v| v.display_str())
1605
+ .unwrap_or_default();
1606
+ let event_data = args.get(1).cloned().unwrap_or(ZxValue::Null);
1607
+ self.events.push((event_name, event_data));
1608
+ Ok(ZxValue::Null)
1609
+ }
1610
+
1611
+ // ── Chain info ───────────────────────────────────
1612
+ "block_number" => {
1613
+ let val = self
1614
+ .env
1615
+ .get("_block_number")
1616
+ .cloned()
1617
+ .unwrap_or(ZxValue::Int(0));
1618
+ Ok(val)
1619
+ }
1620
+ "block_timestamp" => {
1621
+ let val = self
1622
+ .env
1623
+ .get("_block_timestamp")
1624
+ .cloned()
1625
+ .unwrap_or(ZxValue::Float(0.0));
1626
+ Ok(val)
1627
+ }
1628
+
1629
+ // ── Balance / Transfer ───────────────────────────
1630
+ "get_balance" => {
1631
+ let addr = args
1632
+ .first()
1633
+ .map(|v| v.display_str())
1634
+ .unwrap_or_default();
1635
+ let key = format!("_balance:{}", addr);
1636
+ let balance = self
1637
+ .blockchain_state
1638
+ .get(&key)
1639
+ .cloned()
1640
+ .unwrap_or(ZxValue::Int(0));
1641
+ Ok(balance)
1642
+ }
1643
+ "transfer" => {
1644
+ if args.len() < 2 {
1645
+ return Ok(ZxValue::Bool(false));
1646
+ }
1647
+ let to_addr = args[0].display_str();
1648
+ let amount = args[1].as_int();
1649
+ if amount <= 0 {
1650
+ return Ok(ZxValue::Bool(false));
1651
+ }
1652
+ // Get caller address from env
1653
+ let caller = self
1654
+ .env
1655
+ .get("_caller")
1656
+ .map(|v| v.display_str())
1657
+ .unwrap_or_default();
1658
+ let from_key = format!("_balance:{}", caller);
1659
+ let to_key = format!("_balance:{}", to_addr);
1660
+
1661
+ let from_balance = self
1662
+ .blockchain_state
1663
+ .get(&from_key)
1664
+ .map(|v| v.as_int())
1665
+ .unwrap_or(0);
1666
+ if from_balance < amount {
1667
+ return Ok(ZxValue::Bool(false));
1668
+ }
1669
+ let to_balance = self
1670
+ .blockchain_state
1671
+ .get(&to_key)
1672
+ .map(|v| v.as_int())
1673
+ .unwrap_or(0);
1674
+ // Overflow check
1675
+ if to_balance.checked_add(amount).is_none() {
1676
+ return Ok(ZxValue::Bool(false));
1677
+ }
1678
+ // Apply transfer
1679
+ if self.in_transaction {
1680
+ self.tx_pending
1681
+ .insert(from_key, ZxValue::Int(from_balance - amount));
1682
+ self.tx_pending
1683
+ .insert(to_key, ZxValue::Int(to_balance + amount));
1684
+ } else {
1685
+ self.blockchain_state
1686
+ .insert(from_key, ZxValue::Int(from_balance - amount));
1687
+ self.blockchain_state
1688
+ .insert(to_key, ZxValue::Int(to_balance + amount));
1689
+ }
1690
+ Ok(ZxValue::Bool(true))
1691
+ }
1692
+
1693
+ // ── I/O ──────────────────────────────────────────
1694
+ "print" => {
1695
+ let text = args
1696
+ .iter()
1697
+ .map(|v| v.display_str())
1698
+ .collect::<Vec<_>>()
1699
+ .join(" ");
1700
+ self.output.push(text);
1701
+ Ok(ZxValue::Null)
1702
+ }
1703
+
1704
+ // ── Type / Conversion builtins ───────────────────
1705
+ "len" => {
1706
+ let val = args.first().cloned().unwrap_or(ZxValue::Null);
1707
+ match &val {
1708
+ ZxValue::Str(s) => Ok(ZxValue::Int(s.len() as i64)),
1709
+ ZxValue::List(v) => Ok(ZxValue::Int(v.len() as i64)),
1710
+ ZxValue::Map(v) => Ok(ZxValue::Int(v.len() as i64)),
1711
+ _ => Ok(ZxValue::Int(0)),
1712
+ }
1713
+ }
1714
+ "str" | "to_string" => {
1715
+ let val = args.first().cloned().unwrap_or(ZxValue::Null);
1716
+ Ok(ZxValue::Str(val.display_str()))
1717
+ }
1718
+ "int" | "to_int" => {
1719
+ let val = args.first().cloned().unwrap_or(ZxValue::Null);
1720
+ match &val {
1721
+ ZxValue::Str(s) => {
1722
+ let i = s.parse::<i64>().unwrap_or(0);
1723
+ Ok(ZxValue::Int(i))
1724
+ }
1725
+ _ => Ok(ZxValue::Int(val.as_int())),
1726
+ }
1727
+ }
1728
+ "float" | "to_float" => {
1729
+ let val = args.first().cloned().unwrap_or(ZxValue::Null);
1730
+ match &val {
1731
+ ZxValue::Str(s) => {
1732
+ let f = s.parse::<f64>().unwrap_or(0.0);
1733
+ Ok(ZxValue::Float(f))
1734
+ }
1735
+ _ => Ok(ZxValue::Float(val.as_float())),
1736
+ }
1737
+ }
1738
+ "type" => {
1739
+ let val = args.first().cloned().unwrap_or(ZxValue::Null);
1740
+ let t = match &val {
1741
+ ZxValue::Null => "null",
1742
+ ZxValue::Bool(_) => "bool",
1743
+ ZxValue::Int(_) => "int",
1744
+ ZxValue::Float(_) => "float",
1745
+ ZxValue::Str(_) => "string",
1746
+ ZxValue::List(_) => "list",
1747
+ ZxValue::Map(_) => "map",
1748
+ ZxValue::PyObj(_) => "object",
1749
+ };
1750
+ Ok(ZxValue::Str(t.to_string()))
1751
+ }
1752
+ "abs" => {
1753
+ let val = args.first().cloned().unwrap_or(ZxValue::Int(0));
1754
+ match &val {
1755
+ ZxValue::Int(i) => Ok(ZxValue::Int(i.abs())),
1756
+ ZxValue::Float(f) => Ok(ZxValue::Float(f.abs())),
1757
+ _ => Ok(ZxValue::Int(val.as_int().abs())),
1758
+ }
1759
+ }
1760
+ "min" => {
1761
+ if args.len() < 2 {
1762
+ return Ok(args.first().cloned().unwrap_or(ZxValue::Null));
1763
+ }
1764
+ let a = &args[0];
1765
+ let b = &args[1];
1766
+ match a.partial_cmp(b) {
1767
+ Some(std::cmp::Ordering::Less) | Some(std::cmp::Ordering::Equal) => {
1768
+ Ok(a.clone())
1769
+ }
1770
+ _ => Ok(b.clone()),
1771
+ }
1772
+ }
1773
+ "max" => {
1774
+ if args.len() < 2 {
1775
+ return Ok(args.first().cloned().unwrap_or(ZxValue::Null));
1776
+ }
1777
+ let a = &args[0];
1778
+ let b = &args[1];
1779
+ match a.partial_cmp(b) {
1780
+ Some(std::cmp::Ordering::Greater) | Some(std::cmp::Ordering::Equal) => {
1781
+ Ok(a.clone())
1782
+ }
1783
+ _ => Ok(b.clone()),
1784
+ }
1785
+ }
1786
+
1787
+ // ── Collection builtins ──────────────────────────
1788
+ "keys" => {
1789
+ let val = args.first().cloned().unwrap_or(ZxValue::Null);
1790
+ match &val {
1791
+ ZxValue::Map(pairs) => {
1792
+ let keys: Vec<ZxValue> =
1793
+ pairs.iter().map(|(k, _)| ZxValue::Str(k.clone())).collect();
1794
+ Ok(ZxValue::List(keys))
1795
+ }
1796
+ _ => Ok(ZxValue::List(vec![])),
1797
+ }
1798
+ }
1799
+ "values" => {
1800
+ let val = args.first().cloned().unwrap_or(ZxValue::Null);
1801
+ match &val {
1802
+ ZxValue::Map(pairs) => {
1803
+ let vals: Vec<ZxValue> =
1804
+ pairs.iter().map(|(_, v)| v.clone()).collect();
1805
+ Ok(ZxValue::List(vals))
1806
+ }
1807
+ _ => Ok(ZxValue::List(vec![])),
1808
+ }
1809
+ }
1810
+ "push" => {
1811
+ // push(list, item) → list with item appended
1812
+ if args.len() < 2 {
1813
+ return Ok(args.first().cloned().unwrap_or(ZxValue::Null));
1814
+ }
1815
+ match &args[0] {
1816
+ ZxValue::List(items) => {
1817
+ let mut new_list = items.clone();
1818
+ new_list.push(args[1].clone());
1819
+ Ok(ZxValue::List(new_list))
1820
+ }
1821
+ _ => Ok(args[0].clone()),
1822
+ }
1823
+ }
1824
+ "pop" => {
1825
+ // pop(list) → last element (or null)
1826
+ let val = args.first().cloned().unwrap_or(ZxValue::Null);
1827
+ match &val {
1828
+ ZxValue::List(items) => {
1829
+ Ok(items.last().cloned().unwrap_or(ZxValue::Null))
1830
+ }
1831
+ _ => Ok(ZxValue::Null),
1832
+ }
1833
+ }
1834
+ "contains" => {
1835
+ if args.len() < 2 {
1836
+ return Ok(ZxValue::Bool(false));
1837
+ }
1838
+ match &args[0] {
1839
+ ZxValue::List(items) => {
1840
+ let found = items.iter().any(|item| item == &args[1]);
1841
+ Ok(ZxValue::Bool(found))
1842
+ }
1843
+ ZxValue::Str(s) => {
1844
+ let needle = args[1].display_str();
1845
+ Ok(ZxValue::Bool(s.contains(&needle)))
1846
+ }
1847
+ ZxValue::Map(pairs) => {
1848
+ let key = args[1].display_str();
1849
+ let found = pairs.iter().any(|(k, _)| k == &key);
1850
+ Ok(ZxValue::Bool(found))
1851
+ }
1852
+ _ => Ok(ZxValue::Bool(false)),
1853
+ }
1854
+ }
1855
+ "index_of" => {
1856
+ if args.len() < 2 {
1857
+ return Ok(ZxValue::Int(-1));
1858
+ }
1859
+ match &args[0] {
1860
+ ZxValue::List(items) => {
1861
+ let pos = items.iter().position(|item| item == &args[1]);
1862
+ Ok(ZxValue::Int(pos.map(|p| p as i64).unwrap_or(-1)))
1863
+ }
1864
+ ZxValue::Str(s) => {
1865
+ let needle = args[1].display_str();
1866
+ let pos = s.find(&needle);
1867
+ Ok(ZxValue::Int(pos.map(|p| p as i64).unwrap_or(-1)))
1868
+ }
1869
+ _ => Ok(ZxValue::Int(-1)),
1870
+ }
1871
+ }
1872
+ "slice" => {
1873
+ // slice(collection, start, end?)
1874
+ let coll = args.first().cloned().unwrap_or(ZxValue::Null);
1875
+ let start = args.get(1).map(|v| v.as_int()).unwrap_or(0).max(0) as usize;
1876
+ let end_arg = args.get(2);
1877
+ match &coll {
1878
+ ZxValue::List(items) => {
1879
+ let end = end_arg
1880
+ .map(|v| v.as_int().max(0) as usize)
1881
+ .unwrap_or(items.len())
1882
+ .min(items.len());
1883
+ let start = start.min(end);
1884
+ Ok(ZxValue::List(items[start..end].to_vec()))
1885
+ }
1886
+ ZxValue::Str(s) => {
1887
+ let chars: Vec<char> = s.chars().collect();
1888
+ let end = end_arg
1889
+ .map(|v| v.as_int().max(0) as usize)
1890
+ .unwrap_or(chars.len())
1891
+ .min(chars.len());
1892
+ let start = start.min(end);
1893
+ Ok(ZxValue::Str(chars[start..end].iter().collect()))
1894
+ }
1895
+ _ => Ok(ZxValue::Null),
1896
+ }
1897
+ }
1898
+ "reverse" => {
1899
+ let val = args.first().cloned().unwrap_or(ZxValue::Null);
1900
+ match val {
1901
+ ZxValue::List(mut items) => {
1902
+ items.reverse();
1903
+ Ok(ZxValue::List(items))
1904
+ }
1905
+ ZxValue::Str(s) => Ok(ZxValue::Str(s.chars().rev().collect())),
1906
+ _ => Ok(val),
1907
+ }
1908
+ }
1909
+ "sort" => {
1910
+ let val = args.first().cloned().unwrap_or(ZxValue::Null);
1911
+ match val {
1912
+ ZxValue::List(mut items) => {
1913
+ items.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
1914
+ Ok(ZxValue::List(items))
1915
+ }
1916
+ _ => Ok(val),
1917
+ }
1918
+ }
1919
+ "concat" => {
1920
+ // concat(a, b) — concatenate strings or lists
1921
+ if args.len() < 2 {
1922
+ return Ok(args.first().cloned().unwrap_or(ZxValue::Null));
1923
+ }
1924
+ match (&args[0], &args[1]) {
1925
+ (ZxValue::Str(a), ZxValue::Str(b)) => {
1926
+ Ok(ZxValue::Str(format!("{}{}", a, b)))
1927
+ }
1928
+ (ZxValue::List(a), ZxValue::List(b)) => {
1929
+ let mut result = a.clone();
1930
+ result.extend(b.iter().cloned());
1931
+ Ok(ZxValue::List(result))
1932
+ }
1933
+ _ => Ok(ZxValue::Str(format!(
1934
+ "{}{}",
1935
+ args[0].display_str(),
1936
+ args[1].display_str()
1937
+ ))),
1938
+ }
1939
+ }
1940
+
1941
+ // ── String builtins ──────────────────────────────
1942
+ "split" => {
1943
+ let s = args.first().map(|v| v.display_str()).unwrap_or_default();
1944
+ let sep = args.get(1).map(|v| v.display_str()).unwrap_or_else(|| " ".to_string());
1945
+ let parts: Vec<ZxValue> = s.split(&sep).map(|p| ZxValue::Str(p.to_string())).collect();
1946
+ Ok(ZxValue::List(parts))
1947
+ }
1948
+ "join" => {
1949
+ // join(list, separator)
1950
+ let list = args.first().cloned().unwrap_or(ZxValue::List(vec![]));
1951
+ let sep = args.get(1).map(|v| v.display_str()).unwrap_or_else(|| ",".to_string());
1952
+ match &list {
1953
+ ZxValue::List(items) => {
1954
+ let joined: String = items
1955
+ .iter()
1956
+ .map(|v| v.display_str())
1957
+ .collect::<Vec<_>>()
1958
+ .join(&sep);
1959
+ Ok(ZxValue::Str(joined))
1960
+ }
1961
+ _ => Ok(ZxValue::Str(list.display_str())),
1962
+ }
1963
+ }
1964
+ "trim" => {
1965
+ let s = args.first().map(|v| v.display_str()).unwrap_or_default();
1966
+ Ok(ZxValue::Str(s.trim().to_string()))
1967
+ }
1968
+ "upper" => {
1969
+ let s = args.first().map(|v| v.display_str()).unwrap_or_default();
1970
+ Ok(ZxValue::Str(s.to_uppercase()))
1971
+ }
1972
+ "lower" => {
1973
+ let s = args.first().map(|v| v.display_str()).unwrap_or_default();
1974
+ Ok(ZxValue::Str(s.to_lowercase()))
1975
+ }
1976
+ "starts_with" => {
1977
+ if args.len() < 2 {
1978
+ return Ok(ZxValue::Bool(false));
1979
+ }
1980
+ let s = args[0].display_str();
1981
+ let prefix = args[1].display_str();
1982
+ Ok(ZxValue::Bool(s.starts_with(&prefix)))
1983
+ }
1984
+ "ends_with" => {
1985
+ if args.len() < 2 {
1986
+ return Ok(ZxValue::Bool(false));
1987
+ }
1988
+ let s = args[0].display_str();
1989
+ let suffix = args[1].display_str();
1990
+ Ok(ZxValue::Bool(s.ends_with(&suffix)))
1991
+ }
1992
+ "replace" => {
1993
+ // replace(string, old, new)
1994
+ if args.len() < 3 {
1995
+ return Ok(args.first().cloned().unwrap_or(ZxValue::Null));
1996
+ }
1997
+ let s = args[0].display_str();
1998
+ let old = args[1].display_str();
1999
+ let new = args[2].display_str();
2000
+ Ok(ZxValue::Str(s.replace(&old, &new)))
2001
+ }
2002
+ "substring" => {
2003
+ // substring(string, start, end?)
2004
+ let s = args.first().map(|v| v.display_str()).unwrap_or_default();
2005
+ let chars: Vec<char> = s.chars().collect();
2006
+ let start = args.get(1).map(|v| v.as_int().max(0) as usize).unwrap_or(0);
2007
+ let end = args
2008
+ .get(2)
2009
+ .map(|v| v.as_int().max(0) as usize)
2010
+ .unwrap_or(chars.len())
2011
+ .min(chars.len());
2012
+ let start = start.min(end);
2013
+ Ok(ZxValue::Str(chars[start..end].iter().collect()))
2014
+ }
2015
+ "char_at" => {
2016
+ let s = args.first().map(|v| v.display_str()).unwrap_or_default();
2017
+ let idx = args.get(1).map(|v| v.as_int().max(0) as usize).unwrap_or(0);
2018
+ let ch = s.chars().nth(idx);
2019
+ Ok(ch
2020
+ .map(|c| ZxValue::Str(c.to_string()))
2021
+ .unwrap_or(ZxValue::Null))
2022
+ }
2023
+
2024
+ // ── Cross-contract calls (require Python) ────────
2025
+ "contract_call" | "static_call" | "delegate_call" => {
2026
+ Err(VmError::NeedsPythonFallback)
2027
+ }
2028
+
2029
+ // ── Unknown ──────────────────────────────────────
2030
+ _ => Err(VmError::NeedsPythonFallback),
2031
+ }
2032
+ }
2033
+
2034
+ // ── Builtin implementations ─────────────────────────────────────
2035
+
2036
+ /// Keccak-256 hash → hex string.
2037
+ fn builtin_keccak256(data: &ZxValue) -> ZxValue {
2038
+ let input = data.display_str();
2039
+ let mut hasher = Keccak::v256();
2040
+ let mut output = [0u8; 32];
2041
+ hasher.update(input.as_bytes());
2042
+ hasher.finalize(&mut output);
2043
+ ZxValue::Str(hex::encode(output))
2044
+ }
2045
+
2046
+ /// SHA-256 hash → hex string.
2047
+ fn builtin_sha256(data: &ZxValue) -> ZxValue {
2048
+ let input = data.display_str();
2049
+ let hash = Sha256::digest(input.as_bytes());
2050
+ ZxValue::Str(hex::encode(hash))
2051
+ }
2052
+
2053
+ /// ECDSA-secp256k1 signature verification.
2054
+ fn builtin_verify_sig(sig: &ZxValue, msg: &ZxValue, pk: &ZxValue) -> bool {
2055
+ let sig_bytes = Self::zxvalue_to_bytes(sig);
2056
+ let msg_bytes = Self::zxvalue_to_bytes(msg);
2057
+ let pk_bytes = Self::zxvalue_to_bytes(pk);
2058
+ verify_single(&msg_bytes, &sig_bytes, &pk_bytes)
2059
+ }
2060
+
2061
+ /// Convert ZxValue to bytes (for crypto operations).
2062
+ fn zxvalue_to_bytes(val: &ZxValue) -> Vec<u8> {
2063
+ match val {
2064
+ ZxValue::Str(s) => {
2065
+ // Try hex decode first, then fall back to raw bytes
2066
+ hex::decode(s).unwrap_or_else(|_| s.as_bytes().to_vec())
2067
+ }
2068
+ _ => val.display_str().as_bytes().to_vec(),
2069
+ }
2070
+ }
2071
+ }
2072
+
2073
+ // ── PyO3 Bridge — RustVMExecutor ─────────────────────────────────────
2074
+
2075
+ /// Python-callable executor wrapping the Rust bytecode interpreter.
2076
+ ///
2077
+ /// Usage from Python:
2078
+ /// ```python
2079
+ /// from zexus_core import RustVMExecutor
2080
+ /// executor = RustVMExecutor()
2081
+ /// result = executor.execute(zxc_bytes, env={}, state={}, gas_limit=1000000)
2082
+ /// ```
2083
+ #[pyclass]
2084
+ pub struct RustVMExecutor {
2085
+ /// Stats from last execution
2086
+ last_instructions: u64,
2087
+ last_gas_used: u64,
2088
+ /// Gas discount factor (0.0–1.0). Default 0.6 = 40% cheaper than Python.
2089
+ gas_discount: f64,
2090
+ }
2091
+
2092
+ #[pymethods]
2093
+ impl RustVMExecutor {
2094
+ #[new]
2095
+ #[pyo3(signature = (gas_discount=0.6))]
2096
+ fn new(gas_discount: f64) -> Self {
2097
+ RustVMExecutor {
2098
+ last_instructions: 0,
2099
+ last_gas_used: 0,
2100
+ gas_discount: gas_discount.clamp(0.01, 1.0),
2101
+ }
2102
+ }
2103
+
2104
+ /// Get the current gas discount factor.
2105
+ #[getter]
2106
+ fn gas_discount(&self) -> f64 {
2107
+ self.gas_discount
2108
+ }
2109
+
2110
+ /// Set the gas discount factor (0.01–1.0).
2111
+ #[setter]
2112
+ fn set_gas_discount(&mut self, discount: f64) {
2113
+ self.gas_discount = discount.clamp(0.01, 1.0);
2114
+ }
2115
+
2116
+ /// Execute .zxc binary bytecode and return the result.
2117
+ ///
2118
+ /// Args:
2119
+ /// data: bytes — serialized .zxc bytecode
2120
+ /// env: dict — initial environment variables (optional)
2121
+ /// state: dict — initial blockchain state (optional)
2122
+ /// gas_limit: int — gas limit (0 = unlimited)
2123
+ ///
2124
+ /// Returns:
2125
+ /// dict with keys: result, env, state, output, gas_used,
2126
+ /// instructions_executed, needs_fallback
2127
+ #[pyo3(signature = (data, env=None, state=None, gas_limit=0))]
2128
+ fn execute(
2129
+ &mut self,
2130
+ py: Python<'_>,
2131
+ data: &Bound<'_, PyBytes>,
2132
+ env: Option<&Bound<'_, PyDict>>,
2133
+ state: Option<&Bound<'_, PyDict>>,
2134
+ gas_limit: u64,
2135
+ ) -> PyResult<PyObject> {
2136
+ let raw = data.as_bytes();
2137
+
2138
+ // Deserialize .zxc → ZxcModule (GIL-free)
2139
+ let module = binary_bytecode::deserialize_zxc(raw, true)
2140
+ .map_err(|e| pyo3::exceptions::PyValueError::new_err(e))?;
2141
+
2142
+ // Create VM
2143
+ let mut vm = RustVM::from_module(&module);
2144
+
2145
+ // Set gas limit and discount
2146
+ if gas_limit > 0 {
2147
+ vm.set_gas_limit(gas_limit);
2148
+ }
2149
+ vm.set_gas_discount(self.gas_discount);
2150
+
2151
+ // Convert Python env → Rust env
2152
+ if let Some(py_env) = env {
2153
+ let mut rust_env = HashMap::new();
2154
+ for (k, v) in py_env.iter() {
2155
+ let key = k.extract::<String>().unwrap_or_default();
2156
+ let val = py_to_zx(py, &v.to_object(py));
2157
+ rust_env.insert(key, val);
2158
+ }
2159
+ vm.set_env(rust_env);
2160
+ }
2161
+
2162
+ // Convert Python state → Rust state
2163
+ if let Some(py_state) = state {
2164
+ let mut rust_state = HashMap::new();
2165
+ for (k, v) in py_state.iter() {
2166
+ let key = k.extract::<String>().unwrap_or_default();
2167
+ let val = py_to_zx(py, &v.to_object(py));
2168
+ rust_state.insert(key, val);
2169
+ }
2170
+ vm.set_blockchain_state(rust_state);
2171
+ }
2172
+
2173
+ // Execute (GIL is held but computation is pure Rust)
2174
+ let exec_result = vm.execute();
2175
+
2176
+ // Build result dict
2177
+ let result_dict = PyDict::new_bound(py);
2178
+
2179
+ match exec_result {
2180
+ Ok(val) => {
2181
+ result_dict.set_item("result", zx_to_py(py, &val))?;
2182
+ result_dict.set_item("needs_fallback", false)?;
2183
+ result_dict.set_item("error", py.None())?;
2184
+ }
2185
+ Err(VmError::NeedsPythonFallback) => {
2186
+ result_dict.set_item("result", py.None())?;
2187
+ result_dict.set_item("needs_fallback", true)?;
2188
+ result_dict.set_item("error", py.None())?;
2189
+ }
2190
+ Err(VmError::OutOfGas { used, limit, opcode }) => {
2191
+ result_dict.set_item("result", py.None())?;
2192
+ result_dict.set_item("needs_fallback", false)?;
2193
+ result_dict
2194
+ .set_item("error", format!("OutOfGas: used={}, limit={}, op={}", used, limit, opcode))?;
2195
+ }
2196
+ Err(VmError::RequireFailed(msg)) => {
2197
+ result_dict.set_item("result", py.None())?;
2198
+ result_dict.set_item("needs_fallback", false)?;
2199
+ result_dict.set_item("error", format!("RequireFailed: {}", msg))?;
2200
+ }
2201
+ Err(e) => {
2202
+ result_dict.set_item("result", py.None())?;
2203
+ result_dict.set_item("needs_fallback", false)?;
2204
+ result_dict.set_item("error", format!("{}", e))?;
2205
+ }
2206
+ }
2207
+
2208
+ // Return environment
2209
+ let py_env_out = PyDict::new_bound(py);
2210
+ for (k, v) in vm.get_env() {
2211
+ py_env_out.set_item(k, zx_to_py(py, v))?;
2212
+ }
2213
+ result_dict.set_item("env", py_env_out)?;
2214
+
2215
+ // Return blockchain state
2216
+ let py_state_out = PyDict::new_bound(py);
2217
+ for (k, v) in vm.get_blockchain_state() {
2218
+ py_state_out.set_item(k, zx_to_py(py, v))?;
2219
+ }
2220
+ result_dict.set_item("state", py_state_out)?;
2221
+
2222
+ // Output
2223
+ let py_output = PyList::empty_bound(py);
2224
+ for line in vm.get_output() {
2225
+ py_output.append(line)?;
2226
+ }
2227
+ result_dict.set_item("output", py_output)?;
2228
+
2229
+ // Events (Phase 6)
2230
+ let py_events = PyList::empty_bound(py);
2231
+ for (name, data) in vm.get_events() {
2232
+ let ev = PyDict::new_bound(py);
2233
+ ev.set_item("event", name)?;
2234
+ ev.set_item("data", zx_to_py(py, data))?;
2235
+ py_events.append(ev)?;
2236
+ }
2237
+ result_dict.set_item("events", py_events)?;
2238
+
2239
+ // Stats
2240
+ let (instr_count, gas_used, _gas_limit) = vm.get_stats();
2241
+ result_dict.set_item("gas_used", gas_used)?;
2242
+ result_dict.set_item("instructions_executed", instr_count)?;
2243
+ result_dict.set_item("gas_discount", self.gas_discount)?;
2244
+
2245
+ self.last_instructions = instr_count;
2246
+ self.last_gas_used = gas_used;
2247
+
2248
+ Ok(result_dict.to_object(py))
2249
+ }
2250
+
2251
+ /// Get stats from the last execution.
2252
+ fn last_stats(&self) -> (u64, u64) {
2253
+ (self.last_instructions, self.last_gas_used)
2254
+ }
2255
+
2256
+ /// Execute .zxc binary bytecode without any Python interop.
2257
+ /// Returns only numeric stats (for benchmarking).
2258
+ #[pyo3(signature = (data, iterations=1, gas_limit=0))]
2259
+ fn benchmark(
2260
+ &self,
2261
+ py: Python<'_>,
2262
+ data: &Bound<'_, PyBytes>,
2263
+ iterations: u32,
2264
+ gas_limit: u64,
2265
+ ) -> PyResult<PyObject> {
2266
+ let raw = data.as_bytes();
2267
+
2268
+ let module = binary_bytecode::deserialize_zxc(raw, true)
2269
+ .map_err(|e| pyo3::exceptions::PyValueError::new_err(e))?;
2270
+
2271
+ let start = std::time::Instant::now();
2272
+ let mut total_instrs: u64 = 0;
2273
+ let mut last_result = ZxValue::Null;
2274
+
2275
+ for _ in 0..iterations {
2276
+ let mut vm = RustVM::from_module(&module);
2277
+ if gas_limit > 0 {
2278
+ vm.set_gas_limit(gas_limit);
2279
+ }
2280
+ match vm.execute() {
2281
+ Ok(val) => {
2282
+ let (instrs, _, _) = vm.get_stats();
2283
+ total_instrs += instrs;
2284
+ last_result = val;
2285
+ }
2286
+ Err(VmError::NeedsPythonFallback) => {
2287
+ let (instrs, _, _) = vm.get_stats();
2288
+ total_instrs += instrs;
2289
+ }
2290
+ Err(e) => {
2291
+ return Err(pyo3::exceptions::PyRuntimeError::new_err(format!("{}", e)));
2292
+ }
2293
+ }
2294
+ }
2295
+
2296
+ let elapsed = start.elapsed();
2297
+ let result = PyDict::new_bound(py);
2298
+ result.set_item("iterations", iterations)?;
2299
+ result.set_item("total_instructions", total_instrs)?;
2300
+ result.set_item("elapsed_ms", elapsed.as_secs_f64() * 1000.0)?;
2301
+ result.set_item(
2302
+ "instructions_per_sec",
2303
+ if elapsed.as_secs_f64() > 0.0 {
2304
+ total_instrs as f64 / elapsed.as_secs_f64()
2305
+ } else {
2306
+ 0.0
2307
+ },
2308
+ )?;
2309
+ result.set_item("result", zx_to_py(py, &last_result))?;
2310
+
2311
+ Ok(result.to_object(py))
2312
+ }
2313
+ }