zexus 1.6.2 → 1.6.3

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.
@@ -196,187 +196,208 @@ class FunctionEvaluatorMixin:
196
196
  def apply_function(self, fn, args, env=None):
197
197
  debug_log("apply_function", f"Calling {fn}")
198
198
 
199
- # Phase 2 & 3: Trigger plugin hooks and check capabilities
200
- if hasattr(self, 'integration_context'):
201
- if isinstance(fn, (Action, LambdaFunction)):
202
- func_name = fn.name if hasattr(fn, 'name') else str(fn)
203
- # Trigger before-call hook
204
- self.integration_context.plugins.before_action_call(func_name, {})
205
-
206
- # Check required capabilities
207
- try:
208
- self.integration_context.capabilities.require_capability("core.language")
209
- except PermissionError:
210
- return EvaluationError(f"Permission denied: insufficient capabilities for {func_name}")
211
-
199
+ # Resource limit: Track call depth (Security Fix #7)
200
+ func_name = None
212
201
  if isinstance(fn, (Action, LambdaFunction)):
213
- debug_log(" Calling user-defined function")
214
-
215
- # Check if this is an async action
216
- is_async = getattr(fn, 'is_async', False)
202
+ func_name = fn.name if hasattr(fn, 'name') else str(fn)
203
+ try:
204
+ self.resource_limiter.enter_call(func_name)
205
+ except Exception as e:
206
+ # Convert ResourceError to EvaluationError
207
+ from .resource_limiter import ResourceError, TimeoutError
208
+ if isinstance(e, (ResourceError, TimeoutError)):
209
+ return EvaluationError(str(e))
210
+ raise # Re-raise if not a resource error
211
+
212
+ try:
213
+ # Phase 2 & 3: Trigger plugin hooks and check capabilities
214
+ if hasattr(self, 'integration_context'):
215
+ if isinstance(fn, (Action, LambdaFunction)):
216
+ # Trigger before-call hook
217
+ self.integration_context.plugins.before_action_call(func_name, {})
218
+
219
+ # Check required capabilities
220
+ try:
221
+ self.integration_context.capabilities.require_capability("core.language")
222
+ except PermissionError:
223
+ return EvaluationError(f"Permission denied: insufficient capabilities for {func_name}")
217
224
 
218
- if is_async:
219
- # Create a coroutine that lazily executes the async action
220
- from ..object import Coroutine
221
- import sys
225
+ if isinstance(fn, (Action, LambdaFunction)):
226
+ debug_log(" Calling user-defined function")
222
227
 
223
- # print(f"[ASYNC CREATE] Creating coroutine for async action, fn.env has keys: {list(fn.env.store.keys()) if hasattr(fn.env, 'store') else 'N/A'}", file=sys.stderr)
228
+ # Check if this is an async action
229
+ is_async = getattr(fn, 'is_async', False)
224
230
 
225
- def async_generator():
226
- """Generator that executes the async action body"""
227
- new_env = Environment(outer=fn.env)
228
-
229
- # Bind parameters
230
- for i, param in enumerate(fn.parameters):
231
- if i < len(args):
232
- param_name = param.value if hasattr(param, 'value') else str(param)
233
- new_env.set(param_name, args[i])
231
+ if is_async:
232
+ # Create a coroutine that lazily executes the async action
233
+ from ..object import Coroutine
234
+ import sys
234
235
 
235
- # Yield control first (makes it a true generator)
236
- yield None
236
+ # print(f"[ASYNC CREATE] Creating coroutine for async action, fn.env has keys: {list(fn.env.store.keys()) if hasattr(fn.env, 'store') else 'N/A'}", file=sys.stderr)
237
237
 
238
- try:
239
- # Evaluate the function body
240
- res = self.eval_node(fn.body, new_env)
238
+ def async_generator():
239
+ """Generator that executes the async action body"""
240
+ new_env = Environment(outer=fn.env)
241
241
 
242
- # Unwrap ReturnValue if needed
243
- if isinstance(res, ReturnValue):
244
- result = res.value
245
- else:
246
- result = res
242
+ # Bind parameters
243
+ for i, param in enumerate(fn.parameters):
244
+ if i < len(args):
245
+ param_name = param.value if hasattr(param, 'value') else str(param)
246
+ new_env.set(param_name, args[i])
247
247
 
248
- # Execute deferred cleanup
249
- if hasattr(self, '_execute_deferred_cleanup'):
250
- self._execute_deferred_cleanup(new_env, [])
248
+ # Yield control first (makes it a true generator)
249
+ yield None
251
250
 
252
- # Return the result (will be caught by StopIteration)
253
- return result
254
- except Exception as e:
255
- # Re-raise exception to be caught by coroutine
256
- raise e
257
-
258
- # Create and return coroutine
259
- gen = async_generator()
260
- coroutine = Coroutine(gen, fn)
261
- return coroutine
262
-
263
- # Synchronous function execution
264
- new_env = Environment(outer=fn.env)
265
-
266
- param_names = []
267
- for i, param in enumerate(fn.parameters):
268
- if i < len(args):
269
- # Handle both Identifier objects and strings
270
- param_name = param.value if hasattr(param, 'value') else str(param)
271
- param_names.append(param_name)
272
- new_env.set(param_name, args[i])
273
- # Lightweight debug: show what is being bound
274
- try:
275
- debug_log(" Set parameter", f"{param_name} = {type(args[i]).__name__}")
276
- except Exception:
277
- pass
278
-
279
- try:
280
- if param_names:
281
- debug_log(" Function parameters bound", f"{param_names}")
282
- except Exception:
283
- pass
284
-
285
- try:
286
- res = self.eval_node(fn.body, new_env)
287
- res = _resolve_awaitable(res)
251
+ try:
252
+ # Evaluate the function body
253
+ res = self.eval_node(fn.body, new_env)
254
+
255
+ # Unwrap ReturnValue if needed
256
+ if isinstance(res, ReturnValue):
257
+ result = res.value
258
+ else:
259
+ result = res
260
+
261
+ # Execute deferred cleanup
262
+ if hasattr(self, '_execute_deferred_cleanup'):
263
+ self._execute_deferred_cleanup(new_env, [])
264
+
265
+ # Return the result (will be caught by StopIteration)
266
+ return result
267
+ except Exception as e:
268
+ # Re-raise exception to be caught by coroutine
269
+ raise e
270
+
271
+ # Create and return coroutine
272
+ gen = async_generator()
273
+ coroutine = Coroutine(gen, fn)
274
+ return coroutine
288
275
 
289
- # Unwrap ReturnValue if needed
290
- if isinstance(res, ReturnValue):
291
- result = res.value
292
- else:
293
- result = res
276
+ # Synchronous function execution
277
+ new_env = Environment(outer=fn.env)
294
278
 
295
- return result
296
- finally:
297
- # CRITICAL: Execute deferred cleanup when function exits
298
- # This happens in finally block to ensure cleanup runs even on errors
299
- if hasattr(self, '_execute_deferred_cleanup'):
300
- self._execute_deferred_cleanup(new_env, [])
301
-
302
- # Phase 2: Trigger after-call hook
303
- if hasattr(self, 'integration_context'):
304
- func_name = fn.name if hasattr(fn, 'name') else str(fn)
305
- self.integration_context.plugins.after_action_call(func_name, result)
306
-
307
- elif isinstance(fn, Builtin):
308
- debug_log(" Calling builtin function", f"{fn.name}")
309
- # Sandbox enforcement: if current env is sandboxed, consult policy
310
- try:
311
- in_sandbox = False
312
- policy_name = None
313
- if env is not None:
314
- try:
315
- in_sandbox = bool(env.get('__in_sandbox__'))
316
- policy_name = env.get('__sandbox_policy__')
317
- except Exception:
318
- in_sandbox = False
279
+ param_names = []
280
+ for i, param in enumerate(fn.parameters):
281
+ if i < len(args):
282
+ # Handle both Identifier objects and strings
283
+ param_name = param.value if hasattr(param, 'value') else str(param)
284
+ param_names.append(param_name)
285
+ new_env.set(param_name, args[i])
286
+ # Lightweight debug: show what is being bound
287
+ try:
288
+ debug_log(" Set parameter", f"{param_name} = {type(args[i]).__name__}")
289
+ except Exception:
290
+ pass
319
291
 
320
- if in_sandbox:
321
- from ..security import get_security_context
322
- ctx = get_security_context()
323
- policy = ctx.get_sandbox_policy(policy_name or 'default')
324
- allowed = None if policy is None else policy.get('allowed_builtins')
325
- # If allowed set exists and builtin not in it -> block
326
- if allowed is not None and fn.name not in allowed:
327
- return EvaluationError(f"Builtin '{fn.name}' not allowed inside sandbox policy '{policy_name or 'default'}'")
328
- except Exception:
329
- # If enforcement fails unexpectedly, proceed to call but log nothing
330
- pass
292
+ try:
293
+ if param_names:
294
+ debug_log(" Function parameters bound", f"{param_names}")
295
+ except Exception:
296
+ pass
297
+
298
+ try:
299
+ res = self.eval_node(fn.body, new_env)
300
+ res = _resolve_awaitable(res)
301
+
302
+ # Unwrap ReturnValue if needed
303
+ if isinstance(res, ReturnValue):
304
+ result = res.value
305
+ else:
306
+ result = res
307
+
308
+ return result
309
+ except Exception as e:
310
+ # Store result for after-call hook
311
+ result = EvaluationError(str(e))
312
+ raise
313
+ finally:
314
+ # CRITICAL: Execute deferred cleanup when function exits
315
+ # This happens in finally block to ensure cleanup runs even on errors
316
+ if hasattr(self, '_execute_deferred_cleanup'):
317
+ self._execute_deferred_cleanup(new_env, [])
318
+
319
+ # Phase 2: Trigger after-call hook
320
+ if hasattr(self, 'integration_context'):
321
+ func_name = fn.name if hasattr(fn, 'name') else str(fn)
322
+ self.integration_context.plugins.after_action_call(func_name, result)
323
+
324
+ elif isinstance(fn, Builtin):
325
+ debug_log(" Calling builtin function", f"{fn.name}")
326
+ # Sandbox enforcement: if current env is sandboxed, consult policy
327
+ try:
328
+ in_sandbox = False
329
+ policy_name = None
330
+ if env is not None:
331
+ try:
332
+ in_sandbox = bool(env.get('__in_sandbox__'))
333
+ policy_name = env.get('__sandbox_policy__')
334
+ except Exception:
335
+ in_sandbox = False
331
336
 
332
- try:
333
- res = fn.fn(*args)
334
- return _resolve_awaitable(res)
335
- except Exception as e:
336
- return EvaluationError(f"Builtin error: {str(e)}")
337
-
338
- elif isinstance(fn, EntityDefinition):
339
- debug_log(" Creating entity instance (old format)")
340
- # Entity constructor: Person("Alice", 30)
341
- # Create instance with positional arguments mapped to properties
342
- from ..object import EntityInstance, String, Integer
343
-
344
- values = {}
345
- # Map positional arguments to property names
346
- if isinstance(fn.properties, dict):
347
- prop_names = list(fn.properties.keys())
348
- else:
349
- prop_names = [prop['name'] for prop in fn.properties]
350
-
351
- for i, arg in enumerate(args):
352
- if i < len(prop_names):
353
- values[prop_names[i]] = arg
354
-
355
- return EntityInstance(fn, values)
356
-
357
- # Handle SecurityEntityDefinition (from security.py with methods support)
358
- from ..security import EntityDefinition as SecurityEntityDef, EntityInstance as SecurityEntityInstance
359
- if isinstance(fn, SecurityEntityDef):
360
- debug_log(" Creating entity instance (with methods)")
361
-
362
- values = {}
363
- # Map positional arguments to property names, INCLUDING INHERITED PROPERTIES
364
- # Use get_all_properties() to get the full property list in correct order
365
- if hasattr(fn, 'get_all_properties'):
366
- # Get all properties (parent + child) in correct order
367
- all_props = fn.get_all_properties()
368
- prop_names = list(all_props.keys())
369
- else:
370
- # Fallback for old-style properties
371
- prop_names = list(fn.properties.keys()) if isinstance(fn.properties, dict) else [prop['name'] for prop in fn.properties]
337
+ if in_sandbox:
338
+ from ..security import get_security_context
339
+ ctx = get_security_context()
340
+ policy = ctx.get_sandbox_policy(policy_name or 'default')
341
+ allowed = None if policy is None else policy.get('allowed_builtins')
342
+ # If allowed set exists and builtin not in it -> block
343
+ if allowed is not None and fn.name not in allowed:
344
+ return EvaluationError(f"Builtin '{fn.name}' not allowed inside sandbox policy '{policy_name or 'default'}'")
345
+ except Exception:
346
+ # If enforcement fails unexpectedly, proceed to call but log nothing
347
+ pass
348
+
349
+ try:
350
+ res = fn.fn(*args)
351
+ return _resolve_awaitable(res)
352
+ except Exception as e:
353
+ return EvaluationError(f"Builtin error: {str(e)}")
372
354
 
373
- for i, arg in enumerate(args):
374
- if i < len(prop_names):
375
- values[prop_names[i]] = arg
355
+ elif isinstance(fn, EntityDefinition):
356
+ debug_log(" Creating entity instance (old format)")
357
+ # Entity constructor: Person("Alice", 30)
358
+ # Create instance with positional arguments mapped to properties
359
+ from ..object import EntityInstance, String, Integer
360
+
361
+ values = {}
362
+ # Map positional arguments to property names
363
+ if isinstance(fn.properties, dict):
364
+ prop_names = list(fn.properties.keys())
365
+ else:
366
+ prop_names = [prop['name'] for prop in fn.properties]
367
+
368
+ for i, arg in enumerate(args):
369
+ if i < len(prop_names):
370
+ values[prop_names[i]] = arg
371
+
372
+ return EntityInstance(fn, values)
376
373
 
377
- debug_log(f" Entity instance created with {len(values)} properties: {list(values.keys())}")
378
- # Use create_instance to handle dependency injection
379
- return fn.create_instance(values)
374
+ # Handle SecurityEntityDefinition (from security.py with methods support)
375
+ from ..security import EntityDefinition as SecurityEntityDef, EntityInstance as SecurityEntityInstance
376
+ if isinstance(fn, SecurityEntityDef):
377
+ debug_log(" Creating entity instance (with methods)")
378
+
379
+ values = {}
380
+ # Map positional arguments to property names, INCLUDING INHERITED PROPERTIES
381
+ # Use get_all_properties() to get the full property list in correct order
382
+ if hasattr(fn, 'get_all_properties'):
383
+ # Get all properties (parent + child) in correct order
384
+ all_props = fn.get_all_properties()
385
+ prop_names = list(all_props.keys())
386
+ else:
387
+ # Fallback for old-style properties
388
+ prop_names = list(fn.properties.keys()) if isinstance(fn.properties, dict) else [prop['name'] for prop in fn.properties]
389
+
390
+ for i, arg in enumerate(args):
391
+ if i < len(prop_names):
392
+ values[prop_names[i]] = arg
393
+
394
+ debug_log(f" Entity instance created with {len(values)} properties: {list(values.keys())}")
395
+ # Use create_instance to handle dependency injection
396
+ return fn.create_instance(values)
397
+ finally:
398
+ # Resource limit: Exit call depth tracking (Security Fix #7)
399
+ if isinstance(fn, (Action, LambdaFunction)):
400
+ self.resource_limiter.exit_call()
380
401
 
381
402
  return EvaluationError(f"Not a function: {fn}")
382
403
 
@@ -651,6 +672,112 @@ class FunctionEvaluatorMixin:
651
672
  return EvaluationError("sqrt() takes exactly 1 argument")
652
673
  return Math.sqrt(a[0])
653
674
 
675
+ # User Input (SECURITY: Returns untrusted strings)
676
+ def _input(*a):
677
+ """Read user input from stdin - automatically marked as untrusted"""
678
+ prompt = ""
679
+ if len(a) == 1:
680
+ if isinstance(a[0], String):
681
+ prompt = a[0].value
682
+ else:
683
+ prompt = str(a[0].inspect() if hasattr(a[0], 'inspect') else a[0])
684
+ elif len(a) > 1:
685
+ return EvaluationError("input() takes 0 or 1 argument (optional prompt)")
686
+
687
+ try:
688
+ user_input = input(prompt)
689
+ # SECURITY: User input is ALWAYS untrusted - external data source
690
+ return String(user_input, is_trusted=False)
691
+ except Exception as e:
692
+ return EvaluationError(f"input() error: {str(e)}")
693
+
694
+ # Cryptographic Functions (SECURITY FIX #5)
695
+ def _hash_password(*a):
696
+ """
697
+ Hash password using bcrypt (secure, industry-standard)
698
+ Usage: hash_password(password) -> hashed_string
699
+ """
700
+ if len(a) != 1:
701
+ return EvaluationError("hash_password() takes exactly 1 argument")
702
+
703
+ password = a[0].value if isinstance(a[0], String) else str(a[0])
704
+
705
+ try:
706
+ import bcrypt
707
+ # Generate salt and hash password
708
+ salt = bcrypt.gensalt()
709
+ hashed = bcrypt.hashpw(password.encode('utf-8'), salt)
710
+ # Return as trusted string (hash, not user input)
711
+ return String(hashed.decode('utf-8'), is_trusted=True)
712
+ except ImportError:
713
+ return EvaluationError("hash_password() requires bcrypt library. Install: pip install bcrypt")
714
+ except Exception as e:
715
+ return EvaluationError(f"hash_password() error: {str(e)}")
716
+
717
+ def _verify_password(*a):
718
+ """
719
+ Verify password against bcrypt hash (constant-time comparison)
720
+ Usage: verify_password(password, hash) -> boolean
721
+ """
722
+ if len(a) != 2:
723
+ return EvaluationError("verify_password() takes exactly 2 arguments: password, hash")
724
+
725
+ password = a[0].value if isinstance(a[0], String) else str(a[0])
726
+ password_hash = a[1].value if isinstance(a[1], String) else str(a[1])
727
+
728
+ try:
729
+ import bcrypt
730
+ # Constant-time comparison via bcrypt
731
+ result = bcrypt.checkpw(password.encode('utf-8'), password_hash.encode('utf-8'))
732
+ return BooleanObj(result)
733
+ except ImportError:
734
+ return EvaluationError("verify_password() requires bcrypt library. Install: pip install bcrypt")
735
+ except Exception as e:
736
+ return EvaluationError(f"verify_password() error: {str(e)}")
737
+
738
+ def _crypto_random(*a):
739
+ """
740
+ Generate cryptographically secure random string
741
+ Usage: crypto_random(length?) -> hex_string (default 32 bytes = 64 hex chars)
742
+ """
743
+ length = 32 # Default: 32 bytes
744
+ if len(a) >= 1:
745
+ if isinstance(a[0], Integer):
746
+ length = a[0].value
747
+ else:
748
+ return EvaluationError("crypto_random() length must be an integer")
749
+
750
+ if len(a) > 1:
751
+ return EvaluationError("crypto_random() takes 0 or 1 argument (optional length)")
752
+
753
+ try:
754
+ import secrets
755
+ # Generate cryptographically secure random hex string
756
+ random_hex = secrets.token_hex(length)
757
+ # Return as trusted string (generated, not user input)
758
+ return String(random_hex, is_trusted=True)
759
+ except Exception as e:
760
+ return EvaluationError(f"crypto_random() error: {str(e)}")
761
+
762
+ def _constant_time_compare(*a):
763
+ """
764
+ Constant-time string comparison (timing-attack resistant)
765
+ Usage: constant_time_compare(a, b) -> boolean
766
+ """
767
+ if len(a) != 2:
768
+ return EvaluationError("constant_time_compare() takes exactly 2 arguments")
769
+
770
+ str_a = a[0].value if isinstance(a[0], String) else str(a[0])
771
+ str_b = a[1].value if isinstance(a[1], String) else str(a[1])
772
+
773
+ try:
774
+ import secrets
775
+ # Use secrets.compare_digest for constant-time comparison
776
+ result = secrets.compare_digest(str_a, str_b)
777
+ return BooleanObj(result)
778
+ except Exception as e:
779
+ return EvaluationError(f"constant_time_compare() error: {str(e)}")
780
+
654
781
  # File I/O
655
782
  def _read_text(*a):
656
783
  if len(a) != 1 or not isinstance(a[0], String):
@@ -1531,7 +1658,8 @@ class FunctionEvaluatorMixin:
1531
1658
  try:
1532
1659
  from ..stdlib.http import HttpModule
1533
1660
  result = HttpModule.get(url, headers, timeout)
1534
- return _python_to_zexus(result)
1661
+ # HTTP responses are external data - mark as untrusted
1662
+ return _python_to_zexus(result, mark_untrusted=True)
1535
1663
  except Exception as e:
1536
1664
  return EvaluationError(f"HTTP GET error: {str(e)}")
1537
1665
 
@@ -1558,7 +1686,8 @@ class FunctionEvaluatorMixin:
1558
1686
  # Determine if data should be sent as JSON
1559
1687
  json_mode = isinstance(a[1], (Map, List))
1560
1688
  result = HttpModule.post(url, data, headers, json=json_mode, timeout=timeout)
1561
- return _python_to_zexus(result)
1689
+ # HTTP responses are external data - mark as untrusted
1690
+ return _python_to_zexus(result, mark_untrusted=True)
1562
1691
  except Exception as e:
1563
1692
  return EvaluationError(f"HTTP POST error: {str(e)}")
1564
1693
 
@@ -1582,7 +1711,8 @@ class FunctionEvaluatorMixin:
1582
1711
  from ..stdlib.http import HttpModule
1583
1712
  json_mode = isinstance(a[1], (Map, List))
1584
1713
  result = HttpModule.put(url, data, headers, json=json_mode, timeout=timeout)
1585
- return _python_to_zexus(result)
1714
+ # HTTP responses are external data - mark as untrusted
1715
+ return _python_to_zexus(result, mark_untrusted=True)
1586
1716
  except Exception as e:
1587
1717
  return EvaluationError(f"HTTP PUT error: {str(e)}")
1588
1718
 
@@ -1604,7 +1734,8 @@ class FunctionEvaluatorMixin:
1604
1734
  try:
1605
1735
  from ..stdlib.http import HttpModule
1606
1736
  result = HttpModule.delete(url, headers, timeout)
1607
- return _python_to_zexus(result)
1737
+ # HTTP responses are external data - mark as untrusted
1738
+ return _python_to_zexus(result, mark_untrusted=True)
1608
1739
  except Exception as e:
1609
1740
  return EvaluationError(f"HTTP DELETE error: {str(e)}")
1610
1741
 
@@ -2095,6 +2226,59 @@ class FunctionEvaluatorMixin:
2095
2226
  else:
2096
2227
  return EvaluationError(f"Unsupported language: {language}")
2097
2228
 
2229
+ # Contract Assertions
2230
+ def _require(*a):
2231
+ """Assert a condition in smart contracts: require(condition, message)
2232
+
2233
+ Throws an error if condition is false. Essential for contract validation.
2234
+
2235
+ Example:
2236
+ require(balance >= amount, "Insufficient balance")
2237
+ require(sender == owner, "Not authorized")
2238
+ require(value > 0, "Amount must be positive")
2239
+ """
2240
+ if len(a) < 1 or len(a) > 2:
2241
+ return EvaluationError("require() takes 1-2 arguments: require(condition, [message])")
2242
+
2243
+ condition = a[0]
2244
+ message = a[1].value if len(a) > 1 and isinstance(a[1], String) else "Requirement failed"
2245
+
2246
+ # Check if condition is truthy
2247
+ from .utils import is_truthy
2248
+ if not is_truthy(condition):
2249
+ # Return error with contract-specific formatting
2250
+ return EvaluationError(f"Contract requirement failed: {message}")
2251
+
2252
+ # Condition passed, return NULL
2253
+ return NULL
2254
+
2255
+ # Contract Assertions
2256
+ def _require(*a):
2257
+ """Assert a condition in smart contracts: require(condition, message)
2258
+
2259
+ Throws an error if condition is false. Essential for contract validation.
2260
+ Note: This is a fallback for contexts where the require statement isn't available.
2261
+
2262
+ Example:
2263
+ require(balance >= amount, "Insufficient balance")
2264
+ require(sender == owner, "Not authorized")
2265
+ require(value > 0, "Amount must be positive")
2266
+ """
2267
+ if len(a) < 1 or len(a) > 2:
2268
+ return EvaluationError("require() takes 1-2 arguments: require(condition, [message])")
2269
+
2270
+ condition = a[0]
2271
+ message = a[1].value if len(a) > 1 and isinstance(a[1], String) else "Requirement failed"
2272
+
2273
+ # Check if condition is truthy
2274
+ from .utils import is_truthy
2275
+ if not is_truthy(condition):
2276
+ # Return error with contract-specific formatting
2277
+ return EvaluationError(f"Contract requirement failed: {message}")
2278
+
2279
+ # Condition passed, return NULL
2280
+ return NULL
2281
+
2098
2282
  # Register mappings
2099
2283
  self.builtins.update({
2100
2284
  "now": Builtin(_now, "now"),
@@ -2103,6 +2287,13 @@ class FunctionEvaluatorMixin:
2103
2287
  "to_hex": Builtin(_to_hex, "to_hex"),
2104
2288
  "from_hex": Builtin(_from_hex, "from_hex"),
2105
2289
  "sqrt": Builtin(_sqrt, "sqrt"),
2290
+ "require": Builtin(_require, "require"),
2291
+ "require": Builtin(_require, "require"),
2292
+ "input": Builtin(_input, "input"),
2293
+ "hash_password": Builtin(_hash_password, "hash_password"),
2294
+ "verify_password": Builtin(_verify_password, "verify_password"),
2295
+ "crypto_random": Builtin(_crypto_random, "crypto_random"),
2296
+ "constant_time_compare": Builtin(_constant_time_compare, "constant_time_compare"),
2106
2297
  "file": Builtin(_file, "file"),
2107
2298
  "file_read_text": Builtin(_read_text, "file_read_text"),
2108
2299
  "file_write_text": Builtin(_write_text, "file_write_text"),
@@ -2142,6 +2333,7 @@ class FunctionEvaluatorMixin:
2142
2333
  "random": Builtin(_random, "random"),
2143
2334
  "persist_set": Builtin(_persist_set, "persist_set"),
2144
2335
  "persist_get": Builtin(_persist_get, "persist_get"),
2336
+ "input": Builtin(_input, "input"),
2145
2337
  "len": Builtin(_len, "len"),
2146
2338
  "type": Builtin(_type, "type"),
2147
2339
  "first": Builtin(_first, "first"),
@@ -2154,6 +2346,9 @@ class FunctionEvaluatorMixin:
2154
2346
  "filter": Builtin(_filter, "filter"),
2155
2347
  })
2156
2348
 
2349
+ # Register access control builtins
2350
+ self._register_access_control_builtins()
2351
+
2157
2352
  # Register concurrency builtins
2158
2353
  self._register_concurrency_builtins()
2159
2354
 
@@ -2512,6 +2707,227 @@ class FunctionEvaluatorMixin:
2512
2707
  "barrier_reset": Builtin(_barrier_reset, "barrier_reset"),
2513
2708
  })
2514
2709
 
2710
+ def _register_access_control_builtins(self):
2711
+ """Register access control functions for contracts"""
2712
+ from ..access_control_system import get_access_control
2713
+ from ..blockchain.transaction import get_current_tx
2714
+
2715
+ def _set_owner(*a):
2716
+ """Set owner of current contract: set_owner(contract_id, owner_address)"""
2717
+ if len(a) != 2:
2718
+ return EvaluationError("set_owner() requires 2 arguments: contract_id, owner_address")
2719
+
2720
+ contract_id = a[0].value if hasattr(a[0], 'value') else str(a[0])
2721
+ owner = a[1].value if hasattr(a[1], 'value') else str(a[1])
2722
+
2723
+ ac = get_access_control()
2724
+ ac.set_owner(contract_id, owner)
2725
+ return NULL
2726
+
2727
+ def _get_owner(*a):
2728
+ """Get owner of contract: get_owner(contract_id)"""
2729
+ if len(a) != 1:
2730
+ return EvaluationError("get_owner() requires 1 argument: contract_id")
2731
+
2732
+ contract_id = a[0].value if hasattr(a[0], 'value') else str(a[0])
2733
+
2734
+ ac = get_access_control()
2735
+ owner = ac.get_owner(contract_id)
2736
+ return String(owner) if owner else NULL
2737
+
2738
+ def _is_owner(*a):
2739
+ """Check if address is owner: is_owner(contract_id, address)"""
2740
+ if len(a) != 2:
2741
+ return EvaluationError("is_owner() requires 2 arguments: contract_id, address")
2742
+
2743
+ contract_id = a[0].value if hasattr(a[0], 'value') else str(a[0])
2744
+ address = a[1].value if hasattr(a[1], 'value') else str(a[1])
2745
+
2746
+ ac = get_access_control()
2747
+ return TRUE if ac.is_owner(contract_id, address) else FALSE
2748
+
2749
+ def _grant_role(*a):
2750
+ """Grant role to address: grant_role(contract_id, address, role)"""
2751
+ if len(a) != 3:
2752
+ return EvaluationError("grant_role() requires 3 arguments: contract_id, address, role")
2753
+
2754
+ contract_id = a[0].value if hasattr(a[0], 'value') else str(a[0])
2755
+ address = a[1].value if hasattr(a[1], 'value') else str(a[1])
2756
+ role = a[2].value if hasattr(a[2], 'value') else str(a[2])
2757
+
2758
+ ac = get_access_control()
2759
+ ac.grant_role(contract_id, address, role)
2760
+ return NULL
2761
+
2762
+ def _revoke_role(*a):
2763
+ """Revoke role from address: revoke_role(contract_id, address, role)"""
2764
+ if len(a) != 3:
2765
+ return EvaluationError("revoke_role() requires 3 arguments: contract_id, address, role")
2766
+
2767
+ contract_id = a[0].value if hasattr(a[0], 'value') else str(a[0])
2768
+ address = a[1].value if hasattr(a[1], 'value') else str(a[1])
2769
+ role = a[2].value if hasattr(a[2], 'value') else str(a[2])
2770
+
2771
+ ac = get_access_control()
2772
+ ac.revoke_role(contract_id, address, role)
2773
+ return NULL
2774
+
2775
+ def _has_role(*a):
2776
+ """Check if address has role: has_role(contract_id, address, role)"""
2777
+ if len(a) != 3:
2778
+ return EvaluationError("has_role() requires 3 arguments: contract_id, address, role")
2779
+
2780
+ contract_id = a[0].value if hasattr(a[0], 'value') else str(a[0])
2781
+ address = a[1].value if hasattr(a[1], 'value') else str(a[1])
2782
+ role = a[2].value if hasattr(a[2], 'value') else str(a[2])
2783
+
2784
+ ac = get_access_control()
2785
+ return TRUE if ac.has_role(contract_id, address, role) else FALSE
2786
+
2787
+ def _get_roles(*a):
2788
+ """Get all roles for address: get_roles(contract_id, address)"""
2789
+ if len(a) != 2:
2790
+ return EvaluationError("get_roles() requires 2 arguments: contract_id, address")
2791
+
2792
+ contract_id = a[0].value if hasattr(a[0], 'value') else str(a[0])
2793
+ address = a[1].value if hasattr(a[1], 'value') else str(a[1])
2794
+
2795
+ ac = get_access_control()
2796
+ roles = ac.get_roles(contract_id, address)
2797
+ return List([String(role) for role in roles])
2798
+
2799
+ def _grant_permission(*a):
2800
+ """Grant permission to address: grant_permission(contract_id, address, permission)"""
2801
+ if len(a) != 3:
2802
+ return EvaluationError("grant_permission() requires 3 arguments: contract_id, address, permission")
2803
+
2804
+ contract_id = a[0].value if hasattr(a[0], 'value') else str(a[0])
2805
+ address = a[1].value if hasattr(a[1], 'value') else str(a[1])
2806
+ permission = a[2].value if hasattr(a[2], 'value') else str(a[2])
2807
+
2808
+ ac = get_access_control()
2809
+ ac.grant_permission(contract_id, address, permission)
2810
+ return NULL
2811
+
2812
+ def _revoke_permission(*a):
2813
+ """Revoke permission from address: revoke_permission(contract_id, address, permission)"""
2814
+ if len(a) != 3:
2815
+ return EvaluationError("revoke_permission() requires 3 arguments: contract_id, address, permission")
2816
+
2817
+ contract_id = a[0].value if hasattr(a[0], 'value') else str(a[0])
2818
+ address = a[1].value if hasattr(a[1], 'value') else str(a[1])
2819
+ permission = a[2].value if hasattr(a[2], 'value') else str(a[2])
2820
+
2821
+ ac = get_access_control()
2822
+ ac.revoke_permission(contract_id, address, permission)
2823
+ return NULL
2824
+
2825
+ def _has_permission(*a):
2826
+ """Check if address has permission: has_permission(contract_id, address, permission)"""
2827
+ if len(a) != 3:
2828
+ return EvaluationError("has_permission() requires 3 arguments: contract_id, address, permission")
2829
+
2830
+ contract_id = a[0].value if hasattr(a[0], 'value') else str(a[0])
2831
+ address = a[1].value if hasattr(a[1], 'value') else str(a[1])
2832
+ permission = a[2].value if hasattr(a[2], 'value') else str(a[2])
2833
+
2834
+ ac = get_access_control()
2835
+ return TRUE if ac.has_permission(contract_id, address, permission) else FALSE
2836
+
2837
+ def _require_owner(*a):
2838
+ """Require caller is owner: require_owner(contract_id, message?)"""
2839
+ if len(a) < 1 or len(a) > 2:
2840
+ return EvaluationError("require_owner() requires 1 or 2 arguments: contract_id, [message]")
2841
+
2842
+ contract_id = a[0].value if hasattr(a[0], 'value') else str(a[0])
2843
+ message = a[1].value if len(a) > 1 and hasattr(a[1], 'value') else None
2844
+
2845
+ # Get current transaction caller
2846
+ tx = get_current_tx()
2847
+ if not tx:
2848
+ return EvaluationError("require_owner() requires transaction context (TX.caller)")
2849
+
2850
+ caller = tx.caller
2851
+
2852
+ ac = get_access_control()
2853
+ try:
2854
+ if message:
2855
+ ac.require_owner(contract_id, caller, message)
2856
+ else:
2857
+ ac.require_owner(contract_id, caller)
2858
+ return NULL
2859
+ except Exception as e:
2860
+ return EvaluationError(str(e))
2861
+
2862
+ def _require_role(*a):
2863
+ """Require caller has role: require_role(contract_id, role, message?)"""
2864
+ if len(a) < 2 or len(a) > 3:
2865
+ return EvaluationError("require_role() requires 2 or 3 arguments: contract_id, role, [message]")
2866
+
2867
+ contract_id = a[0].value if hasattr(a[0], 'value') else str(a[0])
2868
+ role = a[1].value if hasattr(a[1], 'value') else str(a[1])
2869
+ message = a[2].value if len(a) > 2 and hasattr(a[2], 'value') else None
2870
+
2871
+ # Get current transaction caller
2872
+ tx = get_current_tx()
2873
+ if not tx:
2874
+ return EvaluationError("require_role() requires transaction context (TX.caller)")
2875
+
2876
+ caller = tx.caller
2877
+
2878
+ ac = get_access_control()
2879
+ try:
2880
+ if message:
2881
+ ac.require_role(contract_id, caller, role, message)
2882
+ else:
2883
+ ac.require_role(contract_id, caller, role)
2884
+ return NULL
2885
+ except Exception as e:
2886
+ return EvaluationError(str(e))
2887
+
2888
+ def _require_permission(*a):
2889
+ """Require caller has permission: require_permission(contract_id, permission, message?)"""
2890
+ if len(a) < 2 or len(a) > 3:
2891
+ return EvaluationError("require_permission() requires 2 or 3 arguments: contract_id, permission, [message]")
2892
+
2893
+ contract_id = a[0].value if hasattr(a[0], 'value') else str(a[0])
2894
+ permission = a[1].value if hasattr(a[1], 'value') else str(a[1])
2895
+ message = a[2].value if len(a) > 2 and hasattr(a[2], 'value') else None
2896
+
2897
+ # Get current transaction caller
2898
+ tx = get_current_tx()
2899
+ if not tx:
2900
+ return EvaluationError("require_permission() requires transaction context (TX.caller)")
2901
+
2902
+ caller = tx.caller
2903
+
2904
+ ac = get_access_control()
2905
+ try:
2906
+ if message:
2907
+ ac.require_permission(contract_id, caller, permission, message)
2908
+ else:
2909
+ ac.require_permission(contract_id, caller, permission)
2910
+ return NULL
2911
+ except Exception as e:
2912
+ return EvaluationError(str(e))
2913
+
2914
+ # Register access control builtins
2915
+ self.builtins.update({
2916
+ "set_owner": Builtin(_set_owner, "set_owner"),
2917
+ "get_owner": Builtin(_get_owner, "get_owner"),
2918
+ "is_owner": Builtin(_is_owner, "is_owner"),
2919
+ "grant_role": Builtin(_grant_role, "grant_role"),
2920
+ "revoke_role": Builtin(_revoke_role, "revoke_role"),
2921
+ "has_role": Builtin(_has_role, "has_role"),
2922
+ "get_roles": Builtin(_get_roles, "get_roles"),
2923
+ "grant_permission": Builtin(_grant_permission, "grant_permission"),
2924
+ "revoke_permission": Builtin(_revoke_permission, "revoke_permission"),
2925
+ "has_permission": Builtin(_has_permission, "has_permission"),
2926
+ "require_owner": Builtin(_require_owner, "require_owner"),
2927
+ "require_role": Builtin(_require_role, "require_role"),
2928
+ "require_permission": Builtin(_require_permission, "require_permission"),
2929
+ })
2930
+
2515
2931
  def _register_blockchain_builtins(self):
2516
2932
  """Register blockchain cryptographic and utility functions"""
2517
2933
  from ..blockchain.crypto import CryptoPlugin