zexus 1.6.2 → 1.6.4

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,233 @@ 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)
231
+ if is_async:
232
+ # Create a coroutine that lazily executes the async action
233
+ from ..object import Coroutine
234
+ import sys
228
235
 
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])
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)
234
237
 
235
- # Yield control first (makes it a true generator)
236
- yield None
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) or Person{name: "Alice", age: 30}
358
+ # Create instance with positional arguments mapped to properties
359
+ from ..object import EntityInstance, String, Integer
360
+
361
+ values = {}
362
+
363
+ # Special case: If single argument is a Map, use it as field values
364
+ # This handles Entity{field: value} syntax which becomes Entity(Map{...})
365
+ if len(args) == 1 and isinstance(args[0], Map):
366
+ debug_log(" Single Map argument detected - using as field values")
367
+ map_arg = args[0]
368
+ # Extract key-value pairs from the Map
369
+ for key, value in map_arg.pairs.items():
370
+ # Convert key to string if it's a String object
371
+ key_str = key.value if isinstance(key, String) else str(key)
372
+ values[key_str] = value
373
+ else:
374
+ # Map positional arguments to property names
375
+ if isinstance(fn.properties, dict):
376
+ prop_names = list(fn.properties.keys())
377
+ else:
378
+ prop_names = [prop['name'] for prop in fn.properties]
379
+
380
+ for i, arg in enumerate(args):
381
+ if i < len(prop_names):
382
+ values[prop_names[i]] = arg
383
+
384
+ return EntityInstance(fn, values)
376
385
 
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)
386
+ # Handle SecurityEntityDefinition (from security.py with methods support)
387
+ from ..security import EntityDefinition as SecurityEntityDef, EntityInstance as SecurityEntityInstance
388
+ from ..object import String
389
+ if isinstance(fn, SecurityEntityDef):
390
+ debug_log(" Creating entity instance (with methods)")
391
+
392
+ values = {}
393
+
394
+ # Special case: If single argument is a Map, use it as field values
395
+ # This handles Entity{field: value} syntax which becomes Entity(Map{...})
396
+ if len(args) == 1 and isinstance(args[0], Map):
397
+ debug_log(" Single Map argument detected - using as field values")
398
+ map_arg = args[0]
399
+ # Extract key-value pairs from the Map
400
+ for key, value in map_arg.pairs.items():
401
+ # Convert key to string if it's a String object
402
+ key_str = key.value if isinstance(key, String) else str(key)
403
+ values[key_str] = value
404
+ else:
405
+ # Map positional arguments to property names, INCLUDING INHERITED PROPERTIES
406
+ # Use get_all_properties() to get the full property list in correct order
407
+ if hasattr(fn, 'get_all_properties'):
408
+ # Get all properties (parent + child) in correct order
409
+ all_props = fn.get_all_properties()
410
+ prop_names = list(all_props.keys())
411
+ else:
412
+ # Fallback for old-style properties
413
+ prop_names = list(fn.properties.keys()) if isinstance(fn.properties, dict) else [prop['name'] for prop in fn.properties]
414
+
415
+ for i, arg in enumerate(args):
416
+ if i < len(prop_names):
417
+ values[prop_names[i]] = arg
418
+
419
+ debug_log(f" Entity instance created with {len(values)} properties: {list(values.keys())}")
420
+ # Use create_instance to handle dependency injection
421
+ return fn.create_instance(values)
422
+ finally:
423
+ # Resource limit: Exit call depth tracking (Security Fix #7)
424
+ if isinstance(fn, (Action, LambdaFunction)):
425
+ self.resource_limiter.exit_call()
380
426
 
381
427
  return EvaluationError(f"Not a function: {fn}")
382
428
 
@@ -651,6 +697,112 @@ class FunctionEvaluatorMixin:
651
697
  return EvaluationError("sqrt() takes exactly 1 argument")
652
698
  return Math.sqrt(a[0])
653
699
 
700
+ # User Input (SECURITY: Returns untrusted strings)
701
+ def _input(*a):
702
+ """Read user input from stdin - automatically marked as untrusted"""
703
+ prompt = ""
704
+ if len(a) == 1:
705
+ if isinstance(a[0], String):
706
+ prompt = a[0].value
707
+ else:
708
+ prompt = str(a[0].inspect() if hasattr(a[0], 'inspect') else a[0])
709
+ elif len(a) > 1:
710
+ return EvaluationError("input() takes 0 or 1 argument (optional prompt)")
711
+
712
+ try:
713
+ user_input = input(prompt)
714
+ # SECURITY: User input is ALWAYS untrusted - external data source
715
+ return String(user_input, is_trusted=False)
716
+ except Exception as e:
717
+ return EvaluationError(f"input() error: {str(e)}")
718
+
719
+ # Cryptographic Functions (SECURITY FIX #5)
720
+ def _hash_password(*a):
721
+ """
722
+ Hash password using bcrypt (secure, industry-standard)
723
+ Usage: hash_password(password) -> hashed_string
724
+ """
725
+ if len(a) != 1:
726
+ return EvaluationError("hash_password() takes exactly 1 argument")
727
+
728
+ password = a[0].value if isinstance(a[0], String) else str(a[0])
729
+
730
+ try:
731
+ import bcrypt
732
+ # Generate salt and hash password
733
+ salt = bcrypt.gensalt()
734
+ hashed = bcrypt.hashpw(password.encode('utf-8'), salt)
735
+ # Return as trusted string (hash, not user input)
736
+ return String(hashed.decode('utf-8'), is_trusted=True)
737
+ except ImportError:
738
+ return EvaluationError("hash_password() requires bcrypt library. Install: pip install bcrypt")
739
+ except Exception as e:
740
+ return EvaluationError(f"hash_password() error: {str(e)}")
741
+
742
+ def _verify_password(*a):
743
+ """
744
+ Verify password against bcrypt hash (constant-time comparison)
745
+ Usage: verify_password(password, hash) -> boolean
746
+ """
747
+ if len(a) != 2:
748
+ return EvaluationError("verify_password() takes exactly 2 arguments: password, hash")
749
+
750
+ password = a[0].value if isinstance(a[0], String) else str(a[0])
751
+ password_hash = a[1].value if isinstance(a[1], String) else str(a[1])
752
+
753
+ try:
754
+ import bcrypt
755
+ # Constant-time comparison via bcrypt
756
+ result = bcrypt.checkpw(password.encode('utf-8'), password_hash.encode('utf-8'))
757
+ return BooleanObj(result)
758
+ except ImportError:
759
+ return EvaluationError("verify_password() requires bcrypt library. Install: pip install bcrypt")
760
+ except Exception as e:
761
+ return EvaluationError(f"verify_password() error: {str(e)}")
762
+
763
+ def _crypto_random(*a):
764
+ """
765
+ Generate cryptographically secure random string
766
+ Usage: crypto_random(length?) -> hex_string (default 32 bytes = 64 hex chars)
767
+ """
768
+ length = 32 # Default: 32 bytes
769
+ if len(a) >= 1:
770
+ if isinstance(a[0], Integer):
771
+ length = a[0].value
772
+ else:
773
+ return EvaluationError("crypto_random() length must be an integer")
774
+
775
+ if len(a) > 1:
776
+ return EvaluationError("crypto_random() takes 0 or 1 argument (optional length)")
777
+
778
+ try:
779
+ import secrets
780
+ # Generate cryptographically secure random hex string
781
+ random_hex = secrets.token_hex(length)
782
+ # Return as trusted string (generated, not user input)
783
+ return String(random_hex, is_trusted=True)
784
+ except Exception as e:
785
+ return EvaluationError(f"crypto_random() error: {str(e)}")
786
+
787
+ def _constant_time_compare(*a):
788
+ """
789
+ Constant-time string comparison (timing-attack resistant)
790
+ Usage: constant_time_compare(a, b) -> boolean
791
+ """
792
+ if len(a) != 2:
793
+ return EvaluationError("constant_time_compare() takes exactly 2 arguments")
794
+
795
+ str_a = a[0].value if isinstance(a[0], String) else str(a[0])
796
+ str_b = a[1].value if isinstance(a[1], String) else str(a[1])
797
+
798
+ try:
799
+ import secrets
800
+ # Use secrets.compare_digest for constant-time comparison
801
+ result = secrets.compare_digest(str_a, str_b)
802
+ return BooleanObj(result)
803
+ except Exception as e:
804
+ return EvaluationError(f"constant_time_compare() error: {str(e)}")
805
+
654
806
  # File I/O
655
807
  def _read_text(*a):
656
808
  if len(a) != 1 or not isinstance(a[0], String):
@@ -1531,7 +1683,8 @@ class FunctionEvaluatorMixin:
1531
1683
  try:
1532
1684
  from ..stdlib.http import HttpModule
1533
1685
  result = HttpModule.get(url, headers, timeout)
1534
- return _python_to_zexus(result)
1686
+ # HTTP responses are external data - mark as untrusted
1687
+ return _python_to_zexus(result, mark_untrusted=True)
1535
1688
  except Exception as e:
1536
1689
  return EvaluationError(f"HTTP GET error: {str(e)}")
1537
1690
 
@@ -1558,7 +1711,8 @@ class FunctionEvaluatorMixin:
1558
1711
  # Determine if data should be sent as JSON
1559
1712
  json_mode = isinstance(a[1], (Map, List))
1560
1713
  result = HttpModule.post(url, data, headers, json=json_mode, timeout=timeout)
1561
- return _python_to_zexus(result)
1714
+ # HTTP responses are external data - mark as untrusted
1715
+ return _python_to_zexus(result, mark_untrusted=True)
1562
1716
  except Exception as e:
1563
1717
  return EvaluationError(f"HTTP POST error: {str(e)}")
1564
1718
 
@@ -1582,7 +1736,8 @@ class FunctionEvaluatorMixin:
1582
1736
  from ..stdlib.http import HttpModule
1583
1737
  json_mode = isinstance(a[1], (Map, List))
1584
1738
  result = HttpModule.put(url, data, headers, json=json_mode, timeout=timeout)
1585
- return _python_to_zexus(result)
1739
+ # HTTP responses are external data - mark as untrusted
1740
+ return _python_to_zexus(result, mark_untrusted=True)
1586
1741
  except Exception as e:
1587
1742
  return EvaluationError(f"HTTP PUT error: {str(e)}")
1588
1743
 
@@ -1604,7 +1759,8 @@ class FunctionEvaluatorMixin:
1604
1759
  try:
1605
1760
  from ..stdlib.http import HttpModule
1606
1761
  result = HttpModule.delete(url, headers, timeout)
1607
- return _python_to_zexus(result)
1762
+ # HTTP responses are external data - mark as untrusted
1763
+ return _python_to_zexus(result, mark_untrusted=True)
1608
1764
  except Exception as e:
1609
1765
  return EvaluationError(f"HTTP DELETE error: {str(e)}")
1610
1766
 
@@ -1846,6 +2002,8 @@ class FunctionEvaluatorMixin:
1846
2002
  return Integer(len(arg.value))
1847
2003
  if isinstance(arg, List):
1848
2004
  return Integer(len(arg.elements))
2005
+ if isinstance(arg, Map):
2006
+ return Integer(len(arg.pairs))
1849
2007
  # Handle Python list (shouldn't happen, but defensive)
1850
2008
  if isinstance(arg, list):
1851
2009
  return Integer(len(arg))
@@ -2095,6 +2253,59 @@ class FunctionEvaluatorMixin:
2095
2253
  else:
2096
2254
  return EvaluationError(f"Unsupported language: {language}")
2097
2255
 
2256
+ # Contract Assertions
2257
+ def _require(*a):
2258
+ """Assert a condition in smart contracts: require(condition, message)
2259
+
2260
+ Throws an error if condition is false. Essential for contract validation.
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
+
2282
+ # Contract Assertions
2283
+ def _require(*a):
2284
+ """Assert a condition in smart contracts: require(condition, message)
2285
+
2286
+ Throws an error if condition is false. Essential for contract validation.
2287
+ Note: This is a fallback for contexts where the require statement isn't available.
2288
+
2289
+ Example:
2290
+ require(balance >= amount, "Insufficient balance")
2291
+ require(sender == owner, "Not authorized")
2292
+ require(value > 0, "Amount must be positive")
2293
+ """
2294
+ if len(a) < 1 or len(a) > 2:
2295
+ return EvaluationError("require() takes 1-2 arguments: require(condition, [message])")
2296
+
2297
+ condition = a[0]
2298
+ message = a[1].value if len(a) > 1 and isinstance(a[1], String) else "Requirement failed"
2299
+
2300
+ # Check if condition is truthy
2301
+ from .utils import is_truthy
2302
+ if not is_truthy(condition):
2303
+ # Return error with contract-specific formatting
2304
+ return EvaluationError(f"Contract requirement failed: {message}")
2305
+
2306
+ # Condition passed, return NULL
2307
+ return NULL
2308
+
2098
2309
  # Register mappings
2099
2310
  self.builtins.update({
2100
2311
  "now": Builtin(_now, "now"),
@@ -2103,6 +2314,13 @@ class FunctionEvaluatorMixin:
2103
2314
  "to_hex": Builtin(_to_hex, "to_hex"),
2104
2315
  "from_hex": Builtin(_from_hex, "from_hex"),
2105
2316
  "sqrt": Builtin(_sqrt, "sqrt"),
2317
+ "require": Builtin(_require, "require"),
2318
+ "require": Builtin(_require, "require"),
2319
+ "input": Builtin(_input, "input"),
2320
+ "hash_password": Builtin(_hash_password, "hash_password"),
2321
+ "verify_password": Builtin(_verify_password, "verify_password"),
2322
+ "crypto_random": Builtin(_crypto_random, "crypto_random"),
2323
+ "constant_time_compare": Builtin(_constant_time_compare, "constant_time_compare"),
2106
2324
  "file": Builtin(_file, "file"),
2107
2325
  "file_read_text": Builtin(_read_text, "file_read_text"),
2108
2326
  "file_write_text": Builtin(_write_text, "file_write_text"),
@@ -2142,6 +2360,7 @@ class FunctionEvaluatorMixin:
2142
2360
  "random": Builtin(_random, "random"),
2143
2361
  "persist_set": Builtin(_persist_set, "persist_set"),
2144
2362
  "persist_get": Builtin(_persist_get, "persist_get"),
2363
+ "input": Builtin(_input, "input"),
2145
2364
  "len": Builtin(_len, "len"),
2146
2365
  "type": Builtin(_type, "type"),
2147
2366
  "first": Builtin(_first, "first"),
@@ -2154,6 +2373,9 @@ class FunctionEvaluatorMixin:
2154
2373
  "filter": Builtin(_filter, "filter"),
2155
2374
  })
2156
2375
 
2376
+ # Register access control builtins
2377
+ self._register_access_control_builtins()
2378
+
2157
2379
  # Register concurrency builtins
2158
2380
  self._register_concurrency_builtins()
2159
2381
 
@@ -2512,6 +2734,227 @@ class FunctionEvaluatorMixin:
2512
2734
  "barrier_reset": Builtin(_barrier_reset, "barrier_reset"),
2513
2735
  })
2514
2736
 
2737
+ def _register_access_control_builtins(self):
2738
+ """Register access control functions for contracts"""
2739
+ from ..access_control_system import get_access_control
2740
+ from ..blockchain.transaction import get_current_tx
2741
+
2742
+ def _set_owner(*a):
2743
+ """Set owner of current contract: set_owner(contract_id, owner_address)"""
2744
+ if len(a) != 2:
2745
+ return EvaluationError("set_owner() requires 2 arguments: contract_id, owner_address")
2746
+
2747
+ contract_id = a[0].value if hasattr(a[0], 'value') else str(a[0])
2748
+ owner = a[1].value if hasattr(a[1], 'value') else str(a[1])
2749
+
2750
+ ac = get_access_control()
2751
+ ac.set_owner(contract_id, owner)
2752
+ return NULL
2753
+
2754
+ def _get_owner(*a):
2755
+ """Get owner of contract: get_owner(contract_id)"""
2756
+ if len(a) != 1:
2757
+ return EvaluationError("get_owner() requires 1 argument: contract_id")
2758
+
2759
+ contract_id = a[0].value if hasattr(a[0], 'value') else str(a[0])
2760
+
2761
+ ac = get_access_control()
2762
+ owner = ac.get_owner(contract_id)
2763
+ return String(owner) if owner else NULL
2764
+
2765
+ def _is_owner(*a):
2766
+ """Check if address is owner: is_owner(contract_id, address)"""
2767
+ if len(a) != 2:
2768
+ return EvaluationError("is_owner() requires 2 arguments: contract_id, address")
2769
+
2770
+ contract_id = a[0].value if hasattr(a[0], 'value') else str(a[0])
2771
+ address = a[1].value if hasattr(a[1], 'value') else str(a[1])
2772
+
2773
+ ac = get_access_control()
2774
+ return TRUE if ac.is_owner(contract_id, address) else FALSE
2775
+
2776
+ def _grant_role(*a):
2777
+ """Grant role to address: grant_role(contract_id, address, role)"""
2778
+ if len(a) != 3:
2779
+ return EvaluationError("grant_role() requires 3 arguments: contract_id, address, role")
2780
+
2781
+ contract_id = a[0].value if hasattr(a[0], 'value') else str(a[0])
2782
+ address = a[1].value if hasattr(a[1], 'value') else str(a[1])
2783
+ role = a[2].value if hasattr(a[2], 'value') else str(a[2])
2784
+
2785
+ ac = get_access_control()
2786
+ ac.grant_role(contract_id, address, role)
2787
+ return NULL
2788
+
2789
+ def _revoke_role(*a):
2790
+ """Revoke role from address: revoke_role(contract_id, address, role)"""
2791
+ if len(a) != 3:
2792
+ return EvaluationError("revoke_role() requires 3 arguments: contract_id, address, role")
2793
+
2794
+ contract_id = a[0].value if hasattr(a[0], 'value') else str(a[0])
2795
+ address = a[1].value if hasattr(a[1], 'value') else str(a[1])
2796
+ role = a[2].value if hasattr(a[2], 'value') else str(a[2])
2797
+
2798
+ ac = get_access_control()
2799
+ ac.revoke_role(contract_id, address, role)
2800
+ return NULL
2801
+
2802
+ def _has_role(*a):
2803
+ """Check if address has role: has_role(contract_id, address, role)"""
2804
+ if len(a) != 3:
2805
+ return EvaluationError("has_role() requires 3 arguments: contract_id, address, role")
2806
+
2807
+ contract_id = a[0].value if hasattr(a[0], 'value') else str(a[0])
2808
+ address = a[1].value if hasattr(a[1], 'value') else str(a[1])
2809
+ role = a[2].value if hasattr(a[2], 'value') else str(a[2])
2810
+
2811
+ ac = get_access_control()
2812
+ return TRUE if ac.has_role(contract_id, address, role) else FALSE
2813
+
2814
+ def _get_roles(*a):
2815
+ """Get all roles for address: get_roles(contract_id, address)"""
2816
+ if len(a) != 2:
2817
+ return EvaluationError("get_roles() requires 2 arguments: contract_id, address")
2818
+
2819
+ contract_id = a[0].value if hasattr(a[0], 'value') else str(a[0])
2820
+ address = a[1].value if hasattr(a[1], 'value') else str(a[1])
2821
+
2822
+ ac = get_access_control()
2823
+ roles = ac.get_roles(contract_id, address)
2824
+ return List([String(role) for role in roles])
2825
+
2826
+ def _grant_permission(*a):
2827
+ """Grant permission to address: grant_permission(contract_id, address, permission)"""
2828
+ if len(a) != 3:
2829
+ return EvaluationError("grant_permission() requires 3 arguments: contract_id, address, permission")
2830
+
2831
+ contract_id = a[0].value if hasattr(a[0], 'value') else str(a[0])
2832
+ address = a[1].value if hasattr(a[1], 'value') else str(a[1])
2833
+ permission = a[2].value if hasattr(a[2], 'value') else str(a[2])
2834
+
2835
+ ac = get_access_control()
2836
+ ac.grant_permission(contract_id, address, permission)
2837
+ return NULL
2838
+
2839
+ def _revoke_permission(*a):
2840
+ """Revoke permission from address: revoke_permission(contract_id, address, permission)"""
2841
+ if len(a) != 3:
2842
+ return EvaluationError("revoke_permission() requires 3 arguments: contract_id, address, permission")
2843
+
2844
+ contract_id = a[0].value if hasattr(a[0], 'value') else str(a[0])
2845
+ address = a[1].value if hasattr(a[1], 'value') else str(a[1])
2846
+ permission = a[2].value if hasattr(a[2], 'value') else str(a[2])
2847
+
2848
+ ac = get_access_control()
2849
+ ac.revoke_permission(contract_id, address, permission)
2850
+ return NULL
2851
+
2852
+ def _has_permission(*a):
2853
+ """Check if address has permission: has_permission(contract_id, address, permission)"""
2854
+ if len(a) != 3:
2855
+ return EvaluationError("has_permission() requires 3 arguments: contract_id, address, permission")
2856
+
2857
+ contract_id = a[0].value if hasattr(a[0], 'value') else str(a[0])
2858
+ address = a[1].value if hasattr(a[1], 'value') else str(a[1])
2859
+ permission = a[2].value if hasattr(a[2], 'value') else str(a[2])
2860
+
2861
+ ac = get_access_control()
2862
+ return TRUE if ac.has_permission(contract_id, address, permission) else FALSE
2863
+
2864
+ def _require_owner(*a):
2865
+ """Require caller is owner: require_owner(contract_id, message?)"""
2866
+ if len(a) < 1 or len(a) > 2:
2867
+ return EvaluationError("require_owner() requires 1 or 2 arguments: contract_id, [message]")
2868
+
2869
+ contract_id = a[0].value if hasattr(a[0], 'value') else str(a[0])
2870
+ message = a[1].value if len(a) > 1 and hasattr(a[1], 'value') else None
2871
+
2872
+ # Get current transaction caller
2873
+ tx = get_current_tx()
2874
+ if not tx:
2875
+ return EvaluationError("require_owner() requires transaction context (TX.caller)")
2876
+
2877
+ caller = tx.caller
2878
+
2879
+ ac = get_access_control()
2880
+ try:
2881
+ if message:
2882
+ ac.require_owner(contract_id, caller, message)
2883
+ else:
2884
+ ac.require_owner(contract_id, caller)
2885
+ return NULL
2886
+ except Exception as e:
2887
+ return EvaluationError(str(e))
2888
+
2889
+ def _require_role(*a):
2890
+ """Require caller has role: require_role(contract_id, role, message?)"""
2891
+ if len(a) < 2 or len(a) > 3:
2892
+ return EvaluationError("require_role() requires 2 or 3 arguments: contract_id, role, [message]")
2893
+
2894
+ contract_id = a[0].value if hasattr(a[0], 'value') else str(a[0])
2895
+ role = a[1].value if hasattr(a[1], 'value') else str(a[1])
2896
+ message = a[2].value if len(a) > 2 and hasattr(a[2], 'value') else None
2897
+
2898
+ # Get current transaction caller
2899
+ tx = get_current_tx()
2900
+ if not tx:
2901
+ return EvaluationError("require_role() requires transaction context (TX.caller)")
2902
+
2903
+ caller = tx.caller
2904
+
2905
+ ac = get_access_control()
2906
+ try:
2907
+ if message:
2908
+ ac.require_role(contract_id, caller, role, message)
2909
+ else:
2910
+ ac.require_role(contract_id, caller, role)
2911
+ return NULL
2912
+ except Exception as e:
2913
+ return EvaluationError(str(e))
2914
+
2915
+ def _require_permission(*a):
2916
+ """Require caller has permission: require_permission(contract_id, permission, message?)"""
2917
+ if len(a) < 2 or len(a) > 3:
2918
+ return EvaluationError("require_permission() requires 2 or 3 arguments: contract_id, permission, [message]")
2919
+
2920
+ contract_id = a[0].value if hasattr(a[0], 'value') else str(a[0])
2921
+ permission = a[1].value if hasattr(a[1], 'value') else str(a[1])
2922
+ message = a[2].value if len(a) > 2 and hasattr(a[2], 'value') else None
2923
+
2924
+ # Get current transaction caller
2925
+ tx = get_current_tx()
2926
+ if not tx:
2927
+ return EvaluationError("require_permission() requires transaction context (TX.caller)")
2928
+
2929
+ caller = tx.caller
2930
+
2931
+ ac = get_access_control()
2932
+ try:
2933
+ if message:
2934
+ ac.require_permission(contract_id, caller, permission, message)
2935
+ else:
2936
+ ac.require_permission(contract_id, caller, permission)
2937
+ return NULL
2938
+ except Exception as e:
2939
+ return EvaluationError(str(e))
2940
+
2941
+ # Register access control builtins
2942
+ self.builtins.update({
2943
+ "set_owner": Builtin(_set_owner, "set_owner"),
2944
+ "get_owner": Builtin(_get_owner, "get_owner"),
2945
+ "is_owner": Builtin(_is_owner, "is_owner"),
2946
+ "grant_role": Builtin(_grant_role, "grant_role"),
2947
+ "revoke_role": Builtin(_revoke_role, "revoke_role"),
2948
+ "has_role": Builtin(_has_role, "has_role"),
2949
+ "get_roles": Builtin(_get_roles, "get_roles"),
2950
+ "grant_permission": Builtin(_grant_permission, "grant_permission"),
2951
+ "revoke_permission": Builtin(_revoke_permission, "revoke_permission"),
2952
+ "has_permission": Builtin(_has_permission, "has_permission"),
2953
+ "require_owner": Builtin(_require_owner, "require_owner"),
2954
+ "require_role": Builtin(_require_role, "require_role"),
2955
+ "require_permission": Builtin(_require_permission, "require_permission"),
2956
+ })
2957
+
2515
2958
  def _register_blockchain_builtins(self):
2516
2959
  """Register blockchain cryptographic and utility functions"""
2517
2960
  from ..blockchain.crypto import CryptoPlugin