genlayer-test 0.4.0__py3-none-any.whl → 1.0.0__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.
- {genlayer_test-0.4.0.dist-info → genlayer_test-1.0.0.dist-info}/METADATA +54 -8
- genlayer_test-1.0.0.dist-info/RECORD +75 -0
- gltest/__init__.py +7 -6
- gltest/{glchain/client.py → clients.py} +1 -1
- gltest/contracts/__init__.py +4 -0
- gltest/contracts/contract.py +193 -0
- gltest/{glchain/contract.py → contracts/contract_factory.py} +17 -135
- gltest/contracts/contract_functions.py +61 -0
- gltest/contracts/method_stats.py +163 -0
- gltest/contracts/stats_collector.py +259 -0
- gltest/contracts/utils.py +12 -0
- gltest/fixtures.py +2 -6
- gltest/helpers/take_snapshot.py +1 -1
- gltest/logging.py +17 -0
- gltest_cli/config/constants.py +1 -0
- gltest_cli/config/plugin.py +37 -0
- gltest_cli/config/pytest_context.py +9 -0
- gltest_cli/config/types.py +16 -0
- gltest_cli/config/user.py +9 -6
- gltest_cli/logging.py +1 -1
- tests/examples/tests/test_football_prediction_market.py +2 -2
- tests/examples/tests/test_intelligent_oracle_factory.py +6 -6
- tests/examples/tests/test_llm_erc20.py +5 -5
- tests/examples/tests/test_llm_erc20_analyze.py +50 -0
- tests/examples/tests/test_log_indexer.py +23 -11
- tests/examples/tests/test_multi_file_contract.py +2 -2
- tests/examples/tests/test_multi_file_contract_legacy.py +2 -2
- tests/examples/tests/test_multi_read_erc20.py +14 -12
- tests/examples/tests/test_multi_tenant_storage.py +11 -7
- tests/examples/tests/test_read_erc20.py +1 -1
- tests/examples/tests/test_storage.py +4 -4
- tests/examples/tests/test_storage_legacy.py +5 -3
- tests/examples/tests/test_user_storage.py +20 -10
- tests/examples/tests/test_wizard_of_coin.py +1 -1
- tests/gltest_cli/config/test_general_config.py +149 -0
- tests/gltest_cli/config/test_plugin.py +78 -0
- tests/gltest_cli/config/test_user.py +51 -1
- genlayer_test-0.4.0.dist-info/RECORD +0 -66
- gltest/glchain/__init__.py +0 -16
- {genlayer_test-0.4.0.dist-info → genlayer_test-1.0.0.dist-info}/WHEEL +0 -0
- {genlayer_test-0.4.0.dist-info → genlayer_test-1.0.0.dist-info}/entry_points.txt +0 -0
- {genlayer_test-0.4.0.dist-info → genlayer_test-1.0.0.dist-info}/licenses/LICENSE +0 -0
- {genlayer_test-0.4.0.dist-info → genlayer_test-1.0.0.dist-info}/top_level.txt +0 -0
- /gltest/{glchain/account.py → accounts.py} +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: genlayer-test
|
3
|
-
Version: 0.
|
3
|
+
Version: 1.0.0
|
4
4
|
Summary: GenLayer Testing Suite
|
5
5
|
Author: GenLayer
|
6
6
|
License-Expression: MIT
|
@@ -15,7 +15,7 @@ Description-Content-Type: text/markdown
|
|
15
15
|
License-File: LICENSE
|
16
16
|
Requires-Dist: pytest
|
17
17
|
Requires-Dist: setuptools>=77.0
|
18
|
-
Requires-Dist: genlayer-py==0.
|
18
|
+
Requires-Dist: genlayer-py==0.7.1
|
19
19
|
Requires-Dist: colorama>=0.4.6
|
20
20
|
Requires-Dist: pyyaml
|
21
21
|
Requires-Dist: python-dotenv
|
@@ -59,8 +59,8 @@ contract = factory.deploy(account=other_account)
|
|
59
59
|
assert contract.account == other_account
|
60
60
|
|
61
61
|
# Interact with the contract
|
62
|
-
result = contract.get_value() # Read method
|
63
|
-
tx_receipt = contract.set_value(args=["new_value"]) # Write method
|
62
|
+
result = contract.get_value().call() # Read method
|
63
|
+
tx_receipt = contract.set_value(args=["new_value"]).transact() # Write method
|
64
64
|
|
65
65
|
assert tx_execution_succeeded(tx_receipt)
|
66
66
|
```
|
@@ -320,7 +320,7 @@ def test_deployment():
|
|
320
320
|
|
321
321
|
### Read Methods
|
322
322
|
|
323
|
-
Reading from the contract
|
323
|
+
Reading from the contract requires calling `.call()` on the method:
|
324
324
|
|
325
325
|
```python
|
326
326
|
from gltest import get_contract_factory
|
@@ -332,7 +332,7 @@ def test_read_methods():
|
|
332
332
|
contract = factory.deploy()
|
333
333
|
|
334
334
|
# Call a read-only method
|
335
|
-
result = contract.
|
335
|
+
result = contract.get_storage(args=[]).call()
|
336
336
|
|
337
337
|
# Assert the result matches the initial value
|
338
338
|
assert result == "initial_value"
|
@@ -340,7 +340,7 @@ def test_read_methods():
|
|
340
340
|
|
341
341
|
### Write Methods
|
342
342
|
|
343
|
-
Writing to the contract requires transaction
|
343
|
+
Writing to the contract requires calling `.transact()` on the method. Method arguments are passed to the write method, while transaction parameters are passed to `.transact()`:
|
344
344
|
|
345
345
|
```python
|
346
346
|
from gltest import get_contract_factory
|
@@ -354,6 +354,7 @@ def test_write_methods():
|
|
354
354
|
# Call a write method with arguments
|
355
355
|
tx_receipt = contract.update_storage(
|
356
356
|
args=["new_value"], # Method arguments
|
357
|
+
).transact(
|
357
358
|
value=0, # Optional: amount of native currency to send
|
358
359
|
consensus_max_rotations=3, # Optional: max consensus rotations
|
359
360
|
leader_only=False, # Optional: whether to run only on leader
|
@@ -365,7 +366,7 @@ def test_write_methods():
|
|
365
366
|
assert tx_execution_succeeded(tx_receipt)
|
366
367
|
|
367
368
|
# Verify the value was updated
|
368
|
-
assert contract.get_storage() == "new_value"
|
369
|
+
assert contract.get_storage().call() == "new_value"
|
369
370
|
```
|
370
371
|
|
371
372
|
### Assertions
|
@@ -503,6 +504,50 @@ Fixtures help maintain clean, DRY test code by:
|
|
503
504
|
- Ensuring consistent test environments
|
504
505
|
- Managing resource cleanup automatically
|
505
506
|
- Providing appropriate scoping for performance
|
507
|
+
### Statistical Analysis with `.analyze()`
|
508
|
+
|
509
|
+
The GenLayer Testing Suite provides a powerful `.analyze()` method for write operations that performs statistical analysis through multiple simulation runs. This is particularly useful for testing LLM-based contracts where outputs may vary:
|
510
|
+
|
511
|
+
```python
|
512
|
+
from gltest import get_contract_factory
|
513
|
+
|
514
|
+
def test_analyze_method():
|
515
|
+
factory = get_contract_factory("LlmContract")
|
516
|
+
contract = factory.deploy()
|
517
|
+
|
518
|
+
# Analyze a write method's behavior across multiple runs
|
519
|
+
analysis = contract.process_with_llm(args=["input_data"]).analyze(
|
520
|
+
provider="openai", # LLM provider
|
521
|
+
model="gpt-4o", # Model to use
|
522
|
+
runs=100, # Number of simulation runs (default: 100)
|
523
|
+
config=None, # Optional: provider-specific config
|
524
|
+
plugin=None, # Optional: plugin name
|
525
|
+
plugin_config=None, # Optional: plugin configuration
|
526
|
+
)
|
527
|
+
|
528
|
+
# Access analysis results
|
529
|
+
print(f"Method: {analysis.method}")
|
530
|
+
print(f"Success rate: {analysis.success_rate:.2f}%")
|
531
|
+
print(f"Reliability score: {analysis.reliability_score:.2f}%")
|
532
|
+
print(f"Unique states: {analysis.unique_states}")
|
533
|
+
print(f"Execution time: {analysis.execution_time:.1f}s")
|
534
|
+
|
535
|
+
# The analysis returns a MethodStatsSummary object with:
|
536
|
+
# - method: The contract method name
|
537
|
+
# - args: Arguments passed to the method
|
538
|
+
# - total_runs: Total number of simulation runs
|
539
|
+
# - successful_runs: Number of successful executions
|
540
|
+
# - failed_runs: Number of failed executions
|
541
|
+
# - unique_states: Number of unique contract states observed
|
542
|
+
# - reliability_score: Percentage of runs with the most common state
|
543
|
+
# - execution_time: Total time for all simulations
|
544
|
+
```
|
545
|
+
|
546
|
+
The `.analyze()` method helps you:
|
547
|
+
- Test non-deterministic contract methods
|
548
|
+
- Measure consistency of LLM-based operations
|
549
|
+
- Identify edge cases and failure patterns
|
550
|
+
- Benchmark performance across multiple runs
|
506
551
|
|
507
552
|
## 📝 Best Practices
|
508
553
|
|
@@ -546,6 +591,7 @@ Fixtures help maintain clean, DRY test code by:
|
|
546
591
|
```python
|
547
592
|
tx_receipt = contract.set_value(
|
548
593
|
args=["new_value"],
|
594
|
+
).transact(
|
549
595
|
wait_interval=2, # Increase wait interval between status checks
|
550
596
|
wait_retries=20, # Increase number of retry attempts
|
551
597
|
)
|
@@ -0,0 +1,75 @@
|
|
1
|
+
genlayer_test-1.0.0.dist-info/licenses/LICENSE,sha256=che_H4vE0QUx3HvWrAa1_jDEVInift0U6VO15-QqEls,1064
|
2
|
+
gltest/__init__.py,sha256=qoBV3IgDJr8uh9262XsWNMhi-ilkSgMqKVC9FVMk7cw,372
|
3
|
+
gltest/accounts.py,sha256=HUmWguJMolggQaZNRPw-LGlRlQCjLLdUanKRowMv6pI,812
|
4
|
+
gltest/assertions.py,sha256=0dEk0VxcHK4I7GZPHxJmz-2jaA60V499gOSR74rZbfM,1748
|
5
|
+
gltest/clients.py,sha256=1dX6wmG3QCevQRLbSaFlHymZSb-sJ5aYwet1IoX2nbA,1554
|
6
|
+
gltest/exceptions.py,sha256=deJPmrTe5gF33qkkKF2IVJY7lc_knI7Ql3N7jZ8aLZs,510
|
7
|
+
gltest/fixtures.py,sha256=EJXmqcC3LD03v07mepacFl58lAdhbLj6bP5rtALYISk,2507
|
8
|
+
gltest/logging.py,sha256=jAkHsuMm-AEx1Xu1srU6W-0YzTwXJB48mCK-OVzAiN4,342
|
9
|
+
gltest/types.py,sha256=BODmwTr2gAUEiO9FjiuTiWwuKvXgo4xZWstQWNUfnlw,156
|
10
|
+
gltest/artifacts/__init__.py,sha256=qTt3TE19gVNWnQLUlt5aDe4nNvJ2YJ1jzDkMmYIsCG0,194
|
11
|
+
gltest/artifacts/contract.py,sha256=KChpmfjZod_0dVB8y-dvWz6IVm7QlIJsgG2ArtvVDaU,6457
|
12
|
+
gltest/contracts/__init__.py,sha256=A9bvEtYOoqoHS8TLlFBfmNOnfwdsJPEf-AZuikagCHM,166
|
13
|
+
gltest/contracts/contract.py,sha256=NGgciplaYINL_unJvLnOSWL4f8cNlHGMOEEbc6a1kw4,6402
|
14
|
+
gltest/contracts/contract_factory.py,sha256=jjzxgsLW6AiQ4h8LcZwIzdq5Uqm-xkj_JZNhN0djijc,6691
|
15
|
+
gltest/contracts/contract_functions.py,sha256=E-C9RnBkz3FeJRiswUaZbcDn4HRSWitsdwegSjX7E_Q,2089
|
16
|
+
gltest/contracts/method_stats.py,sha256=zWWjvf7K5VC4yrHpDIR717VF7LYp1RaZ1Hr_RZvWxJA,5150
|
17
|
+
gltest/contracts/stats_collector.py,sha256=fuCc8L8hd0tsVGzH4adtZWwPa7ORf0A0zR5Dt1w92Qk,9033
|
18
|
+
gltest/contracts/utils.py,sha256=TTXgcXn9BuRIlKJrjwmU7R3l1IgXsXk2luM-r3lfbbg,296
|
19
|
+
gltest/helpers/__init__.py,sha256=I7HiTu_H7_hP65zY6Wl02r-5eAMr2eZvqBVmusuQLX4,180
|
20
|
+
gltest/helpers/fixture_snapshot.py,sha256=bMgvlEVQBGIQzj7NOyosXWlphI1H2C1o75Zo0C-kGfQ,1931
|
21
|
+
gltest/helpers/take_snapshot.py,sha256=-QkaBvFG4ZsNKv_nCSEsy5Ze1pICOHxVhReSeQmZUlY,1276
|
22
|
+
gltest_cli/logging.py,sha256=YRWIGwCJIkaB747oQvmS2tzF-B7zymdEMJznrlfyQYA,1245
|
23
|
+
gltest_cli/main.py,sha256=Ti2-0Ev1x5_cM0D1UKqdgaDt80CDHEQGtdRne2qLm4M,53
|
24
|
+
gltest_cli/config/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
25
|
+
gltest_cli/config/constants.py,sha256=3iSK337AeupyYl_6Sf8MR_o91XfLmul8m1rVmi3Fvmo,342
|
26
|
+
gltest_cli/config/general.py,sha256=ezpoGsT8grO9zQH6RugV14b1GzeFt-htYToHQBJhNvY,186
|
27
|
+
gltest_cli/config/plugin.py,sha256=gMK1xrkJ4G0ddCjKAFB7lEXecdvoZiwndwwI4Srn66w,4857
|
28
|
+
gltest_cli/config/pytest_context.py,sha256=Ze8JSkrwMTCE8jIhpzU_71CEXg92SiEPvSgNTp-gbS4,243
|
29
|
+
gltest_cli/config/types.py,sha256=w7KyINjM3_n4MYoRPfMBgudptjXjnSKPficAsqqSUTY,6344
|
30
|
+
gltest_cli/config/user.py,sha256=VSS7llCUJc2GSIexdL73FqkLthdiECwmQONO3-WYi7o,8242
|
31
|
+
tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
32
|
+
tests/conftest.py,sha256=RKdoE5_zcMimeojAoA_GSFI9du4pMzMi1vZ1njtfoAs,28
|
33
|
+
tests/examples/contracts/football_prediction_market.py,sha256=0Lm2x2F1DhmUP1fcfzGQAfc50tWFcaHliwyAzXIRFVw,3247
|
34
|
+
tests/examples/contracts/intelligent_oracle.py,sha256=cZNGbjKMXY-pimVmPIKIlS963Gd3L1JAipq0VBR1J5Q,12360
|
35
|
+
tests/examples/contracts/intelligent_oracle_factory.py,sha256=8lBEn3Atb0yUpXwlvnShlcRxCBTXCrrkoITDHWoWuHU,1499
|
36
|
+
tests/examples/contracts/llm_erc20.py,sha256=pOvSUszCtC_f5yDX0tZnj494Ce10uESZ09JLLE8V67o,2534
|
37
|
+
tests/examples/contracts/log_indexer.py,sha256=Nlf8XUt13ujam3k6hbbVrPHK-KJ366Csz1TBjc4P07g,1901
|
38
|
+
tests/examples/contracts/multi_read_erc20.py,sha256=28qYqn191Ro3rP7YJtZwL6Sc7JDXTeh8_QoqvdVPdqM,864
|
39
|
+
tests/examples/contracts/multi_tenant_storage.py,sha256=5F3MCKbzyHMFqLRT9hZNCd3RzjSJvAKVJwLFMeazwog,1906
|
40
|
+
tests/examples/contracts/read_erc20.py,sha256=RgH269F0x482WuLLYcacBnZsGJjhgJp6sG_33cV6Z-w,454
|
41
|
+
tests/examples/contracts/storage.py,sha256=3DD3qvzb0JkVcBtu240e5kaZWgkh-bu6YExqBUYvfaw,501
|
42
|
+
tests/examples/contracts/user_storage.py,sha256=2W2Iv-hQZMkAaPl2RY_F-OeJpD4IlPxgWzN6e1bTkKE,649
|
43
|
+
tests/examples/contracts/wizard_of_coin.py,sha256=Y8daPpoSKdM8wfbCPOIgEdpkLIA1ZMmeg6Hu5fBB-kU,1624
|
44
|
+
tests/examples/contracts/multi_file_contract/__init__.py,sha256=CCdaK5p12GDf35hgbBWURNM5KUn6SWAcuyieTmZwVWE,548
|
45
|
+
tests/examples/contracts/multi_file_contract/other.py,sha256=jHDtjUL3eAUgE6yOYKFw_RfAH7kIwk8CvxUjbWHNruk,236
|
46
|
+
tests/examples/tests/test_football_prediction_market.py,sha256=f2hfDK76WrNJZtFkTPKoPRR6bkmFLEssnlwwltSnU9A,1111
|
47
|
+
tests/examples/tests/test_intelligent_oracle_factory.py,sha256=wEcVAY0HHDf8oyVQ1kEY-2YfRJipkEJYooqW1wWwXls,8317
|
48
|
+
tests/examples/tests/test_llm_erc20.py,sha256=zb5F_7NgvZXhvqL2nULwzuTT6LGDprSy0WgrdjY7pZc,2096
|
49
|
+
tests/examples/tests/test_llm_erc20_analyze.py,sha256=ccWQenEOG-U4TJdu-0i-3qNM4xRRCHSr3Iwz7pxknws,1729
|
50
|
+
tests/examples/tests/test_log_indexer.py,sha256=46AqL7qquNc9GX2wxFxVcQXLqruMnPmxXl1yeB0-KZ4,2869
|
51
|
+
tests/examples/tests/test_multi_file_contract.py,sha256=ZC_zmEE-QtXZ1vxptFSWZw18fRZ5M3zqS7ZCZH1gIfc,569
|
52
|
+
tests/examples/tests/test_multi_file_contract_legacy.py,sha256=UTI7Thc8Wg-lotH9JiB5hp9ba6O-yFy1Ss-yuhlgljA,584
|
53
|
+
tests/examples/tests/test_multi_read_erc20.py,sha256=pSZUxoB33Z2EBtcvXxfUgwGSi_h7ryPoovkiNIfNAVQ,3713
|
54
|
+
tests/examples/tests/test_multi_tenant_storage.py,sha256=TjxN9ks5Wd0QkIszFKNSveNBdJ-wOb7O-fNwGQauXeY,2825
|
55
|
+
tests/examples/tests/test_read_erc20.py,sha256=vLGQwguaNnT497nSq-vt4LrXj4ehn5ZSgfPt0GVFoPc,1254
|
56
|
+
tests/examples/tests/test_storage.py,sha256=y46nPjM-Jd9FVJmaNE29RPqamzxVwYtPPWE_GlXUsls,774
|
57
|
+
tests/examples/tests/test_storage_legacy.py,sha256=STcoRKYP0JcwwNjFSTMpYGuOyXcw2DxGlemUivWp2B0,755
|
58
|
+
tests/examples/tests/test_user_storage.py,sha256=wk0r0AXfKNgI7Eeyc8noNlJKvZBFXDbXTQE_u19XxBQ,2927
|
59
|
+
tests/examples/tests/test_wizard_of_coin.py,sha256=AOQTanDsfZt9zIGkqZass_4BsGcVKTHzqRejN4KhSPI,854
|
60
|
+
tests/gltest/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
61
|
+
tests/gltest/artifact/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
62
|
+
tests/gltest/artifact/test_contract_definition.py,sha256=6R8THNFKKpG7brULzp63vT1_pPd_JFNp3ZS08CJJWrg,3642
|
63
|
+
tests/gltest/artifact/contracts/duplicate_ic_contract_1.py,sha256=bSWsUVjBy5cGtI72cjnkstsMzuUJbB3IG5JjTxOF-dc,500
|
64
|
+
tests/gltest/artifact/contracts/duplicate_ic_contract_2.py,sha256=bSWsUVjBy5cGtI72cjnkstsMzuUJbB3IG5JjTxOF-dc,500
|
65
|
+
tests/gltest/artifact/contracts/not_ic_contract.py,sha256=hQyGnYiiVceYdLI2WrvcFgPqzy1S4-YMb9FPhiHEGSA,510
|
66
|
+
tests/gltest/assertions/test_assertions.py,sha256=qzVrOdOM4xYtIy1sFHVAD_-naDHOequ23tEN0MELh0k,10781
|
67
|
+
tests/gltest_cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
68
|
+
tests/gltest_cli/config/test_general_config.py,sha256=T2haLj41-6tbEfybdXnEycIQQFNkdc_O07qmg_37dqM,5730
|
69
|
+
tests/gltest_cli/config/test_plugin.py,sha256=GYY56VR39-uI2WXK-rlzTxB5Ui2h9f5oF_kO0iNljRo,5862
|
70
|
+
tests/gltest_cli/config/test_user.py,sha256=40nEC-gM03Q86SnmJpkGfMRvKKtEkfx53qaGHj8XXGQ,13988
|
71
|
+
genlayer_test-1.0.0.dist-info/METADATA,sha256=g1QNA8KP65HqSApiaNYcunHwz1Ch2IO_RIkFcbVk5Vo,24143
|
72
|
+
genlayer_test-1.0.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
73
|
+
genlayer_test-1.0.0.dist-info/entry_points.txt,sha256=RWPcSArBpz_G4BYioh5L8Q8hyClRbSgzLimjcWMp-BQ,94
|
74
|
+
genlayer_test-1.0.0.dist-info/top_level.txt,sha256=-qiGZxTRBytujzgVcKpxjvQ-WNeUDjXa59ceGMwMpko,24
|
75
|
+
genlayer_test-1.0.0.dist-info/RECORD,,
|
gltest/__init__.py
CHANGED
@@ -1,14 +1,15 @@
|
|
1
|
-
from gltest.
|
2
|
-
|
1
|
+
from gltest.accounts import (
|
2
|
+
get_default_account,
|
3
|
+
get_accounts,
|
3
4
|
create_accounts,
|
4
|
-
|
5
|
+
create_account,
|
6
|
+
)
|
7
|
+
from gltest.clients import (
|
5
8
|
get_gl_client,
|
6
|
-
get_accounts,
|
7
|
-
get_default_account,
|
8
9
|
)
|
10
|
+
from gltest.contracts import get_contract_factory
|
9
11
|
|
10
12
|
__all__ = [
|
11
|
-
"find_contract_definition",
|
12
13
|
"create_account",
|
13
14
|
"create_accounts",
|
14
15
|
"get_contract_factory",
|
@@ -0,0 +1,193 @@
|
|
1
|
+
import types
|
2
|
+
from eth_account.signers.local import LocalAccount
|
3
|
+
from dataclasses import dataclass
|
4
|
+
from gltest.clients import get_gl_client
|
5
|
+
from gltest.types import CalldataEncodable, GenLayerTransaction, TransactionStatus
|
6
|
+
from typing import List, Any, Optional, Dict, Callable
|
7
|
+
from gltest_cli.config.general import get_general_config
|
8
|
+
from .contract_functions import ContractFunction
|
9
|
+
from .stats_collector import StatsCollector, SimulationConfig
|
10
|
+
|
11
|
+
|
12
|
+
def read_contract_wrapper(
|
13
|
+
self,
|
14
|
+
method_name: str,
|
15
|
+
args: Optional[List[CalldataEncodable]] = None,
|
16
|
+
) -> Any:
|
17
|
+
"""
|
18
|
+
Wrapper to the contract read method.
|
19
|
+
"""
|
20
|
+
|
21
|
+
def call_method():
|
22
|
+
client = get_gl_client()
|
23
|
+
return client.read_contract(
|
24
|
+
address=self.address,
|
25
|
+
function_name=method_name,
|
26
|
+
account=self.account,
|
27
|
+
args=args,
|
28
|
+
)
|
29
|
+
|
30
|
+
return ContractFunction(
|
31
|
+
method_name=method_name,
|
32
|
+
read_only=True,
|
33
|
+
call_method=call_method,
|
34
|
+
)
|
35
|
+
|
36
|
+
|
37
|
+
def write_contract_wrapper(
|
38
|
+
self,
|
39
|
+
method_name: str,
|
40
|
+
args: Optional[List[CalldataEncodable]] = None,
|
41
|
+
) -> GenLayerTransaction:
|
42
|
+
"""
|
43
|
+
Wrapper to the contract write method.
|
44
|
+
"""
|
45
|
+
|
46
|
+
def transact_method(
|
47
|
+
value: int = 0,
|
48
|
+
consensus_max_rotations: Optional[int] = None,
|
49
|
+
leader_only: bool = False,
|
50
|
+
wait_transaction_status: TransactionStatus = TransactionStatus.FINALIZED,
|
51
|
+
wait_interval: Optional[int] = None,
|
52
|
+
wait_retries: Optional[int] = None,
|
53
|
+
wait_triggered_transactions: bool = True,
|
54
|
+
wait_triggered_transactions_status: TransactionStatus = TransactionStatus.FINALIZED,
|
55
|
+
):
|
56
|
+
"""
|
57
|
+
Transact the contract method.
|
58
|
+
"""
|
59
|
+
general_config = get_general_config()
|
60
|
+
actual_wait_interval = (
|
61
|
+
wait_interval
|
62
|
+
if wait_interval is not None
|
63
|
+
else general_config.get_default_wait_interval()
|
64
|
+
)
|
65
|
+
actual_wait_retries = (
|
66
|
+
wait_retries
|
67
|
+
if wait_retries is not None
|
68
|
+
else general_config.get_default_wait_retries()
|
69
|
+
)
|
70
|
+
client = get_gl_client()
|
71
|
+
tx_hash = client.write_contract(
|
72
|
+
address=self.address,
|
73
|
+
function_name=method_name,
|
74
|
+
account=self.account,
|
75
|
+
value=value,
|
76
|
+
consensus_max_rotations=consensus_max_rotations,
|
77
|
+
leader_only=leader_only,
|
78
|
+
args=args,
|
79
|
+
)
|
80
|
+
receipt = client.wait_for_transaction_receipt(
|
81
|
+
transaction_hash=tx_hash,
|
82
|
+
status=wait_transaction_status,
|
83
|
+
interval=actual_wait_interval,
|
84
|
+
retries=actual_wait_retries,
|
85
|
+
)
|
86
|
+
if wait_triggered_transactions:
|
87
|
+
triggered_transactions = receipt["triggered_transactions"]
|
88
|
+
for triggered_transaction in triggered_transactions:
|
89
|
+
client.wait_for_transaction_receipt(
|
90
|
+
transaction_hash=triggered_transaction,
|
91
|
+
status=wait_triggered_transactions_status,
|
92
|
+
interval=actual_wait_interval,
|
93
|
+
retries=actual_wait_retries,
|
94
|
+
)
|
95
|
+
return receipt
|
96
|
+
|
97
|
+
def analyze_method(
|
98
|
+
provider: str,
|
99
|
+
model: str,
|
100
|
+
config: Optional[Dict[str, Any]] = None,
|
101
|
+
plugin: Optional[str] = None,
|
102
|
+
plugin_config: Optional[Dict[str, Any]] = None,
|
103
|
+
runs: int = 100,
|
104
|
+
):
|
105
|
+
"""
|
106
|
+
Analyze the contract method using StatsCollector.
|
107
|
+
"""
|
108
|
+
collector = StatsCollector(
|
109
|
+
contract_address=self.address,
|
110
|
+
method_name=method_name,
|
111
|
+
account=self.account,
|
112
|
+
args=args,
|
113
|
+
)
|
114
|
+
sim_config = SimulationConfig(
|
115
|
+
provider=provider,
|
116
|
+
model=model,
|
117
|
+
config=config,
|
118
|
+
plugin=plugin,
|
119
|
+
plugin_config=plugin_config,
|
120
|
+
)
|
121
|
+
sim_results = collector.run_simulations(sim_config, runs)
|
122
|
+
return collector.analyze_results(sim_results, runs, sim_config)
|
123
|
+
|
124
|
+
return ContractFunction(
|
125
|
+
method_name=method_name,
|
126
|
+
read_only=False,
|
127
|
+
transact_method=transact_method,
|
128
|
+
analyze_method=analyze_method,
|
129
|
+
)
|
130
|
+
|
131
|
+
|
132
|
+
def contract_function_factory(method_name: str, read_only: bool) -> Callable:
|
133
|
+
"""
|
134
|
+
Create a function that interacts with a specific contract method.
|
135
|
+
"""
|
136
|
+
if read_only:
|
137
|
+
return lambda self, args=None: read_contract_wrapper(self, method_name, args)
|
138
|
+
return lambda self, args=None: write_contract_wrapper(self, method_name, args)
|
139
|
+
|
140
|
+
|
141
|
+
@dataclass
|
142
|
+
class Contract:
|
143
|
+
"""
|
144
|
+
Class to interact with a contract, its methods
|
145
|
+
are implemented dynamically at build time.
|
146
|
+
"""
|
147
|
+
|
148
|
+
address: str
|
149
|
+
account: Optional[LocalAccount] = None
|
150
|
+
_schema: Optional[Dict[str, Any]] = None
|
151
|
+
|
152
|
+
@classmethod
|
153
|
+
def new(
|
154
|
+
cls,
|
155
|
+
address: str,
|
156
|
+
schema: Dict[str, Any],
|
157
|
+
account: Optional[LocalAccount] = None,
|
158
|
+
) -> "Contract":
|
159
|
+
"""
|
160
|
+
Build the methods from the schema.
|
161
|
+
"""
|
162
|
+
if not isinstance(schema, dict) or "methods" not in schema:
|
163
|
+
raise ValueError("Invalid schema: must contain 'methods' field")
|
164
|
+
instance = cls(address=address, _schema=schema, account=account)
|
165
|
+
instance._build_methods_from_schema()
|
166
|
+
return instance
|
167
|
+
|
168
|
+
def _build_methods_from_schema(self):
|
169
|
+
"""
|
170
|
+
Build the methods from the schema.
|
171
|
+
"""
|
172
|
+
if self._schema is None:
|
173
|
+
raise ValueError("No schema provided")
|
174
|
+
for method_name, method_info in self._schema["methods"].items():
|
175
|
+
if not isinstance(method_info, dict) or "readonly" not in method_info:
|
176
|
+
raise ValueError(
|
177
|
+
f"Invalid method info for '{method_name}': must contain 'readonly' field"
|
178
|
+
)
|
179
|
+
method_func = contract_function_factory(
|
180
|
+
method_name, method_info["readonly"]
|
181
|
+
)
|
182
|
+
bound_method = types.MethodType(method_func, self)
|
183
|
+
setattr(self, method_name, bound_method)
|
184
|
+
|
185
|
+
def connect(self, account: LocalAccount) -> "Contract":
|
186
|
+
"""
|
187
|
+
Create a new instance of the contract with the same methods and a different account.
|
188
|
+
"""
|
189
|
+
new_contract = self.__class__(
|
190
|
+
address=self.address, account=account, _schema=self._schema
|
191
|
+
)
|
192
|
+
new_contract._build_methods_from_schema()
|
193
|
+
return new_contract
|
@@ -1,145 +1,27 @@
|
|
1
|
+
from dataclasses import dataclass
|
2
|
+
from typing import Type, Union, Optional, List, Any
|
3
|
+
from pathlib import Path
|
1
4
|
from eth_typing import (
|
2
5
|
Address,
|
3
6
|
ChecksumAddress,
|
4
7
|
)
|
5
8
|
from eth_account.signers.local import LocalAccount
|
6
|
-
from typing import Union
|
7
|
-
from pathlib import Path
|
8
|
-
from dataclasses import dataclass
|
9
9
|
from gltest.artifacts import (
|
10
10
|
find_contract_definition_from_name,
|
11
11
|
find_contract_definition_from_path,
|
12
12
|
)
|
13
|
+
from gltest.clients import (
|
14
|
+
get_gl_client,
|
15
|
+
get_gl_hosted_studio_client,
|
16
|
+
get_local_client,
|
17
|
+
)
|
18
|
+
from .contract import Contract
|
19
|
+
from gltest.logging import logger
|
20
|
+
from gltest.types import TransactionStatus
|
13
21
|
from gltest.assertions import tx_execution_failed
|
14
22
|
from gltest.exceptions import DeploymentError
|
15
|
-
from .client import get_gl_client, get_gl_hosted_studio_client, get_local_client
|
16
|
-
from gltest.types import CalldataEncodable, GenLayerTransaction, TransactionStatus
|
17
|
-
from typing import List, Any, Type, Optional, Dict, Callable
|
18
|
-
import types
|
19
23
|
from gltest_cli.config.general import get_general_config
|
20
|
-
from gltest_cli.logging import logger
|
21
|
-
|
22
|
-
|
23
|
-
@dataclass
|
24
|
-
class Contract:
|
25
|
-
"""
|
26
|
-
Class to interact with a contract, its methods
|
27
|
-
are implemented dynamically at build time.
|
28
|
-
"""
|
29
|
-
|
30
|
-
address: str
|
31
|
-
account: Optional[LocalAccount] = None
|
32
|
-
_schema: Optional[Dict[str, Any]] = None
|
33
|
-
|
34
|
-
@classmethod
|
35
|
-
def new(
|
36
|
-
cls,
|
37
|
-
address: str,
|
38
|
-
schema: Dict[str, Any],
|
39
|
-
account: Optional[LocalAccount] = None,
|
40
|
-
) -> "Contract":
|
41
|
-
"""
|
42
|
-
Build the methods from the schema.
|
43
|
-
"""
|
44
|
-
if not isinstance(schema, dict) or "methods" not in schema:
|
45
|
-
raise ValueError("Invalid schema: must contain 'methods' field")
|
46
|
-
instance = cls(address=address, _schema=schema, account=account)
|
47
|
-
instance._build_methods_from_schema()
|
48
|
-
return instance
|
49
|
-
|
50
|
-
def _build_methods_from_schema(self):
|
51
|
-
if self._schema is None:
|
52
|
-
raise ValueError("No schema provided")
|
53
|
-
for method_name, method_info in self._schema["methods"].items():
|
54
|
-
if not isinstance(method_info, dict) or "readonly" not in method_info:
|
55
|
-
raise ValueError(
|
56
|
-
f"Invalid method info for '{method_name}': must contain 'readonly' field"
|
57
|
-
)
|
58
|
-
method_func = self.contract_method_factory(
|
59
|
-
method_name, method_info["readonly"]
|
60
|
-
)
|
61
|
-
bound_method = types.MethodType(method_func, self)
|
62
|
-
setattr(self, method_name, bound_method)
|
63
|
-
|
64
|
-
def connect(self, account: LocalAccount) -> "Contract":
|
65
|
-
"""
|
66
|
-
Create a new instance of the contract with the same methods and a different account.
|
67
|
-
"""
|
68
|
-
new_contract = self.__class__(
|
69
|
-
address=self.address, account=account, _schema=self._schema
|
70
|
-
)
|
71
|
-
new_contract._build_methods_from_schema()
|
72
|
-
return new_contract
|
73
24
|
|
74
|
-
@staticmethod
|
75
|
-
def contract_method_factory(method_name: str, read_only: bool) -> Callable:
|
76
|
-
"""
|
77
|
-
Create a function that interacts with a specific contract method.
|
78
|
-
"""
|
79
|
-
|
80
|
-
def read_contract_wrapper(
|
81
|
-
self,
|
82
|
-
args: Optional[List[CalldataEncodable]] = None,
|
83
|
-
) -> Any:
|
84
|
-
"""
|
85
|
-
Wrapper to the contract read method.
|
86
|
-
"""
|
87
|
-
client = get_gl_client()
|
88
|
-
return client.read_contract(
|
89
|
-
address=self.address,
|
90
|
-
function_name=method_name,
|
91
|
-
account=self.account,
|
92
|
-
args=args,
|
93
|
-
)
|
94
|
-
|
95
|
-
def write_contract_wrapper(
|
96
|
-
self,
|
97
|
-
args: Optional[List[CalldataEncodable]] = None,
|
98
|
-
value: int = 0,
|
99
|
-
consensus_max_rotations: Optional[int] = None,
|
100
|
-
leader_only: bool = False,
|
101
|
-
wait_transaction_status: TransactionStatus = TransactionStatus.FINALIZED,
|
102
|
-
wait_interval: Optional[int] = None,
|
103
|
-
wait_retries: Optional[int] = None,
|
104
|
-
wait_triggered_transactions: bool = True,
|
105
|
-
wait_triggered_transactions_status: TransactionStatus = TransactionStatus.FINALIZED,
|
106
|
-
) -> GenLayerTransaction:
|
107
|
-
"""
|
108
|
-
Wrapper to the contract write method.
|
109
|
-
"""
|
110
|
-
general_config = get_general_config()
|
111
|
-
if wait_interval is None:
|
112
|
-
wait_interval = general_config.get_default_wait_interval()
|
113
|
-
if wait_retries is None:
|
114
|
-
wait_retries = general_config.get_default_wait_retries()
|
115
|
-
client = get_gl_client()
|
116
|
-
tx_hash = client.write_contract(
|
117
|
-
address=self.address,
|
118
|
-
function_name=method_name,
|
119
|
-
account=self.account,
|
120
|
-
value=value,
|
121
|
-
consensus_max_rotations=consensus_max_rotations,
|
122
|
-
leader_only=leader_only,
|
123
|
-
args=args,
|
124
|
-
)
|
125
|
-
receipt = client.wait_for_transaction_receipt(
|
126
|
-
transaction_hash=tx_hash,
|
127
|
-
status=wait_transaction_status,
|
128
|
-
interval=wait_interval,
|
129
|
-
retries=wait_retries,
|
130
|
-
)
|
131
|
-
if wait_triggered_transactions:
|
132
|
-
triggered_transactions = receipt["triggered_transactions"]
|
133
|
-
for triggered_transaction in triggered_transactions:
|
134
|
-
client.wait_for_transaction_receipt(
|
135
|
-
transaction_hash=triggered_transaction,
|
136
|
-
status=wait_triggered_transactions_status,
|
137
|
-
interval=wait_interval,
|
138
|
-
retries=wait_retries,
|
139
|
-
)
|
140
|
-
return receipt
|
141
|
-
|
142
|
-
return read_contract_wrapper if read_only else write_contract_wrapper
|
143
25
|
|
144
26
|
|
145
27
|
@dataclass
|
@@ -184,17 +66,17 @@ class ContractFactory:
|
|
184
66
|
"""Attempts to get the contract schema using multiple clients in a fallback pattern.
|
185
67
|
|
186
68
|
This method tries to get the contract schema in the following order:
|
187
|
-
1.
|
188
|
-
2.
|
189
|
-
3.
|
69
|
+
1. Default client
|
70
|
+
2. Hosted studio client
|
71
|
+
3. Local client
|
190
72
|
|
191
73
|
Returns:
|
192
74
|
Optional[Dict[str, Any]]: The contract schema if successful, None if all attempts fail.
|
193
75
|
"""
|
194
76
|
clients = (
|
77
|
+
("default", get_gl_client()),
|
195
78
|
("hosted studio", get_gl_hosted_studio_client()),
|
196
79
|
("local", get_local_client()),
|
197
|
-
("default", get_gl_client()),
|
198
80
|
)
|
199
81
|
for label, client in clients:
|
200
82
|
try:
|
@@ -216,7 +98,7 @@ class ContractFactory:
|
|
216
98
|
schema = self._get_schema_with_fallback()
|
217
99
|
if schema is None:
|
218
100
|
raise ValueError(
|
219
|
-
"Failed to get schema from all clients (hosted studio,
|
101
|
+
"Failed to get schema from all clients (default, hosted studio, and local)"
|
220
102
|
)
|
221
103
|
|
222
104
|
return Contract.new(address=contract_address, schema=schema, account=account)
|
@@ -273,7 +155,7 @@ class ContractFactory:
|
|
273
155
|
schema = self._get_schema_with_fallback()
|
274
156
|
if schema is None:
|
275
157
|
raise ValueError(
|
276
|
-
"Failed to get schema from all clients (hosted studio,
|
158
|
+
"Failed to get schema from all clients (default, hosted studio, and local)"
|
277
159
|
)
|
278
160
|
|
279
161
|
return Contract.new(
|