eth-prototype 1.3.0b1__py3-none-any.whl → 1.3.1__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
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: eth-prototype
3
- Version: 1.3.0b1
3
+ Version: 1.3.1
4
4
  Summary: Prototype Ethereum Smart Contracts in Python
5
5
  Home-page: https://github.com/gnarvaja/eth-prototype
6
6
  Author: Guillermo M. Narvaja
@@ -26,14 +26,16 @@ Requires-Dist: boto3; extra == "defender"
26
26
  Provides-Extra: gmpy2
27
27
  Requires-Dist: gmpy2; extra == "gmpy2"
28
28
  Provides-Extra: testing
29
- Requires-Dist: setuptools; extra == "testing"
30
- Requires-Dist: pytest; extra == "testing"
29
+ Requires-Dist: boto3; extra == "testing"
30
+ Requires-Dist: factory-boy; extra == "testing"
31
31
  Requires-Dist: gmpy2; extra == "testing"
32
+ Requires-Dist: pytest; extra == "testing"
32
33
  Requires-Dist: pytest-cov; extra == "testing"
33
- Requires-Dist: web3[tester]==7.*; extra == "testing"
34
- Requires-Dist: boto3; extra == "testing"
34
+ Requires-Dist: pytest-mock; extra == "testing"
35
35
  Requires-Dist: pytest-recording; extra == "testing"
36
- Requires-Dist: factory-boy; extra == "testing"
36
+ Requires-Dist: setuptools; extra == "testing"
37
+ Requires-Dist: web3[tester]==7.*; extra == "testing"
38
+ Dynamic: license-file
37
39
 
38
40
  # eth-prototype
39
41
 
@@ -0,0 +1,18 @@
1
+ eth_prototype-1.3.1.dist-info/licenses/AUTHORS.rst,sha256=Ui-05yYXtDZxna6o1yNcfdm8Jt68UIDQ01osiLxlYlU,95
2
+ eth_prototype-1.3.1.dist-info/licenses/LICENSE.txt,sha256=U_Q6_nDYDwZPIuhttHi37hXZ2qU2-HlV2geo9hzHXFw,1087
3
+ ethproto/__init__.py,sha256=YWkAFysBp4tZjLWWB2FFmp5yG23pUYhQvgQW9b3soXs,579
4
+ ethproto/aa_bundler.py,sha256=hwUWkd-9upeUb0_ZJ2mPs8mAt-lmp-2F6xkO8GA1fTw,15793
5
+ ethproto/build_artifacts.py,sha256=whIXEqnh5f89UYu4Cb3KDigGV7juUCbDnfZkg-SYMKA,9878
6
+ ethproto/contracts.py,sha256=rNVbCK1hURy7lWKhzSdXgVWo3wx9O_Ghk-6PfgOsRNk,18662
7
+ ethproto/defender_relay.py,sha256=05A8TfRZwiBhCpo924Pf9CjfKSir2Wvgg1p_asFxJbw,1777
8
+ ethproto/w3wrappers.py,sha256=aA5yQ25d01s8gtWkGSxSEZlVRdq6JM1ceS3cfvSj4uM,22614
9
+ ethproto/wadray.py,sha256=JBsu5KcyU9k70bDK03T2IY6qPVFO30WbYPhwrAHdXao,8262
10
+ ethproto/wrappers.py,sha256=Mj2sgZmcLVmqsnNab6PqIXtNMMPyRVvUj2_8ButEd4w,17304
11
+ ethproto/test_utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
+ ethproto/test_utils/factories.py,sha256=G8DnUDG_yThRxMTCkymzcjm9lR_ni0_ZmTsb3sEfIdI,1805
13
+ ethproto/test_utils/hardhat.py,sha256=HzTqIznu6zVd_-doL96ftFJ235ktDCQen1QDQbNuwfM,2361
14
+ ethproto/test_utils/vcr_utils.py,sha256=1FH2sgJlElSjWkJLuO3C7E2J-4HKyFvjAqkCnGRZJyk,797
15
+ eth_prototype-1.3.1.dist-info/METADATA,sha256=BjVmjZbubsPyMmDHyDtIqgYc5-T4uS1Xzd_lrMd2gZA,2650
16
+ eth_prototype-1.3.1.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
17
+ eth_prototype-1.3.1.dist-info/top_level.txt,sha256=Dl0X7m6N1hxeo4JpGpSNqWC2gtsN0731g-DL1J0mpjc,9
18
+ eth_prototype-1.3.1.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.6.0)
2
+ Generator: setuptools (78.1.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
ethproto/aa_bundler.py CHANGED
@@ -14,6 +14,7 @@ from eth_utils import add_0x_prefix, function_signature_to_4byte_selector
14
14
  from hexbytes import HexBytes
15
15
  from web3 import Web3
16
16
  from web3.constants import ADDRESS_ZERO
17
+ from web3.types import TxParams
17
18
 
18
19
  from .contracts import RevertError
19
20
 
@@ -40,7 +41,7 @@ NonceMode = Enum(
40
41
  ],
41
42
  )
42
43
 
43
- AA_BUNDLER_NONCE_MODE = env.enum("AA_BUNDLER_NONCE_MODE", default="FIXED_KEY_LOCAL_NONCE", type=NonceMode)
44
+ AA_BUNDLER_NONCE_MODE = env.enum("AA_BUNDLER_NONCE_MODE", default="FIXED_KEY_LOCAL_NONCE", enum=NonceMode)
44
45
  AA_BUNDLER_NONCE_KEY = env.int("AA_BUNDLER_NONCE_KEY", 0)
45
46
  AA_BUNDLER_MAX_GETNONCE_RETRIES = env.int("AA_BUNDLER_MAX_GETNONCE_RETRIES", 3)
46
47
 
@@ -94,6 +95,16 @@ class Tx:
94
95
  from_: HexAddress = ADDRESS_ZERO
95
96
  chain_id: int = None
96
97
 
98
+ @classmethod
99
+ def from_tx_params(cls, params: TxParams) -> "Tx":
100
+ return cls(
101
+ target=params["to"],
102
+ data=HexBytes(params["data"]),
103
+ value=params["value"],
104
+ from_=params.get("from", ADDRESS_ZERO),
105
+ chain_id=params.get("chainId", None),
106
+ )
107
+
97
108
  def as_execute_args(self):
98
109
  return [self.target, self.value, self.data]
99
110
 
@@ -320,6 +331,14 @@ class Bundler:
320
331
  self.base_gas_price_factor = base_gas_price_factor
321
332
  self.executor_pk = executor_pk
322
333
 
334
+ def __str__(self):
335
+ return (
336
+ f"Bundler(type={self.bundler_type}, entrypoint={self.entrypoint}, nonce_mode={self.nonce_mode}, "
337
+ f"fixed_nonce_key={self.fixed_nonce_key}, verification_gas_factor={self.verification_gas_factor}, "
338
+ f"gas_limit_factor={self.gas_limit_factor}, priority_gas_price_factor={self.priority_gas_price_factor}, "
339
+ f"base_gas_price_factor={self.base_gas_price_factor})"
340
+ )
341
+
323
342
  def get_nonce_and_key(self, tx: Tx, fetch=False):
324
343
  nonce_key = tx.nonce_key
325
344
  nonce = tx.nonce
@@ -6,10 +6,14 @@ import os.path
6
6
  import re
7
7
  from dataclasses import dataclass
8
8
  from pathlib import Path
9
- from typing import Union, Tuple
9
+ from typing import Tuple, Union
10
10
 
11
11
  LIBRARY_PLACEHOLDER_MATCHER = re.compile(r"__\$[0-9a-f]{34}\$__")
12
12
 
13
+ CONTRACT_REF_MATCHER = re.compile(r"^(?:(?P<package>.*)/)?(?P<contract>[^@]+)(?:@(?P<version>.+))?$")
14
+
15
+ VERSION_MATCHER = re.compile(r"^\d+\.\d+\.\d+$")
16
+
13
17
 
14
18
  @dataclass
15
19
  class Artifact:
@@ -94,6 +98,7 @@ class ArtifactLibrary:
94
98
  self.lookup_paths = [Path(p).absolute() for p in paths]
95
99
  self._fullpath_cache = {}
96
100
  self._name_cache = {}
101
+ self._ref_cache = None
97
102
 
98
103
  def get_artifact(self, contract: str) -> Artifact:
99
104
  """Returns a build artifact by full contract path
@@ -139,3 +144,115 @@ class ArtifactLibrary:
139
144
  raise FileNotFoundError(f"Could not find artifact for {contract_name} on {self.lookup_paths}")
140
145
 
141
146
  return self._name_cache[contract_name]
147
+
148
+ def _load_ref_cache(self):
149
+ self._ref_cache = {}
150
+ for path in self.lookup_paths:
151
+ path = Path(path)
152
+ json_files = path.rglob("*.json")
153
+
154
+ for json_file in json_files:
155
+ # Skip build-info and debug files
156
+ if json_file.parent.stem == "build-info" or json_file.stem.endswith(".dbg"):
157
+ continue
158
+
159
+ parts = json_file.parts[len(path.parts) - 1 :]
160
+
161
+ # Find the first parent named either "build" or "artifacts"
162
+ try:
163
+ build_idx = next(i for i, part in enumerate(parts) if part in ("build", "artifacts"))
164
+ except StopIteration:
165
+ continue
166
+
167
+ if build_idx == 0:
168
+ # Our lookup path is already a build directory, we're probably looking at a hardhat build output
169
+ version = "local"
170
+ else:
171
+ # If we have a version number right before the build directory, we're probably looking at a
172
+ # verifiable binaries directory
173
+ version = parts[build_idx - 1]
174
+ if not VERSION_MATCHER.match(version):
175
+ version = "local"
176
+
177
+ if version == "local":
178
+ # For hardhat output dir, package is everything up to "contracts"
179
+ try:
180
+ package = "/".join(parts[build_idx + 1 : parts.index("contracts")])
181
+ except ValueError:
182
+ # There are exceptions, like @openzeppelin/contracts-upgradeable
183
+ # For those the package is everythin up to the contract directory
184
+ package = "/".join(parts[build_idx + 1 : -2])
185
+
186
+ else:
187
+ # For verifiable binaries dir, package is everything up to version
188
+ package = "/".join(parts[1 : build_idx - 1])
189
+
190
+ if json_file.stem not in self._ref_cache:
191
+ self._ref_cache[json_file.stem] = []
192
+ self._ref_cache[json_file.stem].append(
193
+ {
194
+ "path": json_file,
195
+ "package": package,
196
+ "version": version,
197
+ }
198
+ )
199
+
200
+ def _find_ref(self, contract_ref: str):
201
+ if self._ref_cache is None:
202
+ self._load_ref_cache()
203
+
204
+ ref_match = CONTRACT_REF_MATCHER.match(contract_ref)
205
+ if not ref_match:
206
+ raise ValueError(f"Invalid contract reference: {contract_ref}")
207
+
208
+ contract = ref_match.group("contract")
209
+ if contract not in self._ref_cache:
210
+ return None
211
+
212
+ ref = self._ref_cache[contract]
213
+
214
+ package = ref_match.group("package")
215
+ if package:
216
+ ref = [r for r in ref if r["package"] == package]
217
+
218
+ version = ref_match.group("version")
219
+ if version:
220
+ ref = [r for r in ref if r["version"] == version]
221
+
222
+ if not ref:
223
+ return None
224
+
225
+ # Sort by version ascending, forcing local to be last
226
+ ref = sorted(
227
+ ref,
228
+ key=lambda x: (
229
+ x["version"] == "local",
230
+ tuple(map(int, x["version"].split("."))) if x["version"] != "local" else None,
231
+ ),
232
+ )[-1]
233
+
234
+ return ref
235
+
236
+ def get_artifact_by_ref(self, contract_ref: str) -> Artifact:
237
+ """Returns a build artifact by looking for a matching contract reference.
238
+
239
+ This is compatible with the verifiable binaries structure.
240
+
241
+ Accepts the following kind of references:
242
+
243
+ - <ContractClass>
244
+ - <ContractClass>@<version>
245
+ - <package>/<ContractClass>
246
+ - <package>/<ContractClass>@<version>
247
+
248
+ If version is not specified it uses the latest version available.
249
+
250
+ Calling with contract_ref <ContractClass>@local is the same as calling get_artifact_by_name(<ContractClass>).
251
+ """
252
+ ref = self._find_ref(contract_ref)
253
+
254
+ if ref is None:
255
+ raise FileNotFoundError(f"Could not find artifact for {contract_ref} on {self.lookup_paths}")
256
+
257
+ with open(ref["path"]) as f:
258
+ return Artifact(**json.load(f))
ethproto/w3wrappers.py CHANGED
@@ -5,9 +5,10 @@ from typing import Iterator, List, Union
5
5
  from environs import Env
6
6
  from eth_account.account import Account, LocalAccount
7
7
  from eth_account.signers.base import BaseAccount
8
- from eth_utils.abi import event_abi_to_log_topic
8
+ from eth_utils import add_0x_prefix, event_abi_to_log_topic
9
9
  from hexbytes import HexBytes
10
10
  from web3.contract import Contract
11
+ from web3.contract.contract import ContractEvent
11
12
  from web3.exceptions import ContractLogicError, ExtraDataLengthError
12
13
  from web3.middleware import ExtraDataToPOAMiddleware
13
14
 
@@ -111,7 +112,7 @@ def transact(provider, function, tx_kwargs):
111
112
  }
112
113
  )
113
114
  signed_tx = from_.sign_transaction(tx)
114
- tx_hash = provider.w3.eth.send_raw_transaction(signed_tx.rawTransaction)
115
+ tx_hash = provider.w3.eth.send_raw_transaction(signed_tx.raw_transaction)
115
116
  elif W3_TRANSACT_MODE == "defender-async":
116
117
  from .defender_relay import send_transaction
117
118
 
@@ -211,8 +212,8 @@ class W3EnvAddressBook(AddressBook):
211
212
  if isinstance(name, (Account, LocalAccount)):
212
213
  return name
213
214
  if name is None:
214
- return self.ZERO
215
- if type(name) == str and name.startswith("0x"):
215
+ return list(self.signers.values())[0] if self.signers else self.ZERO
216
+ if isinstance(name, str) and name.startswith("0x"):
216
217
  return name
217
218
  if name in self.name_to_address:
218
219
  return self.name_to_address[name]
@@ -437,7 +438,7 @@ class W3Provider(BaseProvider):
437
438
  self.tx_kwargs = tx_kwargs or {}
438
439
 
439
440
  def get_contract_def(self, eth_contract):
440
- return self.artifact_library.get_artifact_by_name(eth_contract)
441
+ return self.artifact_library.get_artifact_by_ref(eth_contract)
441
442
 
442
443
  def get_contract_factory(self, eth_contract):
443
444
  contract_def = self.get_contract_def(eth_contract)
@@ -448,7 +449,9 @@ class W3Provider(BaseProvider):
448
449
  kwargs["from"] = from_
449
450
  return self.construct(factory, init_params, kwargs)
450
451
 
451
- def get_events(self, eth_wrapper, event_name, filter_kwargs={}):
452
+ def get_events(
453
+ self, eth_wrapper, event_names: Union[list[Union[str, ContractEvent]], str], filter_kwargs=None
454
+ ):
452
455
  """Returns a list of events given a filter, like this:
453
456
 
454
457
  >>> provider.get_events(currencywrapper, "Transfer", dict(from_block=0))
@@ -468,12 +471,36 @@ class W3Provider(BaseProvider):
468
471
  'blockNumber': 23
469
472
  })]
470
473
  """
471
- contract = eth_wrapper.contract
472
- event = getattr(contract.events, event_name)
473
- if "from_block" not in filter_kwargs:
474
- filter_kwargs["from_block"] = self.get_first_block(eth_wrapper)
475
- event_filter = event.create_filter(**filter_kwargs)
476
- return event_filter.get_all_entries()
474
+ if filter_kwargs is None:
475
+ filter_kwargs = {}
476
+
477
+ if isinstance(event_names, (str, ContractEvent)):
478
+ # Backwards compatibility, if we don't get a list we're getting a single event name/ref
479
+ event_names = [event_names]
480
+
481
+ topics = {}
482
+
483
+ for name in event_names:
484
+ if isinstance(name, str):
485
+ # We got a plain event name, let's get the event from the contract
486
+ event: ContractEvent = getattr(eth_wrapper.contract.events, name)
487
+ else:
488
+ # Assume we already got an event reference
489
+ event: ContractEvent = name
490
+
491
+ topics[event.topic] = event
492
+
493
+ filter_params = {
494
+ "fromBlock": filter_kwargs.get("from_block", self.get_first_block(eth_wrapper)),
495
+ "toBlock": filter_kwargs.get("to_block", "latest"),
496
+ "address": eth_wrapper.contract.address,
497
+ "topics": [list(topics.keys())],
498
+ }
499
+
500
+ logs = self.w3.eth.get_logs(filter_params)
501
+
502
+ parsed_events = [topics[add_0x_prefix(log["topics"][0].hex())].process_log(log) for log in logs]
503
+ return parsed_events
477
504
 
478
505
  def init_eth_wrapper(self, eth_wrapper, owner, init_params, kwargs):
479
506
  eth_wrapper.owner = self.address_book.get_account(owner)
@@ -483,7 +510,7 @@ class W3Provider(BaseProvider):
483
510
  for lib, _ in contract_def.libraries():
484
511
  if lib not in libraries:
485
512
  library_def = self.get_contract_factory(lib)
486
- library = self.construct(library_def)
513
+ library = self.construct(library_def, transact_kwargs={"from": eth_wrapper.owner})
487
514
  libraries[lib] = library.address
488
515
 
489
516
  if libraries:
ethproto/wrappers.py CHANGED
@@ -303,7 +303,7 @@ class ETHWrapper:
303
303
  constructor_args = None
304
304
  initialize_args = None
305
305
 
306
- def __init__(self, owner="owner", *init_params, **kwargs):
306
+ def __init__(self, owner=None, *init_params, **kwargs):
307
307
  self.provider_key = kwargs.get("provider_key", None)
308
308
  init_params = self._parse_init_params(init_params, kwargs)
309
309
  self.provider.init_eth_wrapper(self, owner, init_params, kwargs)
@@ -1,18 +0,0 @@
1
- ethproto/__init__.py,sha256=YWkAFysBp4tZjLWWB2FFmp5yG23pUYhQvgQW9b3soXs,579
2
- ethproto/aa_bundler.py,sha256=vVEiorWPpLq7ev8-wKUQ8fpgDZpaOz9LGlKGNFH0faU,14987
3
- ethproto/build_artifacts.py,sha256=xwCd5hJUHP82IA-y3sSfX6fV15kjCGtV19RxNRcoor0,5441
4
- ethproto/contracts.py,sha256=rNVbCK1hURy7lWKhzSdXgVWo3wx9O_Ghk-6PfgOsRNk,18662
5
- ethproto/defender_relay.py,sha256=05A8TfRZwiBhCpo924Pf9CjfKSir2Wvgg1p_asFxJbw,1777
6
- ethproto/w3wrappers.py,sha256=4ZEnJFrc8bV1qHG4dNdom4FL1gEUhgoJORY6cGzlJDk,21549
7
- ethproto/wadray.py,sha256=JBsu5KcyU9k70bDK03T2IY6qPVFO30WbYPhwrAHdXao,8262
8
- ethproto/wrappers.py,sha256=9qDwRDOXw3wquzvGfIsub-VPWm98GBWP7dHLFOUPWzg,17307
9
- ethproto/test_utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
- ethproto/test_utils/factories.py,sha256=G8DnUDG_yThRxMTCkymzcjm9lR_ni0_ZmTsb3sEfIdI,1805
11
- ethproto/test_utils/hardhat.py,sha256=HzTqIznu6zVd_-doL96ftFJ235ktDCQen1QDQbNuwfM,2361
12
- ethproto/test_utils/vcr_utils.py,sha256=1FH2sgJlElSjWkJLuO3C7E2J-4HKyFvjAqkCnGRZJyk,797
13
- eth_prototype-1.3.0b1.dist-info/AUTHORS.rst,sha256=Ui-05yYXtDZxna6o1yNcfdm8Jt68UIDQ01osiLxlYlU,95
14
- eth_prototype-1.3.0b1.dist-info/LICENSE.txt,sha256=U_Q6_nDYDwZPIuhttHi37hXZ2qU2-HlV2geo9hzHXFw,1087
15
- eth_prototype-1.3.0b1.dist-info/METADATA,sha256=Ipe-jiFcOwqo89hto7kwoZ52UetWAmwC7i8y_5km0_g,2583
16
- eth_prototype-1.3.0b1.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
17
- eth_prototype-1.3.0b1.dist-info/top_level.txt,sha256=Dl0X7m6N1hxeo4JpGpSNqWC2gtsN0731g-DL1J0mpjc,9
18
- eth_prototype-1.3.0b1.dist-info/RECORD,,