genlayer-test 0.1.0b6__py3-none-any.whl → 0.1.2__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 (46) hide show
  1. {genlayer_test-0.1.0b6.dist-info → genlayer_test-0.1.2.dist-info}/METADATA +19 -2
  2. genlayer_test-0.1.2.dist-info/RECORD +53 -0
  3. {genlayer_test-0.1.0b6.dist-info → genlayer_test-0.1.2.dist-info}/WHEEL +1 -1
  4. gltest/artifacts/__init__.py +1 -1
  5. gltest/artifacts/contract.py +14 -5
  6. gltest/assertions.py +4 -3
  7. gltest/exceptions.py +9 -1
  8. gltest/glchain/account.py +1 -0
  9. gltest/helpers/__init__.py +1 -1
  10. gltest/helpers/fixture_snapshot.py +9 -1
  11. tests/artifact/contracts/not_ic_contract.py +22 -0
  12. tests/artifact/test_contract_definition.py +92 -0
  13. tests/assertions/test_assertions.py +31 -0
  14. tests/examples/contracts/football_prediction_market.py +97 -0
  15. tests/examples/contracts/intelligent_oracle.py +369 -0
  16. tests/examples/contracts/intelligent_oracle_factory.py +47 -0
  17. tests/examples/contracts/llm_erc20.py +69 -0
  18. tests/examples/contracts/log_indexer.py +67 -0
  19. tests/examples/contracts/multi_file_contract/__init__.py +20 -0
  20. tests/examples/contracts/multi_file_contract/other.py +14 -0
  21. tests/examples/contracts/multi_read_erc20.py +28 -0
  22. tests/examples/contracts/multi_tenant_storage.py +48 -0
  23. tests/examples/contracts/read_erc20.py +14 -0
  24. tests/examples/contracts/storage.py +22 -0
  25. tests/examples/contracts/user_storage.py +24 -0
  26. tests/examples/contracts/wizard_of_coin.py +56 -0
  27. tests/examples/tests/test_football_prediction_market.py +19 -0
  28. tests/examples/tests/test_intelligent_oracle_factory.py +127 -0
  29. tests/examples/tests/test_llm_erc20.py +41 -0
  30. tests/examples/tests/test_log_indexer.py +63 -0
  31. tests/examples/tests/test_multi_file_contract.py +15 -0
  32. tests/examples/tests/test_multi_file_contract_legacy.py +15 -0
  33. tests/examples/tests/test_multi_read_erc20.py +85 -0
  34. tests/examples/tests/test_multi_tenant_storage.py +58 -0
  35. tests/examples/tests/test_read_erc20.py +37 -0
  36. tests/examples/tests/test_storage.py +23 -0
  37. tests/examples/tests/test_storage_legacy.py +23 -0
  38. tests/examples/tests/test_user_storage.py +57 -0
  39. tests/examples/tests/test_wizard_of_coin.py +12 -0
  40. tests/plugin/conftest.py +1 -0
  41. genlayer_test-0.1.0b6.dist-info/RECORD +0 -24
  42. tests/conftest.py +0 -1
  43. {genlayer_test-0.1.0b6.dist-info → genlayer_test-0.1.2.dist-info}/entry_points.txt +0 -0
  44. {genlayer_test-0.1.0b6.dist-info → genlayer_test-0.1.2.dist-info}/licenses/LICENSE +0 -0
  45. {genlayer_test-0.1.0b6.dist-info → genlayer_test-0.1.2.dist-info}/top_level.txt +0 -0
  46. /tests/{test_plugin_hooks.py → plugin/test_plugin_hooks.py} +0 -0
@@ -0,0 +1,369 @@
1
+ # { "Depends": "py-genlayer:test" }
2
+
3
+ import json
4
+ from enum import Enum
5
+ from datetime import datetime, timezone
6
+ from urllib.parse import urlparse
7
+ from genlayer import *
8
+
9
+
10
+ class Status(Enum):
11
+ ACTIVE = "Active"
12
+ RESOLVED = "Resolved"
13
+ ERROR = "Error"
14
+
15
+
16
+ class IntelligentOracle(gl.Contract):
17
+ # Declare persistent storage fields
18
+ prediction_market_id: str
19
+ title: str
20
+ description: str
21
+ potential_outcomes: DynArray[str]
22
+ rules: DynArray[str]
23
+ data_source_domains: DynArray[str]
24
+ resolution_urls: DynArray[str]
25
+ earliest_resolution_date: str # Store as ISO format string
26
+ status: str # Store as string since Enum isn't supported
27
+ analysis: str # Store analysis results
28
+ outcome: str
29
+
30
+ def __init__(
31
+ self,
32
+ prediction_market_id: str,
33
+ title: str,
34
+ description: str,
35
+ potential_outcomes: list[str],
36
+ rules: list[str],
37
+ data_source_domains: list[str],
38
+ resolution_urls: list[str],
39
+ earliest_resolution_date: str,
40
+ ):
41
+ if (
42
+ not prediction_market_id
43
+ or not title
44
+ or not description
45
+ or not potential_outcomes
46
+ or not rules
47
+ or not earliest_resolution_date
48
+ ):
49
+ raise ValueError("Missing required fields.")
50
+
51
+ if not resolution_urls and not data_source_domains:
52
+ raise ValueError("Missing resolution URLs or data source domains.")
53
+
54
+ if len(resolution_urls) > 0 and len(data_source_domains) > 0:
55
+ raise ValueError(
56
+ "Cannot provide both resolution URLs and data source domains."
57
+ )
58
+
59
+ if len(potential_outcomes) < 2:
60
+ raise ValueError("At least two potential outcomes are required.")
61
+
62
+ if len(potential_outcomes) != len(set(potential_outcomes)):
63
+ raise ValueError("Potential outcomes must be unique.")
64
+
65
+ self.prediction_market_id = prediction_market_id
66
+ self.title = title
67
+ self.description = description
68
+
69
+ for outcome in potential_outcomes:
70
+ self.potential_outcomes.append(outcome.strip())
71
+
72
+ for rule in rules:
73
+ self.rules.append(rule)
74
+
75
+ for datasource in data_source_domains:
76
+ self.data_source_domains.append(
77
+ datasource.strip()
78
+ .lower()
79
+ .replace("http://", "")
80
+ .replace("https://", "")
81
+ .replace("www.", "")
82
+ )
83
+
84
+ for url in resolution_urls:
85
+ self.resolution_urls.append(url.strip())
86
+
87
+ self.earliest_resolution_date = earliest_resolution_date
88
+ self.status = Status.ACTIVE.value
89
+
90
+ self.outcome = ""
91
+
92
+ @gl.public.view
93
+ def _check_evidence_domain(self, evidence: str) -> bool:
94
+ try:
95
+ parsed_url = urlparse(evidence)
96
+ evidence_domain = parsed_url.netloc.lower().replace("www.", "")
97
+ return evidence_domain in self.data_source_domains
98
+ except Exception:
99
+ return False
100
+
101
+ @gl.public.write
102
+ def resolve(self, evidence_url: str = "") -> None:
103
+ if self.status == Status.RESOLVED.value:
104
+ raise ValueError("Cannot resolve an already resolved oracle.")
105
+
106
+ current_time = datetime.now().astimezone().date()
107
+ earliest_time = datetime.fromisoformat(self.earliest_resolution_date).date()
108
+ if current_time < earliest_time:
109
+ raise ValueError("Cannot resolve before the earliest resolution date.")
110
+
111
+ if len(self.resolution_urls) > 0 and evidence_url:
112
+ raise ValueError(
113
+ "An evidence URL was provided but the oracle is configured to use resolution URLs already provided."
114
+ )
115
+
116
+ if len(self.resolution_urls) == 0 and not evidence_url:
117
+ raise ValueError(
118
+ "No evidence URL provided and the oracle is not configured to use resolution URLs."
119
+ )
120
+
121
+ if evidence_url:
122
+ is_valid = self._check_evidence_domain(evidence_url)
123
+ if not is_valid:
124
+ raise ValueError(
125
+ "The evidence URL does not match any of the data source domains."
126
+ )
127
+
128
+ analyzed_outputs = []
129
+ resources_to_check = (
130
+ self.resolution_urls if len(self.resolution_urls) > 0 else [evidence_url]
131
+ )
132
+
133
+ title = self.title
134
+ description = self.description
135
+ potential_outcomes = list(self.potential_outcomes)
136
+ rules = list(self.rules)
137
+ earliest_resolution_date = self.earliest_resolution_date
138
+
139
+ for resource_url in resources_to_check:
140
+
141
+ def evaluate_single_source() -> str:
142
+ resource_web_data = gl.get_webpage(resource_url, mode="text")
143
+ print(resource_web_data)
144
+
145
+ task = f"""
146
+ You are an AI Validator tasked with resolving a prediction market.
147
+ Your goal is to determine the correct outcome based on the user-defined rules,
148
+ the provided webpage HTML content, the resolution date, and the list of potential outcomes.
149
+
150
+ ### Inputs
151
+ <title>
152
+ {title}
153
+ </title>
154
+
155
+ <description>
156
+ {description}
157
+ </description>
158
+
159
+ <potential_outcomes>
160
+ {potential_outcomes}
161
+ </potential_outcomes>
162
+
163
+ <rules>
164
+ {rules}
165
+ </rules>
166
+
167
+ <source_url>
168
+ {resource_url}
169
+ </source_url>
170
+
171
+ <webpage_content>
172
+ {resource_web_data}
173
+ </webpage_content>
174
+
175
+ <current_date>
176
+ {datetime.now().astimezone()}
177
+ </current_date>
178
+
179
+ <earliest_resolution_date>
180
+ {earliest_resolution_date}
181
+ </earliest_resolution_date>
182
+
183
+
184
+
185
+
186
+ ### **Your Task:**
187
+ 1. **Analyze the Inputs:**
188
+ - Carefully read and interpret the user-defined rules.
189
+ - Parse the HTML content to extract meaningful information relevant to the rules.
190
+ - Determine if the source pertains to the event that is being predicted.
191
+ - Determine if the event has occurred yet.
192
+
193
+ 2. **Provide Reasoning:**
194
+ - Write a clear, self-contained reasoning for the outcome.
195
+ - Reference specific parts of the rules and the extracted data that support your decision.
196
+ - Ensure that someone reading the reasoning can understand it without needing additional information.
197
+
198
+ 3. **Determine The Outcome:**
199
+ - Based on your analysis, decide which potential outcome is correct.
200
+ - If an outcome can be determined, but the outcome is not in the list of potential outcomes, the outcome should be `ERROR`.
201
+ - If the information is insufficient or inconclusive, or the event has not occurred yet, and you cannot confidently determine an outcome based on this source, the outcome should be `UNDETERMINED`.
202
+
203
+
204
+
205
+
206
+ ### **Output Format:**
207
+
208
+ Provide your response in **valid JSON** format with the following structure:
209
+
210
+ ```json
211
+ {{
212
+ "valid_source": "true | false",
213
+ "event_has_occurred": "true | false",
214
+ "reasoning": "Your detailed reasoning here",
215
+ "outcome": "Chosen outcome from the potential outcomes list, `UNDETERMINED` if no outcome can be determined based on this source, `ERROR` if the outcome is not in the potential outcomes list"
216
+ }}
217
+ ```
218
+
219
+ ### **Constraints and Considerations:**
220
+
221
+ - **Accuracy:** Base your decision strictly on the provided inputs.
222
+ - **Objectivity:** Remain neutral and unbiased.
223
+ - **Clarity:** Make sure your reasoning is easy to understand.
224
+ - **Validity:** Ensure the JSON output is properly formatted and free of errors. Do not include trailing commas.
225
+ """
226
+ result = gl.exec_prompt(task)
227
+ print(result)
228
+ return result
229
+
230
+ result = gl.eq_principle_prompt_comparative(
231
+ evaluate_single_source,
232
+ principle="`outcome` field must be exactly the same. All other fields must be similar",
233
+ )
234
+
235
+ result_dict = _parse_json_dict(result)
236
+ analyzed_outputs.append((resource_url, result_dict))
237
+
238
+ def evaluate_all_sources() -> str:
239
+ task = f"""
240
+ You are an AI Validator tasked with resolving a prediction market Oracle. Your goal is to determine
241
+ the correct outcome based on processed data from all of the individial data sources. Here are your inputs
242
+
243
+ ### Inputs
244
+ <title>
245
+ {title}
246
+ </title>
247
+
248
+ <description>
249
+ {description}
250
+ </description>
251
+
252
+ <potential_outcomes>
253
+ {potential_outcomes}
254
+ </potential_outcomes>
255
+
256
+ <rules>
257
+ {rules}
258
+ </rules>
259
+
260
+ <processed_data>
261
+ {analyzed_outputs}
262
+ </processed_data>
263
+
264
+ <current_date>
265
+ {datetime.now().astimezone()}
266
+ </current_date>
267
+
268
+ <earliest_resolution_date>
269
+ {earliest_resolution_date}
270
+ </earliest_resolution_date>
271
+
272
+ ### **Your Task:**
273
+ 1. **Analyze the Inputs:**
274
+ - Carefully read and interpret the user-defined rules.
275
+ - Take into account all the processed data form the sources.
276
+ - Consider the resolution date in your analysis to ensure timeliness of the data.
277
+
278
+ 2. **Determine The Outcome:**
279
+ - The output should be determined from the processed data form the resolution sources.
280
+ - Based on your analysis, decide which potential outcome is correct.
281
+ - If an outcome can be determined, but the outcome is not in the list of potential outcomes, the outcome should be `ERROR`.
282
+ - If the information is insufficient or inconclusive, and you cannot confidently determine an outcome, the outcome should be `UNDETERMINED`.
283
+ - Your response should reflect a coherent summary outcome from the previous analysis.
284
+ - If multiple sources contradict each other, refer to the rules to determine how to resolve the contradiction.
285
+ - If the rules do not provide a clear resolution, the outcome should be `ERROR`.
286
+
287
+ ### **Output Format:**
288
+
289
+ Provide your response in **valid JSON** format with the following structure:
290
+
291
+ ```json
292
+ {{
293
+ "relevant_sources": "List of URLs that are relevant to the outcome",
294
+ "reasoning": "Your detailed reasoning here",
295
+ "outcome": "Chosen outcome from the potential outcomes list, `UNDETERMINED` if undetermined, `ERROR` if the outcome is not in the potential outcomes list"
296
+ }}
297
+ ```
298
+
299
+ ### **Constraints and Considerations:**
300
+
301
+ - **Accuracy:** Base your decision strictly on the provided inputs.
302
+ - **Objectivity:** Remain neutral and unbiased.
303
+ - **Clarity:** Make sure your reason is easy to understand.
304
+ - **Validity:** Ensure the JSON output is properly formatted and free of errors. Do not include trailing commas.
305
+
306
+ """
307
+
308
+ result = gl.exec_prompt(task)
309
+ print(result)
310
+ return result
311
+
312
+ result = gl.eq_principle_prompt_comparative(
313
+ evaluate_all_sources,
314
+ principle="`outcome` field must be exactly the same. All other fields must be similar",
315
+ )
316
+
317
+ result_dict = _parse_json_dict(result)
318
+ self.analysis = json.dumps(result_dict)
319
+
320
+ if result_dict["outcome"] == "UNDETERMINED":
321
+ return
322
+
323
+ if (
324
+ result_dict["outcome"] == "ERROR"
325
+ or result_dict["outcome"] not in self.potential_outcomes
326
+ ):
327
+ self.status = Status.ERROR.value
328
+ return
329
+
330
+ self.outcome = result_dict["outcome"]
331
+ self.status = Status.RESOLVED.value
332
+
333
+ @gl.public.view
334
+ def get_dict(self) -> dict[str, str]:
335
+ return {
336
+ "title": self.title,
337
+ "description": self.description,
338
+ "potential_outcomes": list(self.potential_outcomes),
339
+ "rules": list(self.rules),
340
+ "data_source_domains": list(self.data_source_domains),
341
+ "resolution_urls": list(self.resolution_urls),
342
+ "status": self.status,
343
+ "earliest_resolution_date": self.earliest_resolution_date,
344
+ "analysis": self.analysis,
345
+ "outcome": self.outcome,
346
+ "prediction_market_id": self.prediction_market_id,
347
+ }
348
+
349
+ @gl.public.view
350
+ def get_status(self) -> str:
351
+ return self.status
352
+
353
+
354
+ def _parse_json_dict(json_str: str) -> dict:
355
+ """
356
+ Used to sanitize the JSON output from the LLM.
357
+ Remove everything before the first '{' and after the last '}', and remove trailing commas before closing braces/brackets
358
+ """
359
+ first_brace = json_str.find("{")
360
+ last_brace = json_str.rfind("}")
361
+ json_str = json_str[first_brace : last_brace + 1]
362
+
363
+ # Remove trailing commas before closing braces/brackets
364
+ import re
365
+
366
+ json_str = re.sub(r",(?!\s*?[\{\[\"\'\w])", "", json_str)
367
+ print(json_str)
368
+
369
+ return json.loads(json_str)
@@ -0,0 +1,47 @@
1
+ # { "Depends": "py-genlayer:test" }
2
+
3
+ from genlayer import *
4
+
5
+
6
+ class Registry(gl.Contract):
7
+ # Declare persistent storage fields
8
+ contract_addresses: DynArray[str]
9
+ intelligent_oracle_code: str
10
+
11
+ def __init__(self, intelligent_oracle_code: str):
12
+ self.intelligent_oracle_code = intelligent_oracle_code
13
+
14
+ @gl.public.write
15
+ def create_new_prediction_market(
16
+ self,
17
+ prediction_market_id: str,
18
+ title: str,
19
+ description: str,
20
+ potential_outcomes: list[str],
21
+ rules: list[str],
22
+ data_source_domains: list[str],
23
+ resolution_urls: list[str],
24
+ earliest_resolution_date: str,
25
+ ) -> None:
26
+ registered_contracts = len(self.contract_addresses)
27
+ contract_address = gl.deploy_contract(
28
+ code=self.intelligent_oracle_code.encode("utf-8"),
29
+ args=[
30
+ prediction_market_id,
31
+ title,
32
+ description,
33
+ potential_outcomes,
34
+ rules,
35
+ data_source_domains,
36
+ resolution_urls,
37
+ earliest_resolution_date,
38
+ ],
39
+ salt_nonce=registered_contracts + 1,
40
+ )
41
+ print("contract_address", contract_address)
42
+ print("contract_address type", type(contract_address))
43
+ self.contract_addresses.append(contract_address.as_hex)
44
+
45
+ @gl.public.view
46
+ def get_contract_addresses(self) -> list[str]:
47
+ return list(self.contract_addresses)
@@ -0,0 +1,69 @@
1
+ # { "Depends": "py-genlayer:test" }
2
+
3
+ import json
4
+
5
+ from genlayer import *
6
+
7
+
8
+ class LlmErc20(gl.Contract):
9
+ balances: TreeMap[Address, u256]
10
+
11
+ def __init__(self, total_supply: int) -> None:
12
+ self.balances[gl.message.sender_address] = u256(total_supply)
13
+
14
+ @gl.public.write
15
+ def transfer(self, amount: int, to_address: str) -> None:
16
+ input = f"""
17
+ You keep track of transactions between users and their balance in coins.
18
+ The current balance for all users in JSON format is:
19
+ {json.dumps(self.get_balances())}
20
+ The transaction to compute is: {{
21
+ sender: "{gl.message.sender_address.as_hex}",
22
+ recipient: "{Address(to_address).as_hex}",
23
+ amount: {amount},
24
+ }}
25
+
26
+ """
27
+ task = """For every transaction, validate that the user sending the Coins has
28
+ enough balance. If any transaction is invalid, it shouldn't be processed.
29
+ Update the balances based on the valid transactions only.
30
+ Given the current balance in JSON format and the transaction provided,
31
+ please provide the result of your calculation with the following format:
32
+ {{
33
+ "transaction_success": bool, // Whether the transaction was successful
34
+ "transaction_error": str, // Empty if transaction is successful
35
+ "updated_balances": object<str, int> // Updated balances after the transaction
36
+ }}
37
+
38
+ It is mandatory that you respond only using the JSON format above,
39
+ nothing else. Don't include any other words or characters,
40
+ your output must be only JSON without any formatting prefix or suffix.
41
+ This result should be perfectly parsable by a JSON parser without errors.
42
+ """
43
+
44
+ criteria = """
45
+ The balance of the sender should have decreased by the amount sent.
46
+ The balance of the receiver should have increased by the amount sent.
47
+ The total sum of all balances should remain the same before and after the transaction"""
48
+
49
+ final_result = (
50
+ gl.eq_principle_prompt_non_comparative(
51
+ lambda: input,
52
+ task=task,
53
+ criteria=criteria,
54
+ )
55
+ .replace("```json", "")
56
+ .replace("```", "")
57
+ )
58
+ print("final_result: ", final_result)
59
+ result_json = json.loads(final_result)
60
+ for k, v in result_json["updated_balances"].items():
61
+ self.balances[Address(k)] = v
62
+
63
+ @gl.public.view
64
+ def get_balances(self) -> dict[str, int]:
65
+ return {k.as_hex: v for k, v in self.balances.items()}
66
+
67
+ @gl.public.view
68
+ def get_balance_of(self, address: str) -> int:
69
+ return self.balances.get(Address(address), 0)
@@ -0,0 +1,67 @@
1
+ # {
2
+ # "Seq": [
3
+ # { "Depends": "py-lib-genlayermodelwrappers:test" },
4
+ # { "Depends": "py-genlayer:test" }
5
+ # ]
6
+ # }
7
+
8
+ import numpy as np
9
+ from genlayer import *
10
+ import genlayermodelwrappers
11
+ from dataclasses import dataclass
12
+ import typing
13
+
14
+
15
+ @allow_storage
16
+ @dataclass
17
+ class StoreValue:
18
+ log_id: u256
19
+ text: str
20
+
21
+
22
+ # contract class
23
+ class LogIndexer(gl.Contract):
24
+ vector_store: VecDB[np.float32, typing.Literal[384], StoreValue]
25
+
26
+ def __init__(self):
27
+ pass
28
+
29
+ def get_embedding_generator(self):
30
+ return genlayermodelwrappers.SentenceTransformer("all-MiniLM-L6-v2")
31
+
32
+ def get_embedding(
33
+ self, txt: str
34
+ ) -> np.ndarray[tuple[typing.Literal[384]], np.dtypes.Float32DType]:
35
+ return self.get_embedding_generator()(txt)
36
+
37
+ @gl.public.view
38
+ def get_closest_vector(self, text: str) -> dict | None:
39
+ emb = self.get_embedding(text)
40
+ result = list(self.vector_store.knn(emb, 1))
41
+ if len(result) == 0:
42
+ return None
43
+ result = result[0]
44
+ return {
45
+ "vector": list(str(x) for x in result.key),
46
+ "similarity": str(1 - result.distance),
47
+ "id": result.value.log_id,
48
+ "text": result.value.text,
49
+ }
50
+
51
+ @gl.public.write
52
+ def add_log(self, log: str, log_id: int) -> None:
53
+ emb = self.get_embedding(log)
54
+ self.vector_store.insert(emb, StoreValue(text=log, log_id=u256(log_id)))
55
+
56
+ @gl.public.write
57
+ def update_log(self, log_id: int, log: str) -> None:
58
+ emb = self.get_embedding(log)
59
+ for elem in self.vector_store.knn(emb, 2):
60
+ if elem.value.text == log:
61
+ elem.value.log_id = u256(log_id)
62
+
63
+ @gl.public.write
64
+ def remove_log(self, id: int) -> None:
65
+ for el in self.vector_store:
66
+ if el.value.log_id == id:
67
+ el.remove()
@@ -0,0 +1,20 @@
1
+ from genlayer import *
2
+
3
+
4
+ class MultiFileContract(gl.Contract):
5
+ other_addr: Address
6
+
7
+ def __init__(self):
8
+ with open("/contract/other.py", "rt") as f:
9
+ text = f.read()
10
+ self.other_addr = gl.deploy_contract(
11
+ code=text.encode("utf-8"), args=["123"], salt_nonce=1
12
+ )
13
+
14
+ @gl.public.write
15
+ def wait(self) -> None:
16
+ pass
17
+
18
+ @gl.public.view
19
+ def test(self) -> str:
20
+ return gl.ContractAt(self.other_addr).view().test()
@@ -0,0 +1,14 @@
1
+ # { "Depends": "py-genlayer:test" }
2
+
3
+ from genlayer import *
4
+
5
+
6
+ class Other(gl.Contract):
7
+ data: str
8
+
9
+ def __init__(self, data: str):
10
+ self.data = data
11
+
12
+ @gl.public.view
13
+ def test(self) -> str:
14
+ return self.data
@@ -0,0 +1,28 @@
1
+ # { "Depends": "py-genlayer:test" }
2
+
3
+ from genlayer import *
4
+
5
+
6
+ class multi_read_erc20(gl.Contract):
7
+ balances: TreeMap[Address, TreeMap[Address, u256]]
8
+
9
+ def __init__(self):
10
+ pass
11
+
12
+ @gl.public.write
13
+ def update_token_balances(
14
+ self, account_address: str, token_contracts: list[str]
15
+ ) -> None:
16
+ for token_contract in token_contracts:
17
+ contract = gl.ContractAt(Address(token_contract))
18
+ balance = contract.view().get_balance_of(account_address)
19
+ self.balances.get_or_insert_default(Address(account_address))[
20
+ Address(token_contract)
21
+ ] = balance
22
+
23
+ @gl.public.view
24
+ def get_balances(self) -> dict[str, dict[str, int]]:
25
+ return {
26
+ k.as_hex: {k.as_hex: v for k, v in v.items()}
27
+ for k, v in self.balances.items()
28
+ }
@@ -0,0 +1,48 @@
1
+ # { "Depends": "py-genlayer:test" }
2
+
3
+ from genlayer import *
4
+
5
+
6
+ class MultiTentantStorage(gl.Contract):
7
+ """
8
+ Same functionality as UserStorage, but implemented with multiple storage contracts.
9
+ Each user is assigned to a storage contract, and all storage contracts are managed by this same contract.
10
+ This contract does not prevent users from directly interacting with the storage contracts, but it doesn't bother us for testing purposes.
11
+ This is done to test contract calls between different contracts.
12
+ """
13
+
14
+ all_storage_contracts: DynArray[Address]
15
+ available_storage_contracts: DynArray[Address]
16
+ mappings: TreeMap[
17
+ Address, Address
18
+ ] # mapping of user address to storage contract address
19
+
20
+ def __init__(self, storage_contracts: list[str]):
21
+ for el in storage_contracts:
22
+ self.all_storage_contracts.append(Address(el))
23
+ self.available_storage_contracts.append(Address(el))
24
+
25
+ @gl.public.view
26
+ def get_available_contracts(self) -> list[str]:
27
+ return [x.as_hex for x in self.available_storage_contracts]
28
+
29
+ @gl.public.view
30
+ def get_all_storages(self) -> dict[str, str]:
31
+ return {
32
+ storage_contract.as_hex: gl.ContractAt(storage_contract)
33
+ .view()
34
+ .get_storage()
35
+ for storage_contract in self.all_storage_contracts
36
+ }
37
+
38
+ @gl.public.write
39
+ def update_storage(self, new_storage: str) -> None:
40
+ # Assign user to a storage contract if not already assigned
41
+ if gl.message.sender_address not in self.mappings:
42
+ self.mappings[gl.message.sender_address] = self.available_storage_contracts[
43
+ -1
44
+ ]
45
+ self.available_storage_contracts.pop()
46
+
47
+ contract_to_use = self.mappings[gl.message.sender_address]
48
+ gl.ContractAt(contract_to_use).emit(gas=100000).update_storage(new_storage)
@@ -0,0 +1,14 @@
1
+ # { "Depends": "py-genlayer:test" }
2
+
3
+ from genlayer import *
4
+
5
+
6
+ class read_erc20(gl.Contract):
7
+ token_contract: Address
8
+
9
+ def __init__(self, token_contract: str):
10
+ self.token_contract = Address(token_contract)
11
+
12
+ @gl.public.view
13
+ def get_balance_of(self, account_address: str) -> int:
14
+ return gl.ContractAt(self.token_contract).view().get_balance_of(account_address)