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.
Files changed (44) hide show
  1. {genlayer_test-0.4.0.dist-info → genlayer_test-1.0.0.dist-info}/METADATA +54 -8
  2. genlayer_test-1.0.0.dist-info/RECORD +75 -0
  3. gltest/__init__.py +7 -6
  4. gltest/{glchain/client.py → clients.py} +1 -1
  5. gltest/contracts/__init__.py +4 -0
  6. gltest/contracts/contract.py +193 -0
  7. gltest/{glchain/contract.py → contracts/contract_factory.py} +17 -135
  8. gltest/contracts/contract_functions.py +61 -0
  9. gltest/contracts/method_stats.py +163 -0
  10. gltest/contracts/stats_collector.py +259 -0
  11. gltest/contracts/utils.py +12 -0
  12. gltest/fixtures.py +2 -6
  13. gltest/helpers/take_snapshot.py +1 -1
  14. gltest/logging.py +17 -0
  15. gltest_cli/config/constants.py +1 -0
  16. gltest_cli/config/plugin.py +37 -0
  17. gltest_cli/config/pytest_context.py +9 -0
  18. gltest_cli/config/types.py +16 -0
  19. gltest_cli/config/user.py +9 -6
  20. gltest_cli/logging.py +1 -1
  21. tests/examples/tests/test_football_prediction_market.py +2 -2
  22. tests/examples/tests/test_intelligent_oracle_factory.py +6 -6
  23. tests/examples/tests/test_llm_erc20.py +5 -5
  24. tests/examples/tests/test_llm_erc20_analyze.py +50 -0
  25. tests/examples/tests/test_log_indexer.py +23 -11
  26. tests/examples/tests/test_multi_file_contract.py +2 -2
  27. tests/examples/tests/test_multi_file_contract_legacy.py +2 -2
  28. tests/examples/tests/test_multi_read_erc20.py +14 -12
  29. tests/examples/tests/test_multi_tenant_storage.py +11 -7
  30. tests/examples/tests/test_read_erc20.py +1 -1
  31. tests/examples/tests/test_storage.py +4 -4
  32. tests/examples/tests/test_storage_legacy.py +5 -3
  33. tests/examples/tests/test_user_storage.py +20 -10
  34. tests/examples/tests/test_wizard_of_coin.py +1 -1
  35. tests/gltest_cli/config/test_general_config.py +149 -0
  36. tests/gltest_cli/config/test_plugin.py +78 -0
  37. tests/gltest_cli/config/test_user.py +51 -1
  38. genlayer_test-0.4.0.dist-info/RECORD +0 -66
  39. gltest/glchain/__init__.py +0 -16
  40. {genlayer_test-0.4.0.dist-info → genlayer_test-1.0.0.dist-info}/WHEEL +0 -0
  41. {genlayer_test-0.4.0.dist-info → genlayer_test-1.0.0.dist-info}/entry_points.txt +0 -0
  42. {genlayer_test-0.4.0.dist-info → genlayer_test-1.0.0.dist-info}/licenses/LICENSE +0 -0
  43. {genlayer_test-0.4.0.dist-info → genlayer_test-1.0.0.dist-info}/top_level.txt +0 -0
  44. /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.4.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.6.1
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 is straightforward:
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.get_value(args=[])
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 handling:
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.glchain import (
2
- create_account,
1
+ from gltest.accounts import (
2
+ get_default_account,
3
+ get_accounts,
3
4
  create_accounts,
4
- get_contract_factory,
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",
@@ -1,6 +1,6 @@
1
1
  from genlayer_py.chains import localnet
2
2
  from genlayer_py import create_client
3
- from .account import get_default_account
3
+ from gltest.accounts import get_default_account
4
4
  from functools import lru_cache
5
5
  from gltest_cli.config.general import get_general_config
6
6
 
@@ -0,0 +1,4 @@
1
+ from .contract import Contract
2
+ from .contract_factory import ContractFactory, get_contract_factory
3
+
4
+ __all__ = ["Contract", "ContractFactory", "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. Hosted studio client
188
- 2. Local client
189
- 3. Regular client
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, local, and regular)"
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, local, and regular)"
158
+ "Failed to get schema from all clients (default, hosted studio, and local)"
277
159
  )
278
160
 
279
161
  return Contract.new(