zexus 1.7.2 → 1.8.1
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.
- package/README.md +57 -6
- package/package.json +2 -1
- package/rust_core/Cargo.lock +603 -0
- package/rust_core/Cargo.toml +26 -0
- package/rust_core/README.md +15 -0
- package/rust_core/pyproject.toml +25 -0
- package/rust_core/src/binary_bytecode.rs +543 -0
- package/rust_core/src/contract_vm.rs +643 -0
- package/rust_core/src/executor.rs +847 -0
- package/rust_core/src/hasher.rs +90 -0
- package/rust_core/src/lib.rs +71 -0
- package/rust_core/src/merkle.rs +128 -0
- package/rust_core/src/rust_vm.rs +2313 -0
- package/rust_core/src/signature.rs +79 -0
- package/rust_core/src/state_adapter.rs +281 -0
- package/rust_core/src/validator.rs +116 -0
- package/scripts/postinstall.js +34 -2
- package/src/zexus/__init__.py +1 -1
- package/src/zexus/blockchain/accelerator.py +27 -0
- package/src/zexus/blockchain/contract_vm.py +409 -3
- package/src/zexus/blockchain/rust_bridge.py +64 -0
- package/src/zexus/cli/main.py +1 -1
- package/src/zexus/cli/zpm.py +1 -1
- package/src/zexus/evaluator/bytecode_compiler.py +150 -52
- package/src/zexus/evaluator/core.py +151 -809
- package/src/zexus/evaluator/expressions.py +27 -22
- package/src/zexus/evaluator/functions.py +171 -126
- package/src/zexus/evaluator/statements.py +55 -112
- package/src/zexus/module_cache.py +20 -9
- package/src/zexus/object.py +330 -38
- package/src/zexus/parser/parser.py +69 -14
- package/src/zexus/parser/strategy_context.py +228 -5
- package/src/zexus/parser/strategy_structural.py +2 -2
- package/src/zexus/persistence.py +46 -17
- package/src/zexus/security.py +140 -234
- package/src/zexus/type_checker.py +44 -5
- package/src/zexus/vm/binary_bytecode.py +7 -3
- package/src/zexus/vm/bytecode.py +6 -0
- package/src/zexus/vm/cache.py +24 -46
- package/src/zexus/vm/compiler.py +80 -20
- package/src/zexus/vm/fastops.c +1093 -2975
- package/src/zexus/vm/gas_metering.py +2 -2
- package/src/zexus/vm/memory_pool.py +21 -9
- package/src/zexus/vm/vm.py +527 -67
- package/src/zexus/zpm/package_manager.py +1 -1
- package/src/zexus.egg-info/PKG-INFO +79 -12
- package/src/zexus.egg-info/SOURCES.txt +23 -1
- package/src/zexus.egg-info/requires.txt +26 -0
- package/src/zexus.egg-info/entry_points.txt +0 -4
|
@@ -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
|
+
}
|