xian-tech-contracting 1.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. contracting/__init__.py +0 -0
  2. contracting/client.py +389 -0
  3. contracting/compilation/__init__.py +0 -0
  4. contracting/compilation/compiler.py +155 -0
  5. contracting/compilation/linter.py +419 -0
  6. contracting/compilation/parser.py +55 -0
  7. contracting/compilation/whitelists.py +145 -0
  8. contracting/constants.py +54 -0
  9. contracting/contracts/__init__.py +0 -0
  10. contracting/contracts/proxythis.py +11 -0
  11. contracting/contracts/submission.s.py +108 -0
  12. contracting/contracts/thistest2.py +11 -0
  13. contracting/contracts/zk_registry.s.py +153 -0
  14. contracting/execution/__init__.py +0 -0
  15. contracting/execution/executor.py +235 -0
  16. contracting/execution/module.py +188 -0
  17. contracting/execution/native_tracer.py +139 -0
  18. contracting/execution/python_tracer.py +188 -0
  19. contracting/execution/runtime.py +428 -0
  20. contracting/execution/tracer.py +57 -0
  21. contracting/execution/tracer_common.py +282 -0
  22. contracting/names.py +26 -0
  23. contracting/stdlib/__init__.py +0 -0
  24. contracting/stdlib/bridge/__init__.py +0 -0
  25. contracting/stdlib/bridge/access.py +41 -0
  26. contracting/stdlib/bridge/crypto.py +35 -0
  27. contracting/stdlib/bridge/hashing.py +43 -0
  28. contracting/stdlib/bridge/imports.py +337 -0
  29. contracting/stdlib/bridge/orm.py +62 -0
  30. contracting/stdlib/bridge/random.py +128 -0
  31. contracting/stdlib/bridge/zk.py +274 -0
  32. contracting/stdlib/env.py +33 -0
  33. contracting/storage/__init__.py +0 -0
  34. contracting/storage/contract.py +115 -0
  35. contracting/storage/driver.py +390 -0
  36. contracting/storage/lmdb_store.py +137 -0
  37. contracting/storage/orm.py +428 -0
  38. xian_tech_contracting-1.0.1.dist-info/METADATA +125 -0
  39. xian_tech_contracting-1.0.1.dist-info/RECORD +42 -0
  40. xian_tech_contracting-1.0.1.dist-info/WHEEL +5 -0
  41. xian_tech_contracting-1.0.1.dist-info/licenses/LICENSE +7 -0
  42. xian_tech_contracting-1.0.1.dist-info/top_level.txt +1 -0
File without changes
contracting/client.py ADDED
@@ -0,0 +1,389 @@
1
+ import ast
2
+ import inspect
3
+ import os
4
+ from datetime import datetime
5
+ from functools import partial
6
+ from types import FunctionType
7
+
8
+ import autopep8
9
+ from xian_runtime_types.time import Datetime
10
+
11
+ from contracting.compilation.compiler import ContractingCompiler
12
+ from contracting.execution import runtime
13
+ from contracting.execution.executor import Executor
14
+ from contracting.storage.driver import Driver
15
+
16
+ from . import constants
17
+ from .storage.orm import Hash, Variable
18
+
19
+
20
+ class AbstractContract:
21
+ def __init__(
22
+ self,
23
+ name,
24
+ signer,
25
+ environment,
26
+ executor: Executor,
27
+ funcs,
28
+ return_full_output=False,
29
+ ):
30
+ self.name = name
31
+ self.signer = signer
32
+ self.environment = environment
33
+ self.executor = executor
34
+ self.functions = funcs
35
+
36
+ # set up virtual functions
37
+ for f in funcs:
38
+ # unpack tuple packed in SenecaClient
39
+ func, kwargs = f
40
+
41
+ # set the kwargs to None. these will fail if they are not provided
42
+ default_kwargs = {}
43
+ for kwarg in kwargs:
44
+ default_kwargs[kwarg] = None
45
+
46
+ # each function is a partial that allows kwarg overloading and overriding
47
+ setattr(
48
+ self,
49
+ func,
50
+ partial(
51
+ self._abstract_function_call,
52
+ signer=self.signer,
53
+ contract_name=self.name,
54
+ executor=self.executor,
55
+ func=func,
56
+ return_full_output=return_full_output,
57
+ # environment=self.environment,
58
+ **default_kwargs,
59
+ ),
60
+ )
61
+
62
+ def keys(self):
63
+ # Scope strictly to this contract's namespace
64
+ return self.executor.driver.keys(f"{self.name}.")
65
+
66
+ # a variable contains a DOT, but no __, and no :
67
+ # a hash contains a DOT, no __, and a :
68
+ # a constant contains __, a DOT, and :
69
+
70
+ def quick_read(self, variable, key=None, args=None):
71
+ a = []
72
+
73
+ if key is not None:
74
+ a.append(key)
75
+
76
+ if args is not None and isinstance(args, list):
77
+ for arg in args:
78
+ a.append(arg)
79
+
80
+ k = self.executor.driver.make_key(
81
+ contract=self.name, variable=variable, args=a
82
+ )
83
+ return self.executor.driver.get(k)
84
+
85
+ def quick_write(self, variable, key=None, value=None, args=None):
86
+ if key is not None:
87
+ a = [key]
88
+ else:
89
+ a = []
90
+
91
+ if args is not None and isinstance(args, list):
92
+ for arg in args:
93
+ a.append(arg)
94
+
95
+ k = self.executor.driver.make_key(
96
+ contract=self.name, variable=variable, args=a
97
+ )
98
+
99
+ self.executor.driver.set(k, value)
100
+ self.executor.driver.commit()
101
+
102
+ def run_private_function(self, f, signer=None, environment=None, **kwargs):
103
+ # Override kwargs if provided
104
+ signer = signer or self.signer
105
+ environment = environment or self.environment
106
+
107
+ # Let executor access private functions
108
+ self.executor.bypass_privates = True
109
+
110
+ try:
111
+ # Append private method prefix to function name if it isn't there already
112
+ if not f.startswith(constants.PRIVATE_METHOD_PREFIX):
113
+ f = "{}{}".format(constants.PRIVATE_METHOD_PREFIX, f)
114
+
115
+ return self._abstract_function_call(
116
+ signer=signer,
117
+ executor=self.executor,
118
+ contract_name=self.name,
119
+ environment=environment,
120
+ func=f,
121
+ metering=None,
122
+ now=None,
123
+ **kwargs,
124
+ )
125
+ finally:
126
+ # Always restore restricted mode, even if the private call fails.
127
+ self.executor.bypass_privates = False
128
+
129
+ def __getattr__(self, item):
130
+ try:
131
+ # return the attribute if it exists on the instance
132
+ return self.__getattribute__(item)
133
+ except AttributeError as e:
134
+ # otherwise, attempt to resolve it. full name is contract.item
135
+ fullname = "{}.{}".format(self.name, item)
136
+
137
+ # if the raw name exists, it is a __protected__ or a variable, so prepare for those
138
+ if fullname in self.keys():
139
+ variable = Variable(
140
+ contract=self.name, name=item, driver=self.executor.driver
141
+ )
142
+
143
+ # return just the value if it is __protected__ to prevent sets
144
+ if item.startswith("__"):
145
+ return variable.get()
146
+
147
+ # otherwise, return the variable object with allows sets
148
+ return variable
149
+
150
+ # otherwise, see if contract.items: has more than one entry
151
+ if (
152
+ len(
153
+ self.executor.driver.values(
154
+ prefix=self.name + "." + item + ":"
155
+ )
156
+ )
157
+ > 0
158
+ ):
159
+ # if so, it is a hash. return the hash object
160
+ return Hash(
161
+ contract=self.name, name=item, driver=self.executor.driver
162
+ )
163
+
164
+ # otherwise, the attribut does not exist, so throw the error.
165
+ raise e
166
+
167
+ def now(self):
168
+ d = datetime.today()
169
+ return Datetime(d.year, d.month, d.day, hour=d.hour, minute=d.minute)
170
+
171
+ def _abstract_function_call(
172
+ self,
173
+ signer,
174
+ executor,
175
+ contract_name,
176
+ func,
177
+ environment=None,
178
+ stamps=constants.DEFAULT_STAMPS,
179
+ metering=None,
180
+ now=None,
181
+ return_full_output=False,
182
+ **kwargs,
183
+ ):
184
+
185
+ # for k, v in kwargs.items():
186
+ # assert v is not None, 'Keyword "{}" not provided. Must not be None.'.format(k)
187
+ environment = environment or self.environment
188
+
189
+ if now is None:
190
+ now = self.now()
191
+
192
+ if environment.get("now") is None:
193
+ environment.update({"now": now})
194
+
195
+ output = executor.execute(
196
+ sender=signer,
197
+ contract_name=contract_name,
198
+ function_name=func,
199
+ kwargs=kwargs,
200
+ stamps=stamps,
201
+ environment=environment,
202
+ metering=metering,
203
+ )
204
+
205
+ if executor.production:
206
+ executor.sandbox.terminate()
207
+
208
+ if output["status_code"] == 1:
209
+ raise output["result"] if not return_full_output else output
210
+ return output["result"] if not return_full_output else output
211
+
212
+
213
+ class ContractingClient:
214
+ def __init__(
215
+ self,
216
+ signer="sys",
217
+ submission_filename=os.path.join(
218
+ os.path.dirname(__file__), "contracts/submission.s.py"
219
+ ),
220
+ storage_home=constants.STORAGE_HOME,
221
+ driver: Driver = None,
222
+ metering=False,
223
+ compiler=ContractingCompiler(),
224
+ environment={},
225
+ tracer_mode: str | None = None,
226
+ ):
227
+ if tracer_mode is not None:
228
+ runtime.rt.set_tracer_mode(tracer_mode)
229
+ driver = (
230
+ driver if driver is not None else Driver(storage_home=storage_home)
231
+ )
232
+ self.executor = Executor(metering=metering, driver=driver)
233
+ self.raw_driver = driver
234
+ self.signer = signer
235
+ self.compiler = compiler
236
+ self.submission_filename = submission_filename
237
+ self.environment = environment
238
+ # Get submission contract from file
239
+ if submission_filename is not None:
240
+ # Seed the genesis contracts into the instance
241
+ with open(self.submission_filename) as f:
242
+ contract = f.read()
243
+
244
+ self.raw_driver.set_contract(name="submission", code=contract)
245
+ self.raw_driver.commit()
246
+
247
+ # Get submission contract from state
248
+ self.submission_contract = self.get_contract("submission")
249
+
250
+ def set_submission_contract(self, filename=None, commit=True):
251
+ state_contract = self.get_contract("submission")
252
+
253
+ if filename is None:
254
+ filename = self.submission_filename
255
+
256
+ if filename is None and state_contract is None:
257
+ raise AssertionError(
258
+ "No submission contract provided or found in state."
259
+ )
260
+
261
+ if filename is not None:
262
+ with open(filename) as f:
263
+ contract = f.read()
264
+
265
+ self.raw_driver.delete_contract(name="submission")
266
+ self.raw_driver.set_contract(name="submission", code=contract)
267
+
268
+ if commit:
269
+ self.raw_driver.commit()
270
+
271
+ self.submission_contract = self.get_contract("submission")
272
+
273
+ def flush(self):
274
+ # flushes storage and resubmits genesis contracts
275
+ self.raw_driver.flush_full()
276
+ self.raw_driver.flush_cache()
277
+
278
+ if self.submission_filename is not None:
279
+ self.set_submission_contract()
280
+
281
+ # Returns abstract contract which has partial methods mapped to each exported function.
282
+ def get_contract(self, name):
283
+ contract = self.raw_driver.get_contract(name)
284
+
285
+ if contract is None:
286
+ return None
287
+
288
+ tree = ast.parse(contract)
289
+
290
+ function_defs = [
291
+ n for n in ast.walk(tree) if isinstance(n, ast.FunctionDef)
292
+ ]
293
+
294
+ funcs = []
295
+ for definition in function_defs:
296
+ func_name = definition.name
297
+ kwargs = [arg.arg for arg in definition.args.args]
298
+
299
+ funcs.append((func_name, kwargs))
300
+
301
+ return AbstractContract(
302
+ name=name,
303
+ signer=self.signer,
304
+ environment=self.environment,
305
+ executor=self.executor,
306
+ funcs=funcs,
307
+ )
308
+
309
+ def closure_to_code_string(self, f):
310
+ closure_code = inspect.getsource(f)
311
+ closure_code = autopep8.fix_code(closure_code)
312
+ closure_tree = ast.parse(closure_code)
313
+
314
+ # Remove the enclosing function by swapping out the function def node with its children
315
+ assert len(closure_tree.body) == 1, "Module has multiple body nodes."
316
+ assert isinstance(closure_tree.body[0], ast.FunctionDef), (
317
+ "Function definition not found at root."
318
+ )
319
+
320
+ func_def_body = closure_tree.body[0]
321
+ closure_tree.body = func_def_body.body
322
+
323
+ contract_code = ast.unparse(closure_tree)
324
+ name = func_def_body.name
325
+
326
+ return contract_code, name
327
+
328
+ def lint(self, f, raise_errors=False):
329
+ if isinstance(f, FunctionType):
330
+ f, _ = self.closure_to_code_string(f)
331
+
332
+ if raise_errors:
333
+ self.compiler.linter.check_raise(f)
334
+ return None
335
+
336
+ return self.compiler.linter.check(f)
337
+
338
+ def compile(self, f):
339
+ if isinstance(f, FunctionType):
340
+ f, _ = self.closure_to_code_string(f)
341
+
342
+ code = self.compiler.parse_to_code(f)
343
+ return code
344
+
345
+ def submit(
346
+ self,
347
+ f,
348
+ name=None,
349
+ metering=None,
350
+ owner=None,
351
+ constructor_args={},
352
+ signer=None,
353
+ ):
354
+
355
+ assert self.submission_contract is not None, (
356
+ "No submission contract set. Try set_submission_contract first."
357
+ )
358
+
359
+ if isinstance(f, FunctionType):
360
+ f, n = self.closure_to_code_string(f)
361
+ if name is None:
362
+ name = n
363
+
364
+ assert name is not None, "No name provided."
365
+
366
+ if signer is None:
367
+ signer = self.signer
368
+
369
+ self.submission_contract.submit_contract(
370
+ name=name,
371
+ code=f,
372
+ owner=owner,
373
+ constructor_args=constructor_args,
374
+ metering=metering,
375
+ signer=signer,
376
+ )
377
+
378
+ def get_contracts(self):
379
+ contracts = []
380
+ for key in self.raw_driver.keys():
381
+ if key.endswith(".__code__"):
382
+ contracts.append(key.replace(".__code__", ""))
383
+ return contracts
384
+
385
+ def get_var(self, contract, variable, arguments=[], mark=False):
386
+ return self.raw_driver.get_var(contract, variable, arguments, mark)
387
+
388
+ def set_var(self, contract, variable, arguments=[], value=None, mark=False):
389
+ self.raw_driver.set_var(contract, variable, arguments, value, mark)
File without changes
@@ -0,0 +1,155 @@
1
+ import ast
2
+
3
+ from contracting import constants
4
+ from contracting.compilation.linter import Linter, LintingError
5
+
6
+
7
+ class ContractingCompiler(ast.NodeTransformer):
8
+ def __init__(self, module_name="__main__", linter=None):
9
+ self.module_name = module_name
10
+ self.linter = linter or Linter()
11
+ self.lint_alerts = None
12
+ self.source = None
13
+ self.constructor_visited = False
14
+ self.private_names = set()
15
+ self.orm_names = set()
16
+ self.visited_names = set() # store the method visits
17
+
18
+ def parse(self, source: str, lint=True):
19
+ self.constructor_visited = False
20
+ self.source = source
21
+
22
+ tree = ast.parse(source)
23
+
24
+ if lint:
25
+ self.lint_alerts = self.linter.check(tree)
26
+ else:
27
+ self.lint_alerts = None
28
+
29
+ tree = self.visit(tree)
30
+
31
+ if self.lint_alerts is not None:
32
+ raise LintingError(self.lint_alerts)
33
+
34
+ # check all visited nodes and see if they are actually private
35
+
36
+ # An Expr node can have a value func of compilation.Name, or compilation.
37
+ # Attribute which you much access the value of.
38
+ # TODO: This code branching is not ideal and should be investigated for simplicity.
39
+ for node in self.visited_names:
40
+ if node.id in self.private_names or node.id in self.orm_names:
41
+ node.id = self.privatize(node.id)
42
+
43
+ ast.fix_missing_locations(tree)
44
+
45
+ # reset state
46
+ self.private_names = set()
47
+ self.orm_names = set()
48
+ self.visited_names = set()
49
+ self.lint_alerts = None
50
+ self.source = None
51
+
52
+ return tree
53
+
54
+ @staticmethod
55
+ def privatize(s):
56
+ return "{}{}".format(constants.PRIVATE_METHOD_PREFIX, s)
57
+
58
+ def compile(self, source: str, lint=True):
59
+ tree = self.parse(source, lint=lint)
60
+
61
+ compiled_code = compile(tree, "<compilation>", "exec")
62
+
63
+ return compiled_code
64
+
65
+ def normalize_source(self, source: str, lint=True):
66
+ tree = ast.parse(source)
67
+
68
+ if lint:
69
+ lint_alerts = self.linter.check(tree)
70
+ if lint_alerts is not None:
71
+ raise LintingError(lint_alerts)
72
+
73
+ ast.fix_missing_locations(tree)
74
+ return ast.unparse(tree)
75
+
76
+ def parse_to_code(self, source, lint=True):
77
+ tree = self.parse(source, lint=lint)
78
+ return ast.unparse(tree)
79
+
80
+ def visit_FunctionDef(self, node):
81
+
82
+ # Presumes all decorators are valid, as caught by linter.
83
+ if node.decorator_list:
84
+ # Presumes that a single decorator is passed. This is caught by the linter.
85
+ decorator = node.decorator_list.pop()
86
+
87
+ # change the name of the init function to '____' so it is uncallable except once
88
+ if decorator.id == constants.INIT_DECORATOR_STRING:
89
+ node.name = "____"
90
+
91
+ elif decorator.id == constants.EXPORT_DECORATOR_STRING:
92
+ # Transform @export decorators to @__export(contract_name) decorators
93
+ decorator.id = "{}{}".format(
94
+ "__", constants.EXPORT_DECORATOR_STRING
95
+ )
96
+
97
+ new_node = ast.Call(
98
+ func=decorator,
99
+ args=[ast.Constant(value=self.module_name)],
100
+ keywords=[],
101
+ )
102
+
103
+ node.decorator_list.append(new_node)
104
+
105
+ else:
106
+ self.private_names.add(node.name)
107
+ node.name = self.privatize(node.name)
108
+
109
+ self.generic_visit(node)
110
+
111
+ return node
112
+
113
+ def visit_Assign(self, node):
114
+ if (
115
+ isinstance(node.value, ast.Call)
116
+ and not isinstance(node.value.func, ast.Attribute)
117
+ and node.value.func.id in constants.ORM_CLASS_NAMES
118
+ ):
119
+ node.value.keywords.append(
120
+ ast.keyword(
121
+ arg="contract",
122
+ value=ast.Constant(value=self.module_name),
123
+ )
124
+ )
125
+ node.value.keywords.append(
126
+ ast.keyword(
127
+ arg="name",
128
+ value=ast.Constant(value=node.targets[0].id),
129
+ )
130
+ )
131
+ self.orm_names.add(node.targets[0].id)
132
+
133
+ self.generic_visit(node)
134
+
135
+ return node
136
+
137
+ def visit_Name(self, node):
138
+ self.visited_names.add(node)
139
+ return node
140
+
141
+ def visit_Expr(self, node):
142
+ self.generic_visit(node)
143
+ return node
144
+
145
+ def visit_Constant(self, node):
146
+ if isinstance(node.value, float):
147
+ literal = ast.get_source_segment(self.source, node) or str(
148
+ node.value
149
+ )
150
+ return ast.Call(
151
+ func=ast.Name(id="decimal", ctx=ast.Load()),
152
+ args=[ast.Constant(value=literal)],
153
+ keywords=[],
154
+ )
155
+ return node