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
|
@@ -2092,8 +2092,10 @@ class StatementEvaluatorMixin:
|
|
|
2092
2092
|
sandbox_fs = builder.build()
|
|
2093
2093
|
sandbox_env.set('__sandbox_vfs__', sandbox_fs)
|
|
2094
2094
|
sandbox_env.set('__sandbox_id__', sandbox_id)
|
|
2095
|
-
except Exception:
|
|
2096
|
-
|
|
2095
|
+
except Exception as exc:
|
|
2096
|
+
# MEDIUM (M10): If sandbox isolation can't be established, do not
|
|
2097
|
+
# execute the sandbox body under a weaker (or non-existent) policy.
|
|
2098
|
+
return EvaluationError(f"Sandbox initialization failed: {exc}")
|
|
2097
2099
|
|
|
2098
2100
|
# Ensure default sandbox policy exists
|
|
2099
2101
|
try:
|
|
@@ -2228,9 +2230,11 @@ class StatementEvaluatorMixin:
|
|
|
2228
2230
|
|
|
2229
2231
|
# Set up TX context
|
|
2230
2232
|
from ..object import Map, String, Integer
|
|
2233
|
+
# SECURITY (H5): Use module-level import instead of __import__
|
|
2234
|
+
import time as _time_mod
|
|
2231
2235
|
tx_context = Map({
|
|
2232
2236
|
String("caller"): String("system"), # Default caller
|
|
2233
|
-
String("timestamp"): Integer(int(
|
|
2237
|
+
String("timestamp"): Integer(int(_time_mod.time())),
|
|
2234
2238
|
})
|
|
2235
2239
|
contract_env.set("TX", tx_context)
|
|
2236
2240
|
|
|
@@ -3318,7 +3322,17 @@ class StatementEvaluatorMixin:
|
|
|
3318
3322
|
# === PERFORMANCE OPTIMIZATION STATEMENTS ===
|
|
3319
3323
|
|
|
3320
3324
|
def eval_native_statement(self, node, env, stack_trace):
|
|
3321
|
-
"""Evaluate native statement - call C/C++ code directly.
|
|
3325
|
+
"""Evaluate native statement - call C/C++ code directly.
|
|
3326
|
+
|
|
3327
|
+
SECURITY (C3): Requires ZEXUS_ALLOW_NATIVE=1 environment variable.
|
|
3328
|
+
Disabled by default to prevent arbitrary native code loading via ctypes.
|
|
3329
|
+
"""
|
|
3330
|
+
import os as _os
|
|
3331
|
+
if _os.environ.get('ZEXUS_ALLOW_NATIVE') != '1':
|
|
3332
|
+
return EvaluationError(
|
|
3333
|
+
"Native statements are disabled for security. "
|
|
3334
|
+
"Set ZEXUS_ALLOW_NATIVE=1 environment variable to enable ctypes FFI."
|
|
3335
|
+
)
|
|
3322
3336
|
try:
|
|
3323
3337
|
import ctypes
|
|
3324
3338
|
|
|
@@ -4190,107 +4204,6 @@ class StatementEvaluatorMixin:
|
|
|
4190
4204
|
except Exception as e:
|
|
4191
4205
|
debug_log("eval_using_statement", f"Error cleaning up resource: {e}")
|
|
4192
4206
|
|
|
4193
|
-
# === CONCURRENCY & PERFORMANCE STATEMENT EVALUATORS ===
|
|
4194
|
-
|
|
4195
|
-
def eval_channel_statement(self, node, env, stack_trace):
|
|
4196
|
-
"""Evaluate channel statement - declare a message passing channel."""
|
|
4197
|
-
from ..concurrency_system import get_concurrency_manager
|
|
4198
|
-
|
|
4199
|
-
manager = get_concurrency_manager()
|
|
4200
|
-
|
|
4201
|
-
# Get channel name
|
|
4202
|
-
channel_name = node.name.value if hasattr(node.name, 'value') else str(node.name)
|
|
4203
|
-
|
|
4204
|
-
# Get element type if specified
|
|
4205
|
-
element_type = None
|
|
4206
|
-
if hasattr(node, 'element_type') and node.element_type:
|
|
4207
|
-
element_type = str(node.element_type)
|
|
4208
|
-
|
|
4209
|
-
# Get capacity if specified
|
|
4210
|
-
capacity = 0
|
|
4211
|
-
if hasattr(node, 'capacity') and node.capacity:
|
|
4212
|
-
capacity = self.eval_node(node.capacity, env, stack_trace)
|
|
4213
|
-
if isinstance(capacity, Integer):
|
|
4214
|
-
capacity = capacity.value
|
|
4215
|
-
else:
|
|
4216
|
-
capacity = 0
|
|
4217
|
-
|
|
4218
|
-
# Create channel
|
|
4219
|
-
channel = manager.create_channel(channel_name, element_type, capacity)
|
|
4220
|
-
debug_log("eval_channel_statement", f"Created channel: {channel_name}")
|
|
4221
|
-
|
|
4222
|
-
# Store in environment
|
|
4223
|
-
env.set(channel_name, channel)
|
|
4224
|
-
return String(f"Channel '{channel_name}' created")
|
|
4225
|
-
|
|
4226
|
-
def eval_send_statement(self, node, env, stack_trace):
|
|
4227
|
-
"""Evaluate send statement - send value to a channel."""
|
|
4228
|
-
|
|
4229
|
-
# Evaluate channel expression
|
|
4230
|
-
channel = self.eval_node(node.channel_expr, env, stack_trace)
|
|
4231
|
-
|
|
4232
|
-
# Evaluate value to send
|
|
4233
|
-
value = self.eval_node(node.value_expr, env, stack_trace)
|
|
4234
|
-
|
|
4235
|
-
# Send to channel
|
|
4236
|
-
if hasattr(channel, 'send'):
|
|
4237
|
-
try:
|
|
4238
|
-
channel.send(value, timeout=5.0)
|
|
4239
|
-
debug_log("eval_send_statement", f"Sent to channel: {value}")
|
|
4240
|
-
return String(f"Value sent to channel")
|
|
4241
|
-
except Exception as e:
|
|
4242
|
-
return String(f"Error sending to channel: {e}")
|
|
4243
|
-
else:
|
|
4244
|
-
return String(f"Error: not a valid channel")
|
|
4245
|
-
|
|
4246
|
-
def eval_receive_statement(self, node, env, stack_trace):
|
|
4247
|
-
"""Evaluate receive statement - receive value from a channel."""
|
|
4248
|
-
|
|
4249
|
-
# Evaluate channel expression
|
|
4250
|
-
channel = self.eval_node(node.channel_expr, env, stack_trace)
|
|
4251
|
-
|
|
4252
|
-
# Receive from channel
|
|
4253
|
-
if hasattr(channel, 'receive'):
|
|
4254
|
-
try:
|
|
4255
|
-
value = channel.receive(timeout=5.0)
|
|
4256
|
-
debug_log("eval_receive_statement", f"Received from channel: {value}")
|
|
4257
|
-
|
|
4258
|
-
# Bind to target if specified
|
|
4259
|
-
if hasattr(node, 'target') and node.target:
|
|
4260
|
-
target_name = node.target.value if hasattr(node.target, 'value') else str(node.target)
|
|
4261
|
-
env.set(target_name, value)
|
|
4262
|
-
|
|
4263
|
-
return value if value is not None else NULL
|
|
4264
|
-
except Exception as e:
|
|
4265
|
-
return String(f"Error receiving from channel: {e}")
|
|
4266
|
-
else:
|
|
4267
|
-
return String(f"Error: not a valid channel")
|
|
4268
|
-
|
|
4269
|
-
def eval_atomic_statement(self, node, env, stack_trace):
|
|
4270
|
-
"""Evaluate atomic statement - execute indivisible operation."""
|
|
4271
|
-
from ..concurrency_system import get_concurrency_manager
|
|
4272
|
-
|
|
4273
|
-
manager = get_concurrency_manager()
|
|
4274
|
-
|
|
4275
|
-
# Create/get atomic region
|
|
4276
|
-
atomic_id = f"atomic_{id(node)}"
|
|
4277
|
-
atomic = manager.create_atomic(atomic_id)
|
|
4278
|
-
|
|
4279
|
-
# Execute atomically
|
|
4280
|
-
def execute_block():
|
|
4281
|
-
if hasattr(node, 'body') and node.body:
|
|
4282
|
-
# Atomic block
|
|
4283
|
-
return self.eval_node(node.body, env, stack_trace)
|
|
4284
|
-
elif hasattr(node, 'expr') and node.expr:
|
|
4285
|
-
# Atomic expression
|
|
4286
|
-
return self.eval_node(node.expr, env, stack_trace)
|
|
4287
|
-
return NULL
|
|
4288
|
-
|
|
4289
|
-
result = atomic.execute(execute_block)
|
|
4290
|
-
debug_log("eval_atomic_statement", "Atomic operation completed")
|
|
4291
|
-
|
|
4292
|
-
return result if result is not NULL else NULL
|
|
4293
|
-
|
|
4294
4207
|
# === BLOCKCHAIN STATEMENT EVALUATION ===
|
|
4295
4208
|
|
|
4296
4209
|
def eval_ledger_statement(self, node, env, stack_trace):
|
|
@@ -4796,11 +4709,16 @@ class StatementEvaluatorMixin:
|
|
|
4796
4709
|
# Store method signatures
|
|
4797
4710
|
methods = {}
|
|
4798
4711
|
for method in node.methods:
|
|
4799
|
-
|
|
4800
|
-
|
|
4801
|
-
|
|
4802
|
-
'
|
|
4803
|
-
|
|
4712
|
+
# methods may be strings (from context parser) or ActionStatement objects
|
|
4713
|
+
if isinstance(method, str):
|
|
4714
|
+
method_name = method
|
|
4715
|
+
methods[method_name] = {'params': [], 'return_type': None}
|
|
4716
|
+
else:
|
|
4717
|
+
method_name = method.name.value if hasattr(method.name, 'value') else str(method.name) if hasattr(method, 'name') else str(method)
|
|
4718
|
+
methods[method_name] = {
|
|
4719
|
+
'params': method.parameters if hasattr(method, 'parameters') else [],
|
|
4720
|
+
'return_type': method.return_type if hasattr(method, 'return_type') else None
|
|
4721
|
+
}
|
|
4804
4722
|
|
|
4805
4723
|
# Create protocol object
|
|
4806
4724
|
protocol_def = {
|
|
@@ -4825,6 +4743,22 @@ class StatementEvaluatorMixin:
|
|
|
4825
4743
|
# Get variable name
|
|
4826
4744
|
var_name = node.name.value if hasattr(node.name, 'value') else str(node.name)
|
|
4827
4745
|
|
|
4746
|
+
# Ensure persistence backend is initialised on this environment
|
|
4747
|
+
if hasattr(env, '_persistent_storage') and env._persistent_storage is None:
|
|
4748
|
+
try:
|
|
4749
|
+
from ..persistence import PersistentStorage, MemoryTracker
|
|
4750
|
+
# Derive scope from the contract name if available, else use a default
|
|
4751
|
+
scope = env.get("__contract_name__") or "global"
|
|
4752
|
+
if hasattr(scope, 'value'):
|
|
4753
|
+
scope = scope.value
|
|
4754
|
+
scope = str(scope)
|
|
4755
|
+
env._persistent_storage = PersistentStorage(scope)
|
|
4756
|
+
if env._memory_tracker is None:
|
|
4757
|
+
env._memory_tracker = MemoryTracker()
|
|
4758
|
+
env._memory_tracker.start_tracking()
|
|
4759
|
+
except (ImportError, Exception):
|
|
4760
|
+
pass # Persistence module not available — fall through to regular storage
|
|
4761
|
+
|
|
4828
4762
|
# Evaluate initial value if provided
|
|
4829
4763
|
value = NULL
|
|
4830
4764
|
if node.initial_value:
|
|
@@ -4835,8 +4769,17 @@ class StatementEvaluatorMixin:
|
|
|
4835
4769
|
# Mark as persistent in environment (special marker)
|
|
4836
4770
|
env.set(f"__persistent_{var_name}__", True)
|
|
4837
4771
|
|
|
4838
|
-
# Store the
|
|
4839
|
-
env
|
|
4772
|
+
# Store via the persistence layer if available, otherwise regular
|
|
4773
|
+
if hasattr(env, 'set_persistent') and hasattr(env, '_persistent_storage') and env._persistent_storage is not None:
|
|
4774
|
+
env.set_persistent(var_name, value)
|
|
4775
|
+
else:
|
|
4776
|
+
env.set(var_name, value)
|
|
4777
|
+
|
|
4778
|
+
# On startup, restore the previously-persisted value (overrides default)
|
|
4779
|
+
if hasattr(env, 'get_persistent') and hasattr(env, '_persistent_storage') and env._persistent_storage is not None:
|
|
4780
|
+
persisted = env.get_persistent(var_name)
|
|
4781
|
+
if persisted is not None:
|
|
4782
|
+
env.store[var_name] = persisted
|
|
4840
4783
|
|
|
4841
4784
|
return NULL
|
|
4842
4785
|
|
|
@@ -103,6 +103,9 @@ def cache_contract_ast(source_hash: str, ast: Any) -> None:
|
|
|
103
103
|
def get_module_candidates(file_path: str, importer_file: str = None) -> list[str]:
|
|
104
104
|
"""Get candidate paths for a module, checking zpm_modules etc.
|
|
105
105
|
|
|
106
|
+
SECURITY (H7): All resolved candidates are confined to the project root (cwd).
|
|
107
|
+
Absolute paths and traversals outside the project are rejected.
|
|
108
|
+
|
|
106
109
|
Args:
|
|
107
110
|
file_path: The module path to import
|
|
108
111
|
importer_file: The absolute path of the file doing the importing (for relative imports)
|
|
@@ -111,10 +114,16 @@ def get_module_candidates(file_path: str, importer_file: str = None) -> list[str
|
|
|
111
114
|
List of candidate absolute paths to check
|
|
112
115
|
"""
|
|
113
116
|
candidates = []
|
|
117
|
+
project_root = os.path.realpath(os.getcwd())
|
|
118
|
+
|
|
119
|
+
def _is_within_sandbox(resolved: str) -> bool:
|
|
120
|
+
real = os.path.realpath(resolved)
|
|
121
|
+
return real == project_root or real.startswith(project_root + os.sep)
|
|
114
122
|
|
|
115
123
|
if os.path.isabs(file_path):
|
|
116
|
-
# Absolute
|
|
117
|
-
|
|
124
|
+
# SECURITY: Absolute paths are only accepted if within project_root
|
|
125
|
+
if _is_within_sandbox(file_path):
|
|
126
|
+
candidates.append(file_path)
|
|
118
127
|
else:
|
|
119
128
|
# Relative path - resolve based on importer's directory
|
|
120
129
|
if importer_file and file_path.startswith('./'):
|
|
@@ -123,7 +132,7 @@ def get_module_candidates(file_path: str, importer_file: str = None) -> list[str
|
|
|
123
132
|
resolved_path = os.path.join(importer_dir, file_path[2:]) # Remove './'
|
|
124
133
|
candidates.append(resolved_path)
|
|
125
134
|
# Also consider project-root relative paths like "./tests/..."
|
|
126
|
-
candidates.append(os.path.join(
|
|
135
|
+
candidates.append(os.path.join(project_root, file_path[2:]))
|
|
127
136
|
elif importer_file and file_path.startswith('../'):
|
|
128
137
|
# Parent directory relative to importing file
|
|
129
138
|
importer_dir = os.path.dirname(importer_file)
|
|
@@ -135,18 +144,20 @@ def get_module_candidates(file_path: str, importer_file: str = None) -> list[str
|
|
|
135
144
|
importer_dir = os.path.dirname(importer_file)
|
|
136
145
|
candidates.append(os.path.join(importer_dir, file_path))
|
|
137
146
|
# Then relative to current working directory
|
|
138
|
-
candidates.append(os.path.join(
|
|
147
|
+
candidates.append(os.path.join(project_root, file_path))
|
|
139
148
|
|
|
140
149
|
# Also check zpm_modules directory
|
|
141
|
-
candidates.append(os.path.join(
|
|
150
|
+
candidates.append(os.path.join(project_root, 'zpm_modules', file_path))
|
|
142
151
|
|
|
143
152
|
# Try adding typical extensions (.zx, .zexus)
|
|
144
153
|
extended_candidates = []
|
|
145
154
|
for candidate in candidates:
|
|
146
|
-
|
|
147
|
-
if
|
|
148
|
-
extended_candidates.append(candidate
|
|
149
|
-
|
|
155
|
+
# SECURITY: Only keep candidates that resolve within the project root
|
|
156
|
+
if _is_within_sandbox(candidate):
|
|
157
|
+
extended_candidates.append(candidate)
|
|
158
|
+
if not candidate.endswith(('.zx', '.zexus')):
|
|
159
|
+
extended_candidates.append(candidate + '.zx')
|
|
160
|
+
extended_candidates.append(candidate + '.zexus')
|
|
150
161
|
|
|
151
162
|
return extended_candidates
|
|
152
163
|
|