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.
Files changed (49) hide show
  1. package/README.md +57 -6
  2. package/package.json +2 -1
  3. package/rust_core/Cargo.lock +603 -0
  4. package/rust_core/Cargo.toml +26 -0
  5. package/rust_core/README.md +15 -0
  6. package/rust_core/pyproject.toml +25 -0
  7. package/rust_core/src/binary_bytecode.rs +543 -0
  8. package/rust_core/src/contract_vm.rs +643 -0
  9. package/rust_core/src/executor.rs +847 -0
  10. package/rust_core/src/hasher.rs +90 -0
  11. package/rust_core/src/lib.rs +71 -0
  12. package/rust_core/src/merkle.rs +128 -0
  13. package/rust_core/src/rust_vm.rs +2313 -0
  14. package/rust_core/src/signature.rs +79 -0
  15. package/rust_core/src/state_adapter.rs +281 -0
  16. package/rust_core/src/validator.rs +116 -0
  17. package/scripts/postinstall.js +34 -2
  18. package/src/zexus/__init__.py +1 -1
  19. package/src/zexus/blockchain/accelerator.py +27 -0
  20. package/src/zexus/blockchain/contract_vm.py +409 -3
  21. package/src/zexus/blockchain/rust_bridge.py +64 -0
  22. package/src/zexus/cli/main.py +1 -1
  23. package/src/zexus/cli/zpm.py +1 -1
  24. package/src/zexus/evaluator/bytecode_compiler.py +150 -52
  25. package/src/zexus/evaluator/core.py +151 -809
  26. package/src/zexus/evaluator/expressions.py +27 -22
  27. package/src/zexus/evaluator/functions.py +171 -126
  28. package/src/zexus/evaluator/statements.py +55 -112
  29. package/src/zexus/module_cache.py +20 -9
  30. package/src/zexus/object.py +330 -38
  31. package/src/zexus/parser/parser.py +69 -14
  32. package/src/zexus/parser/strategy_context.py +228 -5
  33. package/src/zexus/parser/strategy_structural.py +2 -2
  34. package/src/zexus/persistence.py +46 -17
  35. package/src/zexus/security.py +140 -234
  36. package/src/zexus/type_checker.py +44 -5
  37. package/src/zexus/vm/binary_bytecode.py +7 -3
  38. package/src/zexus/vm/bytecode.py +6 -0
  39. package/src/zexus/vm/cache.py +24 -46
  40. package/src/zexus/vm/compiler.py +80 -20
  41. package/src/zexus/vm/fastops.c +1093 -2975
  42. package/src/zexus/vm/gas_metering.py +2 -2
  43. package/src/zexus/vm/memory_pool.py +21 -9
  44. package/src/zexus/vm/vm.py +527 -67
  45. package/src/zexus/zpm/package_manager.py +1 -1
  46. package/src/zexus.egg-info/PKG-INFO +79 -12
  47. package/src/zexus.egg-info/SOURCES.txt +23 -1
  48. package/src/zexus.egg-info/requires.txt +26 -0
  49. package/src/zexus.egg-info/entry_points.txt +0 -4
@@ -17,7 +17,6 @@ Enhanced: File-based persistent caching for faster repeat runs
17
17
 
18
18
  import hashlib
19
19
  import json
20
- import pickle
21
20
  import time
22
21
  from collections import OrderedDict
23
22
  from dataclasses import dataclass
@@ -271,8 +270,9 @@ class BytecodeCache:
271
270
  constants_size = 0
272
271
  for const in bytecode.constants:
273
272
  try:
274
- constants_size += len(pickle.dumps(const))
275
- except (TypeError, pickle.PicklingError):
273
+ import sys as _sys
274
+ constants_size += _sys.getsizeof(const)
275
+ except (TypeError, ValueError):
276
276
  constants_size += 100 # Fallback estimate
277
277
 
278
278
  # Add metadata overhead
@@ -504,9 +504,9 @@ class BytecodeCache:
504
504
  with open(cache_file, 'wb') as f:
505
505
  f.write(data)
506
506
  else:
507
- cache_file = self.cache_dir / f"{key}.cache"
508
- with open(cache_file, 'wb') as f:
509
- pickle.dump(bytecode, f, protocol=pickle.HIGHEST_PROTOCOL)
507
+ # SECURITY (H14): Legacy pickle format disabled — use .zxc only
508
+ if self.debug:
509
+ print(f"⚠️ Cache: Skipping disk save (pickle disabled, .zxc not available)")
510
510
 
511
511
  if self.debug:
512
512
  print(f"💾 Cache: Saved to disk {key[:8]}...")
@@ -531,14 +531,9 @@ class BytecodeCache:
531
531
  print(f"💾 Cache: Loaded .zxc from disk {key[:8]}...")
532
532
  return bytecode
533
533
 
534
- # Fallback to legacy pickle format
535
- cache_file = self.cache_dir / f"{key}.cache"
536
- if cache_file.exists():
537
- with open(cache_file, 'rb') as f:
538
- bytecode = pickle.load(f)
539
- if self.debug:
540
- print(f"💾 Cache: Loaded .cache from disk {key[:8]}...")
541
- return bytecode
534
+ # SECURITY (H14): Legacy pickle .cache format disabled
535
+ # Only .zxc binary format is supported for disk cache
536
+ pass
542
537
  except Exception as e:
543
538
  if self.debug:
544
539
  print(f"⚠️ Cache: Failed to load from disk: {e}")
@@ -713,10 +708,9 @@ class BytecodeCache:
713
708
  f.write(meta_bytes)
714
709
  f.write(bc_bytes)
715
710
  else:
716
- cache_file = self.cache_dir / f"file_{file_key}.cache"
717
- data = {'metadata': meta_dict, 'bytecodes': bytecodes, 'cached_at': time.time()}
718
- with open(cache_file, 'wb') as f:
719
- pickle.dump(data, f, protocol=pickle.HIGHEST_PROTOCOL)
711
+ # SECURITY (H14): Legacy pickle format disabled
712
+ if self.debug:
713
+ print(f"⚠️ FileCache: Skipping disk save (pickle disabled, .zxc not available)")
720
714
 
721
715
  if self.debug:
722
716
  print(f"💾 FileCache: Saved to disk {file_key[:8]}...")
@@ -749,16 +743,8 @@ class BytecodeCache:
749
743
  bytecodes = _zxc_deserialize_multi(raw[4 + meta_len:])
750
744
  cache_file = zxc_file
751
745
 
752
- # Fallback to legacy pickle
753
- if bytecodes is None:
754
- pkl_file = self.cache_dir / f"file_{file_key}.cache"
755
- if pkl_file.exists():
756
- with open(pkl_file, 'rb') as f:
757
- data = pickle.load(f)
758
- cached_meta = data.get('metadata', {})
759
- bytecodes = data.get('bytecodes', [])
760
- cached_at = data.get('cached_at', time.time())
761
- cache_file = pkl_file
746
+ # SECURITY (H14): Legacy pickle format disabled
747
+ pass
762
748
 
763
749
  if cached_meta is None or bytecodes is None:
764
750
  return None
@@ -810,10 +796,11 @@ class BytecodeCache:
810
796
  return
811
797
 
812
798
  try:
813
- index_file = self.cache_dir / 'file_index.cache'
799
+ # SECURITY (H14): Use JSON instead of pickle for index
800
+ index_file = self.cache_dir / 'file_index.json'
814
801
  if index_file.exists():
815
- with open(index_file, 'rb') as f:
816
- index = pickle.load(f)
802
+ with open(index_file, 'r') as f:
803
+ index = json.load(f)
817
804
  if self.debug:
818
805
  print(f"📂 FileCache: Loaded index with {len(index)} files")
819
806
  except Exception as e:
@@ -826,7 +813,8 @@ class BytecodeCache:
826
813
  return
827
814
 
828
815
  try:
829
- index_file = self.cache_dir / 'file_index.cache'
816
+ # SECURITY (H14): Use JSON instead of pickle for index
817
+ index_file = self.cache_dir / 'file_index.json'
830
818
  # Just save file keys and metadata (not bytecode)
831
819
  index = {
832
820
  key: {
@@ -837,8 +825,8 @@ class BytecodeCache:
837
825
  }
838
826
  for key, data in self._file_cache.items()
839
827
  }
840
- with open(index_file, 'wb') as f:
841
- pickle.dump(index, f, protocol=pickle.HIGHEST_PROTOCOL)
828
+ with open(index_file, 'w') as f:
829
+ json.dump(index, f)
842
830
  except Exception as e:
843
831
  if self.debug:
844
832
  print(f"⚠️ FileCache: Failed to save index: {e}")
@@ -985,18 +973,8 @@ class BytecodeCache:
985
973
  cached.get('content_hash') == metadata.content_hash):
986
974
  return True
987
975
 
988
- # Check disk cache
989
- if self.persistent and self.cache_dir:
990
- cache_file = self.cache_dir / f"file_{file_key}.cache"
991
- if cache_file.exists():
992
- try:
993
- with open(cache_file, 'rb') as f:
994
- data = pickle.load(f)
995
- cached_meta = data.get('metadata', {})
996
- return (cached_meta.get('mtime') == metadata.mtime and
997
- cached_meta.get('content_hash') == metadata.content_hash)
998
- except Exception:
999
- pass
976
+ # SECURITY (H14): Legacy pickle .cache disk cache disabled
977
+ # Only .zxc format is used for disk caching now
1000
978
 
1001
979
  return False
1002
980
 
@@ -139,6 +139,20 @@ class BytecodeCompiler:
139
139
  f"Failed to compile {node_type}: {exc}"
140
140
  ) from exc
141
141
 
142
+ def _emit_vm_builtin_call(self, builtin_name: str, payload, discard_result: bool = True) -> None:
143
+ """Emit a VM-builtin call with the AST payload as a constant.
144
+
145
+ This is used to keep the VM bytecode compiler aligned with the evaluator
146
+ bytecode compiler for directive-like statements that the VM can delegate
147
+ to higher-level helpers when available.
148
+ """
149
+ payload_idx = self._add_constant(payload)
150
+ self._emit(Opcode.LOAD_CONST, payload_idx)
151
+ name_idx = self._add_constant(builtin_name)
152
+ self._emit(Opcode.CALL_NAME, (name_idx, 1))
153
+ if discard_result:
154
+ self._emit(Opcode.POP)
155
+
142
156
  def _unsupported_message(self, node_type: str) -> str:
143
157
  """Return a friendly message for unsupported nodes."""
144
158
  hints = {
@@ -801,6 +815,38 @@ class BytecodeCompiler:
801
815
  self._emit(Opcode.CALL_NAME, (gc_idx, 1))
802
816
  self._emit(Opcode.POP)
803
817
 
818
+ # ------------------------------------------------------------------
819
+ # Directive-like statements (delegate to VM helpers when available)
820
+ # ------------------------------------------------------------------
821
+
822
+ def _compile_NativeStatement(self, node):
823
+ """Compile native statement via VM helper (if provided)."""
824
+ self._emit_vm_builtin_call("__vm_native_statement__", node, discard_result=False)
825
+
826
+ def _compile_InlineStatement(self, node):
827
+ """Compile inline optimization directive via VM helper."""
828
+ self._emit_vm_builtin_call("__vm_inline_statement__", node, discard_result=False)
829
+
830
+ def _compile_BufferStatement(self, node):
831
+ """Compile buffer directive via VM helper."""
832
+ self._emit_vm_builtin_call("__vm_buffer_statement__", node, discard_result=False)
833
+
834
+ def _compile_SIMDStatement(self, node):
835
+ """Compile SIMD directive via VM helper."""
836
+ self._emit_vm_builtin_call("__vm_simd_statement__", node, discard_result=False)
837
+
838
+ def _compile_DeferStatement(self, node):
839
+ """Compile defer directive via VM helper."""
840
+ self._emit_vm_builtin_call("__vm_defer_statement__", node, discard_result=False)
841
+
842
+ def _compile_EmitStatement(self, node):
843
+ """Compile emit statement via VM helper."""
844
+ self._emit_vm_builtin_call("__vm_emit_statement__", node, discard_result=False)
845
+
846
+ def _compile_ProtocolStatement(self, node):
847
+ """Compile protocol statement via VM helper."""
848
+ self._emit_vm_builtin_call("__vm_protocol_statement__", node, discard_result=False)
849
+
804
850
 
805
851
  def _compile_ContractStatement(self, node):
806
852
  """Compile smart contract definition into bytecode that creates a real
@@ -939,31 +985,45 @@ class BytecodeCompiler:
939
985
  # Push name
940
986
  self._emit(Opcode.LOAD_CONST, name_idx)
941
987
 
942
- # Compile body members
988
+ # Compile properties and methods from EntityStatement's actual attributes
943
989
  member_count = 0
944
- if hasattr(node.body, 'statements'):
945
- for stmt in node.body.statements:
946
- stmt_type = type(stmt).__name__
947
- # Reuse state/action compilation
948
- if stmt_type in ('StateStatement', 'ActionStatement'):
949
- if stmt_type == 'StateStatement':
950
- # Value
951
- if getattr(stmt, 'initial_value', None):
952
- self._compile_node(stmt.initial_value)
953
- else:
954
- self._emit(Opcode.LOAD_CONST, self._add_constant(None))
955
- # Name
956
- self._emit(Opcode.LOAD_CONST, self._add_constant(stmt.name.value))
957
- elif stmt_type == 'ActionStatement':
958
- self._compile_node(stmt)
959
- self._emit(Opcode.LOAD_NAME, self._add_constant(stmt.name.value))
960
- self._emit(Opcode.LOAD_CONST, self._add_constant(stmt.name.value))
961
-
962
- member_count += 1
990
+
991
+ # Compile properties (list of dicts with 'name', 'type', 'default_value')
992
+ for prop in (node.properties or []):
993
+ prop_name = prop.get('name', '') if isinstance(prop, dict) else getattr(prop, 'name', '')
994
+ default_val = prop.get('default_value', None) if isinstance(prop, dict) else getattr(prop, 'default_value', None)
995
+
996
+ if default_val is not None:
997
+ self._compile_node(default_val)
998
+ else:
999
+ self._emit(Opcode.LOAD_CONST, self._add_constant(None))
1000
+ self._emit(Opcode.LOAD_CONST, self._add_constant(prop_name))
1001
+ member_count += 1
1002
+
1003
+ # Compile methods (list of ActionStatement)
1004
+ for method in (node.methods or []):
1005
+ self._compile_node(method)
1006
+ method_name = method.name.value if hasattr(method.name, 'value') else str(method.name)
1007
+ self._emit(Opcode.LOAD_NAME, self._add_constant(method_name))
1008
+ self._emit(Opcode.LOAD_CONST, self._add_constant(method_name))
1009
+ member_count += 1
963
1010
 
964
1011
  self._emit(Opcode.DEFINE_ENTITY, member_count)
965
1012
  self._emit(Opcode.STORE_NAME, name_idx)
966
1013
 
1014
+ def _compile_ExportStatement(self, node):
1015
+ """Compile export statement - marks names for module export.
1016
+
1017
+ Export is a declaration/annotation; the exported names are already
1018
+ defined by their own declarations, so this just emits EXPORT opcodes
1019
+ to register them in the module's export table.
1020
+ """
1021
+ for name_node in (node.names or []):
1022
+ name = name_node.value if hasattr(name_node, 'value') else str(name_node)
1023
+ name_idx = self._add_constant(name)
1024
+ self._emit(Opcode.LOAD_NAME, name_idx)
1025
+ self._emit(Opcode.EXPORT, name_idx)
1026
+
967
1027
  def _compile_DataStatement(self, node):
968
1028
  """Compile Data/Dataclass definition"""
969
1029
  # Data objects are simpler entities