genlayer-test 0.6.0__py3-none-any.whl → 0.8.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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: genlayer-test
3
- Version: 0.6.0
3
+ Version: 0.8.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.8.1
18
+ Requires-Dist: genlayer-py==0.9.0
19
19
  Requires-Dist: colorama>=0.4.6
20
20
  Requires-Dist: pyyaml
21
21
  Requires-Dist: python-dotenv
@@ -308,6 +308,7 @@ When this flag is enabled, all contracts deployed and all write transactions wil
308
308
  - **State Injection & Consensus Simulation** – Modify contract states dynamically and simulate consensus scenarios for advanced testing.
309
309
  - **Prompt Testing & Statistical Analysis** – Evaluate and statistically test prompts for AI-driven contract execution.
310
310
  - **Scalability to Security & Audit Tools** – Designed to extend into security testing and smart contract auditing.
311
+ - **Custom Transaction Context** – Set custom validators with specific LLM providers and models, and configure GenVM datetime for deterministic testing scenarios.
311
312
 
312
313
  ## 📚 Examples
313
314
 
@@ -387,6 +388,7 @@ def test_deployment():
387
388
  args=["initial_value"], # Constructor arguments
388
389
  account=get_default_account(), # Account to deploy from
389
390
  consensus_max_rotations=3, # Optional: max consensus rotations
391
+ transaction_context=None, # Optional: custom transaction context
390
392
  )
391
393
 
392
394
  # Contract is now deployed and ready to use
@@ -419,7 +421,9 @@ def test_read_methods():
419
421
  contract = factory.deploy()
420
422
 
421
423
  # Call a read-only method
422
- result = contract.get_storage(args=[]).call()
424
+ result = contract.get_storage(args=[]).call(
425
+ transaction_context=None, # Optional: custom transaction context
426
+ )
423
427
 
424
428
  # Assert the result matches the initial value
425
429
  assert result == "initial_value"
@@ -446,6 +450,7 @@ def test_write_methods():
446
450
  consensus_max_rotations=3, # Optional: max consensus rotations
447
451
  wait_interval=1, # Optional: seconds between status checks
448
452
  wait_retries=10, # Optional: max number of retries
453
+ transaction_context=None, # Optional: custom transaction context
449
454
  )
450
455
 
451
456
  # Verify the transaction was successful
@@ -609,6 +614,7 @@ def test_analyze_method():
609
614
  config=None, # Optional: provider-specific config
610
615
  plugin=None, # Optional: plugin name
611
616
  plugin_config=None, # Optional: plugin configuration
617
+ genvm_datetime="2024-01-15T10:30:00Z", # Optional: GenVM datetime in ISO format
612
618
  )
613
619
 
614
620
  # Access analysis results
@@ -757,6 +763,214 @@ def test_with_mocked_llm(setup_validators):
757
763
  - The `setup_validators` fixture handles the mock setup when provided with a mock_response
758
764
  - Mocking is particularly useful for CI/CD pipelines where deterministic results are required
759
765
 
766
+ ### Custom Transaction Context
767
+
768
+ The GenLayer Testing Suite allows you to customize the transaction execution environment by providing a `transaction_context` parameter with custom validators and GenVM datetime settings.
769
+
770
+ #### Using Transaction Context
771
+
772
+ Set custom validators and GenVM datetime for deterministic testing:
773
+
774
+ ```python
775
+ from gltest import get_contract_factory, get_validator_factory
776
+
777
+ def test_with_custom_transaction_context():
778
+ factory = get_contract_factory("MyContract")
779
+ validator_factory = get_validator_factory()
780
+
781
+ # Create custom validators
782
+ validators = validator_factory.batch_create_validators(
783
+ count=3,
784
+ stake=10,
785
+ provider="openai",
786
+ model="gpt-4o",
787
+ config={"temperature": 0.7, "max_tokens": 1000},
788
+ plugin="openai-compatible",
789
+ plugin_config={"api_key_env_var": "OPENAI_API_KEY"}
790
+ )
791
+
792
+ # Create transaction context with custom validators and datetime
793
+ transaction_context = {
794
+ "validators": [v.to_dict() for v in validators],
795
+ "genvm_datetime": "2024-03-15T14:30:00Z" # ISO format datetime
796
+ }
797
+
798
+ # Deploy with custom context
799
+ contract = factory.deploy(
800
+ args=["initial_value"],
801
+ transaction_context=transaction_context
802
+ )
803
+
804
+ # Call methods with custom context
805
+ result = contract.read_method().call(
806
+ transaction_context=transaction_context
807
+ )
808
+
809
+ # Write operations with custom context
810
+ tx_receipt = contract.write_method(args=["value"]).transact(
811
+ transaction_context=transaction_context
812
+ )
813
+ ```
814
+
815
+ #### Mock Validators with Transaction Context
816
+
817
+ Combine mock validators with custom datetime for fully deterministic tests:
818
+
819
+ ```python
820
+ def test_with_mocked_context():
821
+ factory = get_contract_factory("LLMContract")
822
+ validator_factory = get_validator_factory()
823
+
824
+ # Define mock LLM responses
825
+ mock_response = {
826
+ "nondet_exec_prompt": {
827
+ "analyze this": "positive sentiment"
828
+ },
829
+ "eq_principle_prompt_comparative": {
830
+ "values match": True
831
+ }
832
+ }
833
+
834
+ # Create mock validators
835
+ mock_validators = validator_factory.batch_create_mock_validators(
836
+ count=5,
837
+ mock_llm_response=mock_response
838
+ )
839
+
840
+ # Set up deterministic context
841
+ transaction_context = {
842
+ "validators": [v.to_dict() for v in mock_validators],
843
+ "genvm_datetime": "2024-01-01T00:00:00Z" # Fixed datetime for reproducibility
844
+ }
845
+
846
+ # Deploy and test with deterministic context
847
+ contract = factory.deploy(transaction_context=transaction_context)
848
+
849
+ # All operations will use the same mocked validators and datetime
850
+ result = contract.analyze_text(args=["analyze this"]).transact(
851
+ transaction_context=transaction_context
852
+ )
853
+ # Result will consistently return "positive sentiment"
854
+ ```
855
+
856
+ ### Custom Validators
857
+
858
+ The GenLayer Testing Suite includes a `get_validator_factory()` function that allows you to create custom validators with specific configurations for testing different LLM providers and consensus scenarios.
859
+
860
+ #### Creating Custom Validators
861
+
862
+ ```python
863
+ from gltest import get_validator_factory
864
+
865
+ def test_with_custom_validators():
866
+ factory = get_validator_factory()
867
+
868
+ # Create validators with different LLM providers
869
+ openai_validator = factory.create_validator(
870
+ stake=10,
871
+ provider="openai",
872
+ model="gpt-4o",
873
+ config={"temperature": 0.8, "max_tokens": 2000},
874
+ plugin="openai-compatible",
875
+ plugin_config={"api_key_env_var": "OPENAI_API_KEY"}
876
+ )
877
+
878
+ ollama_validator = factory.create_validator(
879
+ stake=8,
880
+ provider="ollama",
881
+ model="mistral",
882
+ config={"temperature": 0.5},
883
+ plugin="ollama",
884
+ plugin_config={"api_url": "http://localhost:11434"}
885
+ )
886
+
887
+ # Use validators in your tests
888
+ validators = [openai_validator, ollama_validator]
889
+ # Configure your test environment with these validators
890
+ ```
891
+
892
+ #### Batch Creation
893
+
894
+ Create multiple validators with the same configuration:
895
+
896
+ ```python
897
+ def test_batch_validators():
898
+ factory = get_validator_factory()
899
+
900
+ # Create 5 validators with identical configuration
901
+ validators = factory.batch_create_validators(
902
+ count=5,
903
+ stake=8,
904
+ provider="openai",
905
+ model="gpt-4o",
906
+ config={"temperature": 0.7, "max_tokens": 1000},
907
+ plugin="openai-compatible",
908
+ plugin_config={"api_key_env_var": "OPENAI_API_KEY"}
909
+ )
910
+ ```
911
+
912
+ #### Mock Validators
913
+
914
+ For deterministic testing, create mock validators that return predefined responses:
915
+
916
+ ```python
917
+ def test_with_mock_validators():
918
+ factory = get_validator_factory()
919
+
920
+ # Define mock responses
921
+ mock_response = {
922
+ "nondet_exec_prompt": {
923
+ "What is 2+2?": "4",
924
+ "Explain quantum physics": "It's complicated"
925
+ },
926
+ "eq_principle_prompt_comparative": {
927
+ "values must match": True
928
+ },
929
+ "eq_principle_prompt_non_comparative": {
930
+ "Is this valid?": True
931
+ }
932
+ }
933
+
934
+ # Create a single mock validator
935
+ mock_validator = factory.create_mock_validator(mock_response)
936
+
937
+ # Create multiple mock validators
938
+ mock_validators = factory.batch_create_mock_validators(
939
+ count=5,
940
+ mock_llm_response=mock_response
941
+ )
942
+ ```
943
+
944
+ #### Validator Methods
945
+
946
+ Each validator object provides useful methods:
947
+ - `to_dict()`: Convert validator to dictionary format for API calls
948
+ - `clone()`: Create an identical copy of the validator
949
+ - `batch_clone(count)`: Create multiple identical copies
950
+
951
+ Example:
952
+ ```python
953
+ def test_validator_cloning():
954
+ factory = get_validator_factory()
955
+
956
+ # Create a base validator
957
+ base_validator = factory.create_validator(
958
+ stake=10,
959
+ provider="openai",
960
+ model="gpt-4o",
961
+ config={"temperature": 0.7},
962
+ plugin="openai-compatible",
963
+ plugin_config={"api_key_env_var": "OPENAI_API_KEY"}
964
+ )
965
+
966
+ # Clone it to create identical validators
967
+ cloned = base_validator.clone()
968
+ multiple_clones = base_validator.batch_clone(3)
969
+
970
+ # Convert to dictionary for API usage
971
+ validator_dict = base_validator.to_dict()
972
+ ```
973
+
760
974
  ## 📝 Best Practices
761
975
 
762
976
  1. **Test Organization**
@@ -1,25 +1,27 @@
1
- genlayer_test-0.6.0.dist-info/licenses/LICENSE,sha256=che_H4vE0QUx3HvWrAa1_jDEVInift0U6VO15-QqEls,1064
2
- gltest/__init__.py,sha256=qoBV3IgDJr8uh9262XsWNMhi-ilkSgMqKVC9FVMk7cw,372
1
+ genlayer_test-0.8.0.dist-info/licenses/LICENSE,sha256=che_H4vE0QUx3HvWrAa1_jDEVInift0U6VO15-QqEls,1064
2
+ gltest/__init__.py,sha256=49112x2CLdYwvCbBZ1laJmMk0NQ7S3u5YUbxPefqhrk,454
3
3
  gltest/accounts.py,sha256=HUmWguJMolggQaZNRPw-LGlRlQCjLLdUanKRowMv6pI,812
4
4
  gltest/assertions.py,sha256=0dEk0VxcHK4I7GZPHxJmz-2jaA60V499gOSR74rZbfM,1748
5
5
  gltest/clients.py,sha256=1dX6wmG3QCevQRLbSaFlHymZSb-sJ5aYwet1IoX2nbA,1554
6
6
  gltest/exceptions.py,sha256=deJPmrTe5gF33qkkKF2IVJY7lc_knI7Ql3N7jZ8aLZs,510
7
7
  gltest/fixtures.py,sha256=EJXmqcC3LD03v07mepacFl58lAdhbLj6bP5rtALYISk,2507
8
8
  gltest/logging.py,sha256=jAkHsuMm-AEx1Xu1srU6W-0YzTwXJB48mCK-OVzAiN4,342
9
- gltest/types.py,sha256=Kj6UuTpRXkA4c5ystKfwgXuCiRJ1VMkbgvP7Auvznhw,184
9
+ gltest/types.py,sha256=H32fHrU5aFMaPHXgEWcHAmLWOZ9pBFVp8PK_ncpVOgM,940
10
10
  gltest/utils.py,sha256=-gHhjrS7i_GhDG3sKOq2qsTtYBt4HHgXHEXh-3RB_rI,573
11
11
  gltest/artifacts/__init__.py,sha256=qTt3TE19gVNWnQLUlt5aDe4nNvJ2YJ1jzDkMmYIsCG0,194
12
12
  gltest/artifacts/contract.py,sha256=KChpmfjZod_0dVB8y-dvWz6IVm7QlIJsgG2ArtvVDaU,6457
13
13
  gltest/contracts/__init__.py,sha256=A9bvEtYOoqoHS8TLlFBfmNOnfwdsJPEf-AZuikagCHM,166
14
- gltest/contracts/contract.py,sha256=hYuugaV_gNFk58mGyLRPKkKVKIjsCDlVpKLvwRBf7sM,6743
15
- gltest/contracts/contract_factory.py,sha256=SCmhp2DRfD8FRejOqa6-_UF-yJaJr0ShB9lDkLLBzU0,8264
16
- gltest/contracts/contract_functions.py,sha256=5gNQYzN47rCJtmcMtUkl4ZK-yDifdZup2YhtlFlrx7E,2203
14
+ gltest/contracts/contract.py,sha256=jLF_ojSM6IIbdGO2_DhsO79r2wZ2Z8eBAJRrZk2qVTI,7748
15
+ gltest/contracts/contract_factory.py,sha256=PpWh4mKf1hDMv_yms5lwFV_EoXxXiuNfdXbwD74hbAU,8929
16
+ gltest/contracts/contract_functions.py,sha256=W6Dpw1z-n9EeJxlNtIbeVnzpv4BPABhtgmgjnS8-P0w,2573
17
17
  gltest/contracts/method_stats.py,sha256=zWWjvf7K5VC4yrHpDIR717VF7LYp1RaZ1Hr_RZvWxJA,5150
18
- gltest/contracts/stats_collector.py,sha256=fuCc8L8hd0tsVGzH4adtZWwPa7ORf0A0zR5Dt1w92Qk,9033
18
+ gltest/contracts/stats_collector.py,sha256=iwsnoYo5aZbI4SVMH7dR-5CQgkglExXfsvaUDpwcdss,9286
19
19
  gltest/contracts/utils.py,sha256=TTXgcXn9BuRIlKJrjwmU7R3l1IgXsXk2luM-r3lfbbg,296
20
20
  gltest/helpers/__init__.py,sha256=I7HiTu_H7_hP65zY6Wl02r-5eAMr2eZvqBVmusuQLX4,180
21
21
  gltest/helpers/fixture_snapshot.py,sha256=bMgvlEVQBGIQzj7NOyosXWlphI1H2C1o75Zo0C-kGfQ,1931
22
22
  gltest/helpers/take_snapshot.py,sha256=-QkaBvFG4ZsNKv_nCSEsy5Ze1pICOHxVhReSeQmZUlY,1276
23
+ gltest/validators/__init__.py,sha256=AXboXORF5a8MVtG7jWMT1fJcwGXNzcX6txXQstwX2EU,152
24
+ gltest/validators/validator_factory.py,sha256=fpb-YyAKuWo4-pXBjrZ_TApYLsm6HHa6kGpbFByRucs,3886
23
25
  gltest_cli/logging.py,sha256=WXVhfq9vT6FtV_jxDqGEGia1ZWSIUKAfmWRnZd_gWQk,1266
24
26
  gltest_cli/main.py,sha256=Ti2-0Ev1x5_cM0D1UKqdgaDt80CDHEQGtdRne2qLm4M,53
25
27
  gltest_cli/config/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -40,21 +42,24 @@ tests/examples/contracts/log_indexer.py,sha256=Nlf8XUt13ujam3k6hbbVrPHK-KJ366Csz
40
42
  tests/examples/contracts/multi_read_erc20.py,sha256=28qYqn191Ro3rP7YJtZwL6Sc7JDXTeh8_QoqvdVPdqM,864
41
43
  tests/examples/contracts/multi_tenant_storage.py,sha256=ceq3X4E7pkvSdy7eQMhAoHme4KFedjAcpkfUYBmcgVM,1941
42
44
  tests/examples/contracts/read_erc20.py,sha256=RgH269F0x482WuLLYcacBnZsGJjhgJp6sG_33cV6Z-w,454
45
+ tests/examples/contracts/simple_time_contract.py,sha256=-9p_umlzUW5X2NL6IknO6Khs_114izSWifP5D2bWCMo,2512
43
46
  tests/examples/contracts/storage.py,sha256=3DD3qvzb0JkVcBtu240e5kaZWgkh-bu6YExqBUYvfaw,501
44
47
  tests/examples/contracts/user_storage.py,sha256=2W2Iv-hQZMkAaPl2RY_F-OeJpD4IlPxgWzN6e1bTkKE,649
45
48
  tests/examples/contracts/wizard_of_coin.py,sha256=Y8daPpoSKdM8wfbCPOIgEdpkLIA1ZMmeg6Hu5fBB-kU,1624
46
49
  tests/examples/contracts/multi_file_contract/__init__.py,sha256=xDn_wS62GhCmnYoI6xIqlic2i882SoPnR2TEauKc9tQ,575
47
50
  tests/examples/contracts/multi_file_contract/other.py,sha256=jHDtjUL3eAUgE6yOYKFw_RfAH7kIwk8CvxUjbWHNruk,236
51
+ tests/examples/tests/test_custom_validators.py,sha256=ounk3fBqDexAI3eRUqscMqKf5vz77tbSxW4Cz8tzfF0,2172
48
52
  tests/examples/tests/test_football_prediction_market.py,sha256=f2hfDK76WrNJZtFkTPKoPRR6bkmFLEssnlwwltSnU9A,1111
49
53
  tests/examples/tests/test_intelligent_oracle_factory.py,sha256=sL26aeb84XpJPCSUSIX3yrbwQE8GZtvNcsaKTpngFmY,7611
50
54
  tests/examples/tests/test_invalid_deploy.py,sha256=pppM8_Vn4DXcWq9iyJXb0SpZnKXkIyJxoIDW5ApCS94,814
51
55
  tests/examples/tests/test_llm_erc20.py,sha256=zb5F_7NgvZXhvqL2nULwzuTT6LGDprSy0WgrdjY7pZc,2096
52
- tests/examples/tests/test_llm_erc20_analyze.py,sha256=ccWQenEOG-U4TJdu-0i-3qNM4xRRCHSr3Iwz7pxknws,1729
56
+ tests/examples/tests/test_llm_erc20_analyze.py,sha256=rVnC3iQW_1J3P6cczEVOOG84K6ldCzlZbMO9WXQdRds,1849
53
57
  tests/examples/tests/test_log_indexer.py,sha256=46AqL7qquNc9GX2wxFxVcQXLqruMnPmxXl1yeB0-KZ4,2869
54
58
  tests/examples/tests/test_multi_file_contract.py,sha256=1Emj6ze4f-OrpxvqExeJhPHjK0cNAQW54MXf6fy2Yuc,469
55
59
  tests/examples/tests/test_multi_read_erc20.py,sha256=pSZUxoB33Z2EBtcvXxfUgwGSi_h7ryPoovkiNIfNAVQ,3713
56
60
  tests/examples/tests/test_multi_tenant_storage.py,sha256=8B3tjnRbT8ATN5obtWkGsN3nOtBU9OSjuaH9aEat3vc,2935
57
61
  tests/examples/tests/test_read_erc20.py,sha256=vLGQwguaNnT497nSq-vt4LrXj4ehn5ZSgfPt0GVFoPc,1254
62
+ tests/examples/tests/test_simple_time_contract.py,sha256=HQCJNheFyaHh73jZHyGOhNItzg045o8XsW-jLnNp-xc,3199
58
63
  tests/examples/tests/test_storage.py,sha256=y46nPjM-Jd9FVJmaNE29RPqamzxVwYtPPWE_GlXUsls,774
59
64
  tests/examples/tests/test_user_storage.py,sha256=wk0r0AXfKNgI7Eeyc8noNlJKvZBFXDbXTQE_u19XxBQ,2927
60
65
  tests/examples/tests/test_wizard_of_coin.py,sha256=AOQTanDsfZt9zIGkqZass_4BsGcVKTHzqRejN4KhSPI,854
@@ -70,8 +75,8 @@ tests/gltest_cli/config/test_config_integration.py,sha256=vPTzr3_h9UMw7m72HogBJE
70
75
  tests/gltest_cli/config/test_general_config.py,sha256=UHtSwVnso-ZwNtYM0Z4v2sCLKwyrVbHlk6b1leVfV84,14703
71
76
  tests/gltest_cli/config/test_plugin.py,sha256=COrEK5tHP1BSzanWbZHmN3EQgE9VuTcPvB95pgOvKS4,7974
72
77
  tests/gltest_cli/config/test_user.py,sha256=JxR655oUFoM9quWQO68CVPKRpT0TMpzS3bF6j6NWyT4,14401
73
- genlayer_test-0.6.0.dist-info/METADATA,sha256=uHgafSUvII1G16UbdZ-26QwmtaV86EX1Pk-VwXsZAxs,32466
74
- genlayer_test-0.6.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
75
- genlayer_test-0.6.0.dist-info/entry_points.txt,sha256=RWPcSArBpz_G4BYioh5L8Q8hyClRbSgzLimjcWMp-BQ,94
76
- genlayer_test-0.6.0.dist-info/top_level.txt,sha256=-qiGZxTRBytujzgVcKpxjvQ-WNeUDjXa59ceGMwMpko,24
77
- genlayer_test-0.6.0.dist-info/RECORD,,
78
+ genlayer_test-0.8.0.dist-info/METADATA,sha256=6LQF4o37U3qg9rmmLLoZps0XpC0T5gFsg_l4NZr7z4g,39178
79
+ genlayer_test-0.8.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
80
+ genlayer_test-0.8.0.dist-info/entry_points.txt,sha256=RWPcSArBpz_G4BYioh5L8Q8hyClRbSgzLimjcWMp-BQ,94
81
+ genlayer_test-0.8.0.dist-info/top_level.txt,sha256=-qiGZxTRBytujzgVcKpxjvQ-WNeUDjXa59ceGMwMpko,24
82
+ genlayer_test-0.8.0.dist-info/RECORD,,
gltest/__init__.py CHANGED
@@ -8,6 +8,8 @@ from gltest.clients import (
8
8
  get_gl_client,
9
9
  )
10
10
  from gltest.contracts import get_contract_factory
11
+ from gltest.validators import get_validator_factory
12
+
11
13
 
12
14
  __all__ = [
13
15
  "create_account",
@@ -16,4 +18,5 @@ __all__ = [
16
18
  "get_gl_client",
17
19
  "get_accounts",
18
20
  "get_default_account",
21
+ "get_validator_factory",
19
22
  ]
@@ -7,7 +7,9 @@ from gltest.types import (
7
7
  GenLayerTransaction,
8
8
  TransactionStatus,
9
9
  TransactionHashVariant,
10
+ TransactionContext,
10
11
  )
12
+ from genlayer_py.types import SimConfig
11
13
  from typing import List, Any, Optional, Dict, Callable
12
14
  from gltest_cli.config.general import get_general_config
13
15
  from .contract_functions import ContractFunction
@@ -25,14 +27,24 @@ def read_contract_wrapper(
25
27
 
26
28
  def call_method(
27
29
  transaction_hash_variant: TransactionHashVariant = TransactionHashVariant.LATEST_NONFINAL,
30
+ transaction_context: Optional[TransactionContext] = None,
28
31
  ):
29
32
  client = get_gl_client()
33
+ sim_config = None
34
+ if transaction_context:
35
+ try:
36
+ sim_config = SimConfig(**transaction_context)
37
+ except TypeError as e:
38
+ raise ValueError(
39
+ f"Invalid transaction_context keys: {sorted(transaction_context.keys())}"
40
+ ) from e
30
41
  return client.read_contract(
31
42
  address=self.address,
32
43
  function_name=method_name,
33
44
  account=self.account,
34
45
  args=args,
35
46
  transaction_hash_variant=transaction_hash_variant,
47
+ sim_config=sim_config,
36
48
  )
37
49
 
38
50
  return ContractFunction(
@@ -59,6 +71,7 @@ def write_contract_wrapper(
59
71
  wait_retries: Optional[int] = None,
60
72
  wait_triggered_transactions: bool = False,
61
73
  wait_triggered_transactions_status: TransactionStatus = TransactionStatus.ACCEPTED,
74
+ transaction_context: Optional[TransactionContext] = None,
62
75
  ):
63
76
  """
64
77
  Transact the contract method.
@@ -80,6 +93,14 @@ def write_contract_wrapper(
80
93
  else False
81
94
  )
82
95
  client = get_gl_client()
96
+ sim_config = None
97
+ if transaction_context:
98
+ try:
99
+ sim_config = SimConfig(**transaction_context)
100
+ except TypeError as e:
101
+ raise ValueError(
102
+ f"Invalid transaction_context keys: {sorted(transaction_context.keys())}"
103
+ ) from e
83
104
  tx_hash = client.write_contract(
84
105
  address=self.address,
85
106
  function_name=method_name,
@@ -88,6 +109,7 @@ def write_contract_wrapper(
88
109
  consensus_max_rotations=consensus_max_rotations,
89
110
  leader_only=leader_only,
90
111
  args=args,
112
+ sim_config=sim_config,
91
113
  )
92
114
  receipt = client.wait_for_transaction_receipt(
93
115
  transaction_hash=tx_hash,
@@ -113,6 +135,7 @@ def write_contract_wrapper(
113
135
  plugin: Optional[str] = None,
114
136
  plugin_config: Optional[Dict[str, Any]] = None,
115
137
  runs: int = 100,
138
+ genvm_datetime: Optional[str] = None,
116
139
  ):
117
140
  """
118
141
  Analyze the contract method using StatsCollector.
@@ -129,6 +152,7 @@ def write_contract_wrapper(
129
152
  config=config,
130
153
  plugin=plugin,
131
154
  plugin_config=plugin_config,
155
+ genvm_datetime=genvm_datetime,
132
156
  )
133
157
  sim_results = collector.run_simulations(sim_config, runs)
134
158
  return collector.analyze_results(sim_results, runs, sim_config)
@@ -15,6 +15,7 @@ from gltest.clients import (
15
15
  get_gl_hosted_studio_client,
16
16
  get_local_client,
17
17
  )
18
+ from genlayer_py.types import SimConfig
18
19
  from .contract import Contract
19
20
  from gltest.logging import logger
20
21
  from gltest.types import TransactionStatus, GenLayerTransaction, CalldataEncodable
@@ -22,6 +23,7 @@ from gltest.assertions import tx_execution_failed
22
23
  from gltest.exceptions import DeploymentError
23
24
  from gltest_cli.config.general import get_general_config
24
25
  from gltest.utils import extract_contract_address
26
+ from gltest.types import TransactionContext
25
27
 
26
28
 
27
29
  @dataclass
@@ -113,6 +115,7 @@ class ContractFactory:
113
115
  wait_transaction_status: TransactionStatus = TransactionStatus.ACCEPTED,
114
116
  wait_triggered_transactions: bool = False,
115
117
  wait_triggered_transactions_status: TransactionStatus = TransactionStatus.ACCEPTED,
118
+ transaction_context: Optional[TransactionContext] = None,
116
119
  ) -> Contract:
117
120
  """
118
121
  Deploy the contract and return a Contract instance (convenience method).
@@ -129,6 +132,7 @@ class ContractFactory:
129
132
  wait_transaction_status=wait_transaction_status,
130
133
  wait_triggered_transactions=wait_triggered_transactions,
131
134
  wait_triggered_transactions_status=wait_triggered_transactions_status,
135
+ transaction_context=transaction_context,
132
136
  )
133
137
 
134
138
  if tx_execution_failed(receipt):
@@ -147,6 +151,7 @@ class ContractFactory:
147
151
  wait_transaction_status: TransactionStatus = TransactionStatus.ACCEPTED,
148
152
  wait_triggered_transactions: bool = False,
149
153
  wait_triggered_transactions_status: TransactionStatus = TransactionStatus.ACCEPTED,
154
+ transaction_context: Optional[TransactionContext] = None,
150
155
  ) -> GenLayerTransaction:
151
156
  """
152
157
  Deploy the contract and return the transaction receipt.
@@ -170,12 +175,21 @@ class ContractFactory:
170
175
 
171
176
  client = get_gl_client()
172
177
  try:
178
+ sim_config = None
179
+ if transaction_context:
180
+ try:
181
+ sim_config = SimConfig(**transaction_context)
182
+ except TypeError as e:
183
+ raise ValueError(
184
+ f"Invalid transaction_context keys: {sorted(transaction_context.keys())}"
185
+ ) from e
173
186
  tx_hash = client.deploy_contract(
174
187
  code=self.contract_code,
175
188
  args=args,
176
189
  account=account,
177
190
  consensus_max_rotations=consensus_max_rotations,
178
191
  leader_only=leader_only,
192
+ sim_config=sim_config,
179
193
  )
180
194
  tx_receipt = client.wait_for_transaction_receipt(
181
195
  transaction_hash=tx_hash,
@@ -1,6 +1,6 @@
1
1
  from dataclasses import dataclass
2
2
  from typing import Callable, Optional, Dict, Any
3
- from gltest.types import TransactionStatus, TransactionHashVariant
3
+ from gltest.types import TransactionStatus, TransactionHashVariant, TransactionContext
4
4
 
5
5
 
6
6
  @dataclass
@@ -14,10 +14,14 @@ class ContractFunction:
14
14
  def call(
15
15
  self,
16
16
  transaction_hash_variant: TransactionHashVariant = TransactionHashVariant.LATEST_NONFINAL,
17
+ transaction_context: Optional[TransactionContext] = None,
17
18
  ):
18
19
  if not self.read_only:
19
20
  raise ValueError("call() not implemented for non-readonly method")
20
- return self.call_method(transaction_hash_variant=transaction_hash_variant)
21
+ return self.call_method(
22
+ transaction_hash_variant=transaction_hash_variant,
23
+ transaction_context=transaction_context,
24
+ )
21
25
 
22
26
  def transact(
23
27
  self,
@@ -28,6 +32,7 @@ class ContractFunction:
28
32
  wait_retries: Optional[int] = None,
29
33
  wait_triggered_transactions: bool = False,
30
34
  wait_triggered_transactions_status: TransactionStatus = TransactionStatus.ACCEPTED,
35
+ transaction_context: Optional[TransactionContext] = None,
31
36
  ):
32
37
  if self.read_only:
33
38
  raise ValueError("Cannot transact read-only method")
@@ -39,6 +44,7 @@ class ContractFunction:
39
44
  wait_retries=wait_retries,
40
45
  wait_triggered_transactions=wait_triggered_transactions,
41
46
  wait_triggered_transactions_status=wait_triggered_transactions_status,
47
+ transaction_context=transaction_context,
42
48
  )
43
49
 
44
50
  def analyze(
@@ -49,6 +55,7 @@ class ContractFunction:
49
55
  plugin: Optional[str] = None,
50
56
  plugin_config: Optional[Dict[str, Any]] = None,
51
57
  runs: int = 100,
58
+ genvm_datetime: Optional[str] = None,
52
59
  ):
53
60
  if self.read_only:
54
61
  raise ValueError("Cannot analyze read-only method")
@@ -59,4 +66,5 @@ class ContractFunction:
59
66
  plugin=plugin,
60
67
  plugin_config=plugin_config,
61
68
  runs=runs,
69
+ genvm_datetime=genvm_datetime,
62
70
  )
@@ -29,6 +29,7 @@ class SimulationConfig:
29
29
  config: Optional[Dict[str, Any]] = None
30
30
  plugin: Optional[str] = None
31
31
  plugin_config: Optional[Dict[str, Any]] = None
32
+ genvm_datetime: Optional[str] = None
32
33
 
33
34
 
34
35
  @dataclass
@@ -103,7 +104,12 @@ class StatsCollector:
103
104
  self, sim_config: SimulationConfig
104
105
  ) -> Dict[str, Any]:
105
106
  """Execute a single simulation."""
106
- config_dict = {
107
+ config_dict = {}
108
+
109
+ if sim_config.genvm_datetime is not None:
110
+ config_dict["genvm_datetime"] = sim_config.genvm_datetime
111
+
112
+ validator_info = {
107
113
  "provider": sim_config.provider,
108
114
  "model": sim_config.model,
109
115
  }
@@ -113,9 +119,10 @@ class StatsCollector:
113
119
  and sim_config.plugin is not None
114
120
  and sim_config.plugin_config is not None
115
121
  ):
116
- config_dict["config"] = sim_config.config
117
- config_dict["plugin"] = sim_config.plugin
118
- config_dict["plugin_config"] = sim_config.plugin_config
122
+ validator_info["config"] = sim_config.config
123
+ validator_info["plugin"] = sim_config.plugin
124
+ validator_info["plugin_config"] = sim_config.plugin_config
125
+ config_dict["validators"] = [validator_info]
119
126
 
120
127
  return self.client.simulate_write_contract(
121
128
  address=self.contract_address,
gltest/types.py CHANGED
@@ -6,3 +6,32 @@ from genlayer_py.types import (
6
6
  CalldataEncodable,
7
7
  TransactionHashVariant,
8
8
  )
9
+ from typing import List, TypedDict, Dict, Any
10
+
11
+
12
+ class MockedLLMResponse(TypedDict):
13
+ """Maps prompts to responses"""
14
+
15
+ # Prompt -> raw JSON string response
16
+ nondet_exec_prompt: Dict[str, str]
17
+
18
+ # Principle -> expected boolean
19
+ eq_principle_prompt_comparative: Dict[str, bool]
20
+ eq_principle_prompt_non_comparative: Dict[str, bool]
21
+
22
+
23
+ class ValidatorConfig(TypedDict):
24
+ """Validator information."""
25
+
26
+ provider: str
27
+ model: str
28
+ config: Dict[str, Any]
29
+ plugin: str
30
+ plugin_config: Dict[str, Any]
31
+
32
+
33
+ class TransactionContext(TypedDict, total=False):
34
+ """Context for transaction operations."""
35
+
36
+ validators: List[ValidatorConfig] # List to create virtual validators
37
+ genvm_datetime: str # ISO format datetime string
@@ -0,0 +1,3 @@
1
+ from .validator_factory import ValidatorFactory, Validator, get_validator_factory
2
+
3
+ __all__ = ["ValidatorFactory", "Validator", "get_validator_factory"]
@@ -0,0 +1,136 @@
1
+ from gltest.types import MockedLLMResponse
2
+ from dataclasses import dataclass
3
+ from typing import Dict, Any, Optional, List
4
+ from copy import deepcopy
5
+
6
+
7
+ @dataclass
8
+ class Validator:
9
+ stake: int
10
+ provider: str
11
+ model: str
12
+ config: Dict[str, Any]
13
+ plugin: str
14
+ plugin_config: Dict[str, Any]
15
+
16
+ # Mock configuration
17
+ mock_enabled: bool
18
+ mock_llm_response: Optional[MockedLLMResponse]
19
+
20
+ def to_dict(self) -> Dict[str, Any]:
21
+ normal_config = {
22
+ "stake": self.stake,
23
+ "provider": self.provider,
24
+ "model": self.model,
25
+ "config": deepcopy(self.config),
26
+ "plugin": self.plugin,
27
+ "plugin_config": deepcopy(self.plugin_config),
28
+ }
29
+ if not self.mock_enabled:
30
+ return normal_config
31
+
32
+ mock = self.mock_llm_response or {}
33
+ mock_config = {
34
+ "response": mock.get("nondet_exec_prompt", {}),
35
+ "eq_principle_prompt_comparative": mock.get(
36
+ "eq_principle_prompt_comparative", {}
37
+ ),
38
+ "eq_principle_prompt_non_comparative": mock.get(
39
+ "eq_principle_prompt_non_comparative", {}
40
+ ),
41
+ }
42
+ return {
43
+ **normal_config,
44
+ "plugin_config": {
45
+ **self.plugin_config,
46
+ "mock_response": mock_config,
47
+ },
48
+ }
49
+
50
+ def batch_clone(self, count: int) -> List["Validator"]:
51
+ return [self.clone() for _ in range(count)]
52
+
53
+ def clone(self) -> "Validator":
54
+ return Validator(
55
+ stake=self.stake,
56
+ provider=self.provider,
57
+ model=self.model,
58
+ config=deepcopy(self.config),
59
+ plugin=self.plugin,
60
+ plugin_config=deepcopy(self.plugin_config),
61
+ mock_enabled=self.mock_enabled,
62
+ mock_llm_response=deepcopy(self.mock_llm_response),
63
+ )
64
+
65
+
66
+ class ValidatorFactory:
67
+ def __init__(self):
68
+ pass
69
+
70
+ def create_validator(
71
+ self,
72
+ stake: int,
73
+ provider: str,
74
+ model: str,
75
+ config: Dict[str, Any],
76
+ plugin: str,
77
+ plugin_config: Dict[str, Any],
78
+ ) -> Validator:
79
+ return Validator(
80
+ stake=stake,
81
+ provider=provider,
82
+ model=model,
83
+ config=deepcopy(config),
84
+ plugin=plugin,
85
+ plugin_config=deepcopy(plugin_config),
86
+ mock_enabled=False,
87
+ mock_llm_response=None,
88
+ )
89
+
90
+ def batch_create_validators(
91
+ self,
92
+ count: int,
93
+ stake: int,
94
+ provider: str,
95
+ model: str,
96
+ config: Dict[str, Any],
97
+ plugin: str,
98
+ plugin_config: Dict[str, Any],
99
+ ) -> List[Validator]:
100
+ return [
101
+ self.create_validator(
102
+ stake=stake,
103
+ provider=provider,
104
+ model=model,
105
+ config=config,
106
+ plugin=plugin,
107
+ plugin_config=plugin_config,
108
+ )
109
+ for _ in range(count)
110
+ ]
111
+
112
+ def create_mock_validator(self, mock_llm_response: MockedLLMResponse) -> Validator:
113
+ return Validator(
114
+ stake=8,
115
+ provider="openai",
116
+ model="gpt-4o",
117
+ config={"temperature": 0.75, "max_tokens": 500},
118
+ plugin="openai-compatible",
119
+ plugin_config={
120
+ "api_key_env_var": "OPENAIKEY",
121
+ "api_url": "https://api.openai.com",
122
+ },
123
+ mock_enabled=True,
124
+ mock_llm_response=deepcopy(mock_llm_response),
125
+ )
126
+
127
+ def batch_create_mock_validators(
128
+ self,
129
+ count: int,
130
+ mock_llm_response: MockedLLMResponse,
131
+ ) -> List[Validator]:
132
+ return [self.create_mock_validator(mock_llm_response) for _ in range(count)]
133
+
134
+
135
+ def get_validator_factory() -> ValidatorFactory:
136
+ return ValidatorFactory()
@@ -0,0 +1,85 @@
1
+ # {
2
+ # "Seq": [
3
+ # { "Depends": "py-lib-genlayer-embeddings:09h0i209wrzh4xzq86f79c60x0ifs7xcjwl53ysrnw06i54ddxyi" },
4
+ # { "Depends": "py-genlayer:1j12s63yfjpva9ik2xgnffgrs6v44y1f52jvj9w7xvdn7qckd379" }
5
+ # ]
6
+ # }
7
+
8
+ from datetime import datetime, timezone
9
+ from genlayer import *
10
+
11
+
12
+ class SimpleTimeContract(gl.Contract):
13
+ """
14
+ A simple contract that demonstrates time-based function availability.
15
+ """
16
+
17
+ start_date: str # ISO format datetime string
18
+ data: str
19
+ is_active: bool
20
+
21
+ def __init__(self, start_datetime_iso: str):
22
+ """
23
+ Initialize the contract with a required start date (ISO 8601).
24
+ """
25
+ self.start_date = start_datetime_iso
26
+ self.is_active = False
27
+ self.data = ""
28
+
29
+ def _days_since_start(self) -> int:
30
+ """Calculate days elapsed since start date."""
31
+ current = datetime.now(timezone.utc)
32
+ start = datetime.fromisoformat(self.start_date)
33
+ print(f"Current: {current}, Start: {start}")
34
+ delta = current - start
35
+ print(f"Delta: {delta}")
36
+ return delta.days
37
+
38
+ @gl.public.write
39
+ def activate(self):
40
+ """
41
+ Activate the contract.
42
+ Only works if current date is after start date.
43
+ """
44
+ days = self._days_since_start()
45
+
46
+ if days < 0:
47
+ raise ValueError(
48
+ f"Cannot activate before start date. Days until start: {abs(days)}"
49
+ )
50
+
51
+ self.is_active = True
52
+
53
+ @gl.public.write
54
+ def set_data(self, value: str):
55
+ """
56
+ Set data in the contract.
57
+ Only works if contract is active and within 30 days of start.
58
+ """
59
+ if not self.is_active:
60
+ raise ValueError("Contract must be activated first")
61
+
62
+ days = self._days_since_start()
63
+
64
+ if days > 30:
65
+ raise ValueError(
66
+ f"Function expired. Was available for 30 days after start, now at day {days}"
67
+ )
68
+
69
+ self.data = value
70
+
71
+ @gl.public.view
72
+ def get_status(self) -> dict:
73
+ """Get current contract status."""
74
+ days = self._days_since_start()
75
+ current = datetime.now(timezone.utc)
76
+
77
+ return {
78
+ "start_date": self.start_date,
79
+ "current_time": current.isoformat(),
80
+ "days_since_start": days,
81
+ "is_active": self.is_active,
82
+ "data": self.data,
83
+ "can_activate": days >= 0 and not self.is_active,
84
+ "can_set_data": self.is_active and 0 <= days <= 30,
85
+ }
@@ -0,0 +1,65 @@
1
+ from gltest import get_contract_factory
2
+ from gltest.assertions import tx_execution_succeeded
3
+ from gltest import get_validator_factory
4
+ from gltest.types import MockedLLMResponse
5
+ import json
6
+
7
+
8
+ def test_custom_validators():
9
+
10
+ validator_factory = get_validator_factory()
11
+ validators = validator_factory.batch_create_validators(
12
+ count=5,
13
+ stake=8,
14
+ provider="openai",
15
+ model="gpt-4o",
16
+ config={"temperature": 0.75, "max_tokens": 500},
17
+ plugin="openai-compatible",
18
+ plugin_config={
19
+ "api_key_env_var": "OPENAIKEY",
20
+ "api_url": "https://api.openai.com",
21
+ },
22
+ )
23
+
24
+ factory = get_contract_factory("WizardOfCoin")
25
+ contract = factory.deploy(
26
+ args=[True],
27
+ transaction_context={"validators": [v.to_dict() for v in validators]},
28
+ )
29
+
30
+ transaction_response_call_1 = contract.ask_for_coin(
31
+ args=["Can you please give me my coin?"]
32
+ ).transact(transaction_context={"validators": [v.to_dict() for v in validators]})
33
+ assert tx_execution_succeeded(transaction_response_call_1)
34
+
35
+
36
+ def test_custom_mocked_validators():
37
+ mock_llm_response: MockedLLMResponse = {
38
+ "nondet_exec_prompt": {
39
+ "wizard": json.dumps(
40
+ {
41
+ "reasoning": "I am a grumpy wizard and I never give away my coins!",
42
+ "give_coin": False,
43
+ }
44
+ ),
45
+ },
46
+ "eq_principle_prompt_comparative": {
47
+ "The value of give_coin has to match": True
48
+ },
49
+ }
50
+ validator_factory = get_validator_factory()
51
+ validators = validator_factory.batch_create_mock_validators(
52
+ count=5,
53
+ mock_llm_response=mock_llm_response,
54
+ )
55
+
56
+ factory = get_contract_factory("WizardOfCoin")
57
+ contract = factory.deploy(
58
+ args=[True],
59
+ transaction_context={"validators": [v.to_dict() for v in validators]},
60
+ )
61
+
62
+ transaction_response_call_1 = contract.ask_for_coin(
63
+ args=["Can you please give me my coin?"]
64
+ ).transact(transaction_context={"validators": [v.to_dict() for v in validators]})
65
+ assert tx_execution_succeeded(transaction_response_call_1)
@@ -1,4 +1,5 @@
1
1
  from gltest import get_contract_factory, get_default_account, create_account
2
+ from datetime import datetime, timezone
2
3
 
3
4
 
4
5
  TOKEN_TOTAL_SUPPLY = 1000
@@ -21,7 +22,10 @@ def test_llm_erc20_analyze(setup_validators):
21
22
 
22
23
  # Transfer from User A to User B
23
24
  stats = contract.transfer(args=[TRANSFER_AMOUNT, from_account_b.address]).analyze(
24
- provider="openai", model="gpt-4o", runs=3
25
+ provider="openai",
26
+ model="gpt-4o",
27
+ runs=3,
28
+ genvm_datetime=datetime.now(timezone.utc).isoformat(),
25
29
  )
26
30
 
27
31
  # Verify it's a MethodStatsSummary object
@@ -0,0 +1,90 @@
1
+ from gltest import get_contract_factory
2
+ from datetime import datetime, timedelta, timezone
3
+ from gltest.assertions import tx_execution_succeeded, tx_execution_failed
4
+
5
+
6
+ def test_simple_time_contract():
7
+ """Test all time-based functionality in a single comprehensive test."""
8
+
9
+ factory = get_contract_factory("SimpleTimeContract")
10
+
11
+ # Test 1: Deploy with past start date (10 days ago)
12
+ now = datetime.now(timezone.utc)
13
+ past_date = (now - timedelta(days=10)).isoformat()
14
+ contract = factory.deploy(args=[past_date])
15
+
16
+ # Test 1: Check initial status (10 days after start)
17
+ status = contract.get_status().call()
18
+ assert status["is_active"] == False
19
+ assert status["days_since_start"] == 10
20
+ assert status["can_activate"] == True
21
+
22
+ # Test 2: Try to activate before start date (simulate going back in time)
23
+ before_start_date = now - timedelta(days=15) # 5 days before start
24
+ before_start_date_receipt = contract.activate().transact(
25
+ transaction_context={
26
+ "genvm_datetime": before_start_date.isoformat(),
27
+ },
28
+ )
29
+ assert tx_execution_failed(before_start_date_receipt)
30
+
31
+ # Test 3: Activate after start date
32
+ activate_date = now - timedelta(days=5) # 5 days after start (15 days ago + 10)
33
+ activate_receipt = contract.activate().transact(
34
+ transaction_context={
35
+ "genvm_datetime": activate_date.isoformat(),
36
+ },
37
+ )
38
+ assert tx_execution_succeeded(activate_receipt)
39
+
40
+ # Test 4: Verify activation and check status
41
+ status = contract.get_status().call(
42
+ transaction_context={
43
+ "genvm_datetime": activate_date.isoformat(),
44
+ },
45
+ )
46
+ assert status["is_active"] == True
47
+ assert status["days_since_start"] == 5
48
+ assert status["can_set_data"] == True
49
+
50
+ # Test 5: Set data within valid period (within 30 days)
51
+ set_data_date = now - timedelta(days=2) # 8 days after start
52
+ test_data = "Test data within valid period"
53
+ set_data_receipt = contract.set_data(
54
+ args=[test_data],
55
+ ).transact(
56
+ transaction_context={
57
+ "genvm_datetime": set_data_date.isoformat(),
58
+ }
59
+ )
60
+ assert tx_execution_succeeded(set_data_receipt)
61
+
62
+ # Test 6: Verify data was set
63
+ status = contract.get_status().call(
64
+ transaction_context={
65
+ "genvm_datetime": set_data_date.isoformat(),
66
+ },
67
+ )
68
+ assert status["data"] == test_data
69
+ assert status["days_since_start"] == 8
70
+
71
+ # Test 7: Try to set data after 30 days (should fail)
72
+ expired_date = now + timedelta(days=25) # 35 days after start
73
+ expired_date_receipt = contract.set_data(
74
+ args=["Should fail - expired"],
75
+ ).transact(
76
+ transaction_context={
77
+ "genvm_datetime": expired_date.isoformat(),
78
+ }
79
+ )
80
+ assert tx_execution_failed(expired_date_receipt)
81
+
82
+ # Test 8: Check status shows expired
83
+ status = contract.get_status().call(
84
+ transaction_context={
85
+ "genvm_datetime": expired_date.isoformat(),
86
+ },
87
+ )
88
+ assert status["is_active"] == True # Still active
89
+ assert status["can_set_data"] == False # But can't set data
90
+ assert status["days_since_start"] == 35