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.
- package/README.md +165 -5
- package/package.json +1 -1
- package/src/zexus/__init__.py +1 -1
- package/src/zexus/access_control_system/__init__.py +38 -0
- package/src/zexus/access_control_system/access_control.py +237 -0
- package/src/zexus/cli/main.py +1 -1
- package/src/zexus/cli/zpm.py +1 -1
- package/src/zexus/debug_sanitizer.py +250 -0
- package/src/zexus/error_reporter.py +22 -2
- package/src/zexus/evaluator/core.py +17 -21
- package/src/zexus/evaluator/expressions.py +116 -57
- package/src/zexus/evaluator/functions.py +613 -170
- package/src/zexus/evaluator/resource_limiter.py +291 -0
- package/src/zexus/evaluator/statements.py +47 -12
- package/src/zexus/evaluator/utils.py +12 -6
- package/src/zexus/lsp/server.py +1 -1
- package/src/zexus/object.py +21 -2
- package/src/zexus/parser/parser.py +56 -4
- package/src/zexus/parser/strategy_context.py +83 -7
- package/src/zexus/parser/strategy_structural.py +12 -4
- package/src/zexus/persistence.py +105 -6
- package/src/zexus/security.py +43 -25
- package/src/zexus/security_enforcement.py +237 -0
- package/src/zexus/stdlib/fs.py +120 -22
- package/src/zexus/zexus_ast.py +3 -2
- package/src/zexus/zpm/package_manager.py +1 -1
- package/src/zexus.egg-info/PKG-INFO +499 -13
- package/src/zexus.egg-info/SOURCES.txt +258 -152
|
@@ -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
|
-
#
|
|
200
|
-
|
|
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
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
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
|
|
219
|
-
|
|
220
|
-
from ..object import Coroutine
|
|
221
|
-
import sys
|
|
225
|
+
if isinstance(fn, (Action, LambdaFunction)):
|
|
226
|
+
debug_log(" Calling user-defined function")
|
|
222
227
|
|
|
223
|
-
#
|
|
228
|
+
# Check if this is an async action
|
|
229
|
+
is_async = getattr(fn, 'is_async', False)
|
|
224
230
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
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
|
-
#
|
|
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
|
-
|
|
236
|
-
|
|
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
|
-
#
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
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
|
-
#
|
|
249
|
-
|
|
250
|
-
self._execute_deferred_cleanup(new_env, [])
|
|
248
|
+
# Yield control first (makes it a true generator)
|
|
249
|
+
yield None
|
|
251
250
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
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
|
-
#
|
|
290
|
-
|
|
291
|
-
result = res.value
|
|
292
|
-
else:
|
|
293
|
-
result = res
|
|
276
|
+
# Synchronous function execution
|
|
277
|
+
new_env = Environment(outer=fn.env)
|
|
294
278
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
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
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
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
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
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
|
-
|
|
374
|
-
|
|
375
|
-
|
|
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
|
-
|
|
378
|
-
|
|
379
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|