genlayer-test 0.1.0b1__py3-none-any.whl → 0.1.0b3__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.1.0b1.dist-info → genlayer_test-0.1.0b3.dist-info}/METADATA +1 -1
- genlayer_test-0.1.0b3.dist-info/RECORD +18 -0
- {genlayer_test-0.1.0b1.dist-info → genlayer_test-0.1.0b3.dist-info}/WHEEL +1 -1
- genlayer_test-0.1.0b3.dist-info/entry_points.txt +5 -0
- genlayer_test-0.1.0b3.dist-info/top_level.txt +2 -0
- gltest/artifacts/contract.py +62 -28
- gltest/glchain/contract.py +45 -21
- gltest/plugin_config.py +12 -0
- gltest/plugin_hooks.py +16 -0
- gltest/types.py +7 -0
- genlayer_test-0.1.0b1.dist-info/RECORD +0 -15
- genlayer_test-0.1.0b1.dist-info/entry_points.txt +0 -2
- genlayer_test-0.1.0b1.dist-info/top_level.txt +0 -1
- /gltest/cli.py → /gltest_cli/main.py +0 -0
@@ -0,0 +1,18 @@
|
|
1
|
+
gltest/__init__.py,sha256=AK_YfRvwlhrOheOelUG8qIRG17on0-nFCF747dopg2w,332
|
2
|
+
gltest/assertions.py,sha256=wrT5R8yLAxm0B1EzmdZjOK5gBriugIrdtOIt9Qi0G-k,591
|
3
|
+
gltest/exceptions.py,sha256=g7IF0qA8L0PdonXHTsbFnWx05U3jEYWOL3EFTW21IL4,95
|
4
|
+
gltest/plugin_config.py,sha256=O9Dx9IejbBJe-CJ9h8iMVtLzjWlzLGcTGOIgJM6gLWc,204
|
5
|
+
gltest/plugin_hooks.py,sha256=cy4-LPm3Q-ZaO94t5vgjbPjQB8MRxYGGRXeDBeJ7buU,399
|
6
|
+
gltest/types.py,sha256=BODmwTr2gAUEiO9FjiuTiWwuKvXgo4xZWstQWNUfnlw,156
|
7
|
+
gltest/artifacts/__init__.py,sha256=QCsQI8BSOHQwGWFxZVDRL-HA0Dyae3Re24E-yAZrflA,86
|
8
|
+
gltest/artifacts/contract.py,sha256=2wmfQF6FAiNuMlr9pePK7ZJjQj5Tl7-3SefeH3OnSRQ,3089
|
9
|
+
gltest/glchain/__init__.py,sha256=wD2Hzdz_wKpu-mCAX5iBINZDCNdKezLVI2lywkSkjvw,400
|
10
|
+
gltest/glchain/account.py,sha256=vOXxcR09NkT6iFw_m49DW1nVC7kP8wf23555iPPXRkw,402
|
11
|
+
gltest/glchain/client.py,sha256=U_JJJQ0V8oLPLeIbGpFcHOtnCVPJIoN9DMlvHSD1C0I,355
|
12
|
+
gltest/glchain/contract.py,sha256=46QFDjwwg6n0qmjMTPXR1wueV2ZdFOc1g2nas0U9C_c,7837
|
13
|
+
gltest_cli/main.py,sha256=Ti2-0Ev1x5_cM0D1UKqdgaDt80CDHEQGtdRne2qLm4M,53
|
14
|
+
genlayer_test-0.1.0b3.dist-info/METADATA,sha256=zzWsz5KgLuoQO7urlzrLz9BYQMmpP-QOiyZZB3zbeiw,3409
|
15
|
+
genlayer_test-0.1.0b3.dist-info/WHEEL,sha256=SmOxYU7pzNKBqASvQJ7DjX3XGUF92lrGhMb3R6_iiqI,91
|
16
|
+
genlayer_test-0.1.0b3.dist-info/entry_points.txt,sha256=rXhrPVq2IhVsd4uWzxzwCTx7jA1KcQIVNxDCUuxq4f8,89
|
17
|
+
genlayer_test-0.1.0b3.dist-info/top_level.txt,sha256=GSdrnQbiLcZssmtCpbDgBTygsc8Bt_TPeYjwm0FmpdA,18
|
18
|
+
genlayer_test-0.1.0b3.dist-info/RECORD,,
|
gltest/artifacts/contract.py
CHANGED
@@ -1,7 +1,11 @@
|
|
1
1
|
import ast
|
2
|
-
from pathlib import Path
|
3
2
|
from typing import Optional
|
4
3
|
from dataclasses import dataclass
|
4
|
+
from pathlib import Path
|
5
|
+
from gltest.plugin_config import get_contracts_dir
|
6
|
+
import io
|
7
|
+
import zipfile
|
8
|
+
from typing import Union
|
5
9
|
|
6
10
|
|
7
11
|
@dataclass
|
@@ -9,44 +13,74 @@ class ContractDefinition:
|
|
9
13
|
"""Class that represents a contract definition from a .gpy file."""
|
10
14
|
|
11
15
|
contract_name: str
|
12
|
-
contract_code: str
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
+
contract_code: Union[str, bytes]
|
17
|
+
main_file_path: Path
|
18
|
+
runner_file_path: Optional[Path]
|
16
19
|
|
17
|
-
def find_contract_definition(
|
18
|
-
contract_name: str, relative_contracts_dir: str = "contracts"
|
19
|
-
) -> Optional[ContractDefinition]:
|
20
|
-
"""
|
21
|
-
Search in the contracts directory for a contract definition.
|
22
|
-
TODO: Make this more robust to handle imports and other files.
|
23
|
-
"""
|
24
|
-
# Use provided directory or default to 'contracts' in current working directory
|
25
|
-
contracts_dir = Path.cwd() / relative_contracts_dir
|
26
20
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
# Search through all .gpy files in the contracts directory
|
31
|
-
for file_path in contracts_dir.glob("*.gpy"):
|
21
|
+
def search_path_by_class_name(contracts_dir: Path, contract_name: str) -> Path:
|
22
|
+
"""Search for a file by class name in the contracts directory."""
|
23
|
+
for file_path in contracts_dir.rglob("*.gpy"):
|
32
24
|
try:
|
33
25
|
# Read the file content
|
34
26
|
with open(file_path, "r") as f:
|
35
27
|
content = f.read()
|
36
|
-
|
37
28
|
# Parse the content into an AST
|
38
29
|
tree = ast.parse(content)
|
39
|
-
|
40
30
|
# Search for class definitions
|
41
31
|
for node in ast.walk(tree):
|
42
32
|
if isinstance(node, ast.ClassDef) and node.name == contract_name:
|
43
33
|
# Found the contract class
|
44
|
-
return
|
45
|
-
contract_name=contract_name,
|
46
|
-
source_file=str(file_path),
|
47
|
-
contract_code=content,
|
48
|
-
ast_node=node,
|
49
|
-
)
|
34
|
+
return file_path
|
50
35
|
except Exception as e:
|
51
36
|
raise ValueError(f"Error reading file {file_path}: {e}")
|
52
|
-
|
37
|
+
raise FileNotFoundError(f"Contract {contract_name} not found at: {contracts_dir}")
|
38
|
+
|
39
|
+
|
40
|
+
def compute_contract_code(
|
41
|
+
main_file_path: Path,
|
42
|
+
runner_file_path: Optional[Path] = None,
|
43
|
+
) -> str:
|
44
|
+
"""Compute the contract code."""
|
45
|
+
# Single file contract
|
46
|
+
if runner_file_path is None:
|
47
|
+
return main_file_path.read_text()
|
48
|
+
|
49
|
+
# Multifile contract
|
50
|
+
main_file_dir = main_file_path.parent
|
51
|
+
buffer = io.BytesIO()
|
52
|
+
|
53
|
+
with zipfile.ZipFile(buffer, mode="w") as zip:
|
54
|
+
zip.write(main_file_path, "contract/__init__.py")
|
55
|
+
for file_path in main_file_dir.rglob("*"):
|
56
|
+
if file_path.name in ["runner.json", "__init__.gpy"]:
|
57
|
+
continue
|
58
|
+
rel_path = file_path.relative_to(main_file_dir)
|
59
|
+
zip.write(file_path, f"contract/{rel_path}")
|
60
|
+
zip.write(runner_file_path, "runner.json")
|
61
|
+
buffer.flush()
|
62
|
+
return buffer.getvalue()
|
63
|
+
|
64
|
+
|
65
|
+
def find_contract_definition(contract_name: str) -> Optional[ContractDefinition]:
|
66
|
+
"""
|
67
|
+
Search in the contracts directory for a contract definition.
|
68
|
+
"""
|
69
|
+
contracts_dir = get_contracts_dir()
|
70
|
+
if not contracts_dir.exists():
|
71
|
+
raise FileNotFoundError(f"Contracts directory not found at: {contracts_dir}")
|
72
|
+
main_file_path = search_path_by_class_name(contracts_dir, contract_name)
|
73
|
+
main_file_dir = main_file_path.parent
|
74
|
+
runner_file_path = None
|
75
|
+
if main_file_path.name == "__init__.gpy":
|
76
|
+
# Likely a multifile contract
|
77
|
+
runner_file_path = main_file_dir.joinpath("runner.json")
|
78
|
+
if not runner_file_path.exists():
|
79
|
+
# No runner file, so it's a single file contract
|
80
|
+
runner_file_path = None
|
81
|
+
return ContractDefinition(
|
82
|
+
contract_name=contract_name,
|
83
|
+
contract_code=compute_contract_code(main_file_path, runner_file_path),
|
84
|
+
main_file_path=main_file_path,
|
85
|
+
runner_file_path=runner_file_path,
|
86
|
+
)
|
gltest/glchain/contract.py
CHANGED
@@ -3,7 +3,7 @@ from gltest.artifacts import find_contract_definition
|
|
3
3
|
from gltest.assertions import tx_execution_failed
|
4
4
|
from gltest.exceptions import DeploymentError
|
5
5
|
from .client import get_gl_client
|
6
|
-
from
|
6
|
+
from gltest.types import CalldataEncodable, GenLayerTransaction, TransactionStatus
|
7
7
|
from eth_account.signers.local import LocalAccount
|
8
8
|
from typing import List, Any, Type, Optional, Dict, Callable
|
9
9
|
import types
|
@@ -18,36 +18,47 @@ class Contract:
|
|
18
18
|
|
19
19
|
address: str
|
20
20
|
account: Optional[LocalAccount] = None
|
21
|
+
_schema: Optional[Dict[str, Any]] = None
|
21
22
|
|
22
23
|
@classmethod
|
23
|
-
def
|
24
|
-
cls,
|
24
|
+
def new(
|
25
|
+
cls,
|
26
|
+
address: str,
|
27
|
+
schema: Dict[str, Any],
|
28
|
+
account: Optional[LocalAccount] = None,
|
25
29
|
) -> "Contract":
|
26
30
|
"""
|
27
31
|
Build the methods from the schema.
|
28
32
|
"""
|
29
33
|
if not isinstance(schema, dict) or "methods" not in schema:
|
30
34
|
raise ValueError("Invalid schema: must contain 'methods' field")
|
35
|
+
instance = cls(address=address, _schema=schema, account=account)
|
36
|
+
instance._build_methods_from_schema()
|
37
|
+
return instance
|
31
38
|
|
32
|
-
|
33
|
-
|
39
|
+
def _build_methods_from_schema(self):
|
40
|
+
if self._schema is None:
|
41
|
+
raise ValueError("No schema provided")
|
42
|
+
for method_name, method_info in self._schema["methods"].items():
|
34
43
|
if not isinstance(method_info, dict) or "readonly" not in method_info:
|
35
44
|
raise ValueError(
|
36
|
-
f"Invalid method info for {method_name}: must contain 'readonly' field"
|
45
|
+
f"Invalid method info for '{method_name}': must contain 'readonly' field"
|
37
46
|
)
|
38
|
-
|
39
|
-
|
40
|
-
method_name,
|
41
|
-
types.MethodType(
|
42
|
-
cls.contract_method_factory(method_name, method_info["readonly"]),
|
43
|
-
instance,
|
44
|
-
),
|
47
|
+
method_func = self.contract_method_factory(
|
48
|
+
method_name, method_info["readonly"]
|
45
49
|
)
|
46
|
-
|
50
|
+
bound_method = types.MethodType(method_func, self)
|
51
|
+
setattr(self, method_name, bound_method)
|
47
52
|
|
48
53
|
def connect(self, account: LocalAccount) -> "Contract":
|
49
|
-
|
50
|
-
|
54
|
+
"""
|
55
|
+
Create a new instance of the contract with the same methods and a different account.
|
56
|
+
"""
|
57
|
+
new_contract = self.__class__(
|
58
|
+
address=self.address, account=account, _schema=self._schema
|
59
|
+
)
|
60
|
+
new_contract._build_methods_from_schema()
|
61
|
+
return new_contract
|
51
62
|
|
52
63
|
@staticmethod
|
53
64
|
def contract_method_factory(method_name: str, read_only: bool) -> Callable:
|
@@ -78,6 +89,9 @@ class Contract:
|
|
78
89
|
leader_only: bool = False,
|
79
90
|
wait_interval: Optional[int] = None,
|
80
91
|
wait_retries: Optional[int] = None,
|
92
|
+
wait_transaction_status: TransactionStatus = TransactionStatus.FINALIZED,
|
93
|
+
wait_triggered_transactions: bool = True,
|
94
|
+
wait_triggered_transactions_status: TransactionStatus = TransactionStatus.ACCEPTED,
|
81
95
|
) -> GenLayerTransaction:
|
82
96
|
"""
|
83
97
|
Wrapper to the contract write method.
|
@@ -98,8 +112,18 @@ class Contract:
|
|
98
112
|
if wait_retries is not None:
|
99
113
|
extra_args["retries"] = wait_retries
|
100
114
|
receipt = client.wait_for_transaction_receipt(
|
101
|
-
transaction_hash=tx_hash,
|
115
|
+
transaction_hash=tx_hash,
|
116
|
+
status=wait_transaction_status,
|
117
|
+
**extra_args,
|
102
118
|
)
|
119
|
+
if wait_triggered_transactions:
|
120
|
+
triggered_transactions = receipt["triggered_transactions"]
|
121
|
+
for triggered_transaction in triggered_transactions:
|
122
|
+
client.wait_for_transaction_receipt(
|
123
|
+
transaction_hash=triggered_transaction,
|
124
|
+
status=wait_triggered_transactions_status,
|
125
|
+
**extra_args,
|
126
|
+
)
|
103
127
|
return receipt
|
104
128
|
|
105
129
|
return read_contract_wrapper if read_only else write_contract_wrapper
|
@@ -138,6 +162,7 @@ class ContractFactory:
|
|
138
162
|
leader_only: bool = False,
|
139
163
|
wait_interval: Optional[int] = None,
|
140
164
|
wait_retries: Optional[int] = None,
|
165
|
+
wait_transaction_status: TransactionStatus = TransactionStatus.FINALIZED,
|
141
166
|
) -> Contract:
|
142
167
|
"""
|
143
168
|
Deploy the contract
|
@@ -157,9 +182,8 @@ class ContractFactory:
|
|
157
182
|
if wait_retries is not None:
|
158
183
|
extra_args["retries"] = wait_retries
|
159
184
|
tx_receipt = client.wait_for_transaction_receipt(
|
160
|
-
transaction_hash=tx_hash, status=
|
185
|
+
transaction_hash=tx_hash, status=wait_transaction_status, **extra_args
|
161
186
|
)
|
162
|
-
|
163
187
|
if (
|
164
188
|
not tx_receipt
|
165
189
|
or "data" not in tx_receipt
|
@@ -176,8 +200,8 @@ class ContractFactory:
|
|
176
200
|
|
177
201
|
contract_address = tx_receipt["data"]["contract_address"]
|
178
202
|
schema = client.get_contract_schema(address=contract_address)
|
179
|
-
return Contract.
|
180
|
-
address=contract_address, schema=schema
|
203
|
+
return Contract.new(
|
204
|
+
address=contract_address, schema=schema, account=account
|
181
205
|
)
|
182
206
|
except Exception as e:
|
183
207
|
raise DeploymentError(
|
gltest/plugin_config.py
ADDED
gltest/plugin_hooks.py
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
from gltest.plugin_config import set_contracts_dir
|
2
|
+
from pathlib import Path
|
3
|
+
|
4
|
+
|
5
|
+
def pytest_addoption(parser):
|
6
|
+
parser.addoption(
|
7
|
+
"--contracts-dir",
|
8
|
+
action="store",
|
9
|
+
default="contracts",
|
10
|
+
help="Directory containing contract files",
|
11
|
+
)
|
12
|
+
|
13
|
+
|
14
|
+
def pytest_configure(config):
|
15
|
+
contracts_dir = config.getoption("--contracts-dir")
|
16
|
+
set_contracts_dir(Path(contracts_dir))
|
gltest/types.py
ADDED
@@ -1,15 +0,0 @@
|
|
1
|
-
gltest/__init__.py,sha256=AK_YfRvwlhrOheOelUG8qIRG17on0-nFCF747dopg2w,332
|
2
|
-
gltest/assertions.py,sha256=wrT5R8yLAxm0B1EzmdZjOK5gBriugIrdtOIt9Qi0G-k,591
|
3
|
-
gltest/cli.py,sha256=Ti2-0Ev1x5_cM0D1UKqdgaDt80CDHEQGtdRne2qLm4M,53
|
4
|
-
gltest/exceptions.py,sha256=g7IF0qA8L0PdonXHTsbFnWx05U3jEYWOL3EFTW21IL4,95
|
5
|
-
gltest/artifacts/__init__.py,sha256=QCsQI8BSOHQwGWFxZVDRL-HA0Dyae3Re24E-yAZrflA,86
|
6
|
-
gltest/artifacts/contract.py,sha256=tOXv5rOGkoEeBndEp2wB9YnaTNvBRqACkGMkzC_tt4E,1761
|
7
|
-
gltest/glchain/__init__.py,sha256=wD2Hzdz_wKpu-mCAX5iBINZDCNdKezLVI2lywkSkjvw,400
|
8
|
-
gltest/glchain/account.py,sha256=vOXxcR09NkT6iFw_m49DW1nVC7kP8wf23555iPPXRkw,402
|
9
|
-
gltest/glchain/client.py,sha256=U_JJJQ0V8oLPLeIbGpFcHOtnCVPJIoN9DMlvHSD1C0I,355
|
10
|
-
gltest/glchain/contract.py,sha256=pt-LdF8k8jWCmWhU2-LI72w-vdVyi2g9Ux59MR8W3Es,6469
|
11
|
-
genlayer_test-0.1.0b1.dist-info/METADATA,sha256=2nkEPJTInKomnfv-DnvCJoDTkyWrImvUsWyctCqu8mo,3409
|
12
|
-
genlayer_test-0.1.0b1.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
|
13
|
-
genlayer_test-0.1.0b1.dist-info/entry_points.txt,sha256=JfU-i_s5OciWVqtb0Ye5Pu_A1jJkDkitwLn4esJPZz4,43
|
14
|
-
genlayer_test-0.1.0b1.dist-info/top_level.txt,sha256=xTaZGUzUZ0S1U6_lp5SwNMQWZJKFaXI1Yg8gGKFKFJg,7
|
15
|
-
genlayer_test-0.1.0b1.dist-info/RECORD,,
|
@@ -1 +0,0 @@
|
|
1
|
-
gltest
|
File without changes
|