eth-prototype 1.1.0__py3-none-any.whl → 1.1.2__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.1
2
2
  Name: eth-prototype
3
- Version: 1.1.0
3
+ Version: 1.1.2
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
@@ -0,0 +1,14 @@
1
+ ethproto/__init__.py,sha256=YWkAFysBp4tZjLWWB2FFmp5yG23pUYhQvgQW9b3soXs,579
2
+ ethproto/aa_bundler.py,sha256=wWUa-uqTvDKUI7ewxfCQzUsVxIPhu7N-kSHu-_aXtJ4,10495
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=0lm-h-3QpoKK54HvQ66hm_fbaVRy6QFuzedTks3FeFA,21245
7
+ ethproto/wadray.py,sha256=JBsu5KcyU9k70bDK03T2IY6qPVFO30WbYPhwrAHdXao,8262
8
+ ethproto/wrappers.py,sha256=9qDwRDOXw3wquzvGfIsub-VPWm98GBWP7dHLFOUPWzg,17307
9
+ eth_prototype-1.1.2.dist-info/AUTHORS.rst,sha256=Ui-05yYXtDZxna6o1yNcfdm8Jt68UIDQ01osiLxlYlU,95
10
+ eth_prototype-1.1.2.dist-info/LICENSE.txt,sha256=U_Q6_nDYDwZPIuhttHi37hXZ2qU2-HlV2geo9hzHXFw,1087
11
+ eth_prototype-1.1.2.dist-info/METADATA,sha256=Iao560GckmPsEsXAtt1Cm8wEZwElZvzucStqIiTGP9k,2482
12
+ eth_prototype-1.1.2.dist-info/WHEEL,sha256=P9jw-gEje8ByB7_hXoICnHtVCrEwMQh-630tKvQWehc,91
13
+ eth_prototype-1.1.2.dist-info/top_level.txt,sha256=Dl0X7m6N1hxeo4JpGpSNqWC2gtsN0731g-DL1J0mpjc,9
14
+ eth_prototype-1.1.2.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (74.0.0)
2
+ Generator: setuptools (75.3.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
ethproto/aa_bundler.py CHANGED
@@ -1,15 +1,18 @@
1
1
  import random
2
- from warnings import warn
2
+ from collections import defaultdict
3
3
  from enum import Enum
4
- import requests
4
+ from threading import local
5
+ from warnings import warn
6
+
5
7
  from environs import Env
6
8
  from eth_abi import encode
7
9
  from eth_account import Account
8
10
  from eth_account.messages import encode_defunct
9
11
  from hexbytes import HexBytes
10
12
  from web3 import Web3
11
- from .contracts import RevertError
13
+ from web3.constants import ADDRESS_ZERO
12
14
 
15
+ from .contracts import RevertError
13
16
 
14
17
  env = Env()
15
18
 
@@ -17,6 +20,10 @@ AA_BUNDLER_SENDER = env.str("AA_BUNDLER_SENDER", None)
17
20
  AA_BUNDLER_ENTRYPOINT = env.str("AA_BUNDLER_ENTRYPOINT", "0x0000000071727De22E5E9d8BAf0edAc6f37da032")
18
21
  AA_BUNDLER_EXECUTOR_PK = env.str("AA_BUNDLER_EXECUTOR_PK", None)
19
22
  AA_BUNDLER_PROVIDER = env.str("AA_BUNDLER_PROVIDER", "alchemy")
23
+ AA_BUNDLER_GAS_LIMIT_FACTOR = env.float("AA_BUNDLER_GAS_LIMIT_FACTOR", 1)
24
+ AA_BUNDLER_PRIORITY_GAS_PRICE_FACTOR = env.float("AA_BUNDLER_PRIORITY_GAS_PRICE_FACTOR", 1)
25
+ AA_BUNDLER_BASE_GAS_PRICE_FACTOR = env.float("AA_BUNDLER_BASE_GAS_PRICE_FACTOR", 1)
26
+ AA_BUNDLER_VERIFICATION_GAS_FACTOR = env.float("AA_BUNDLER_VERIFICATION_GAS_FACTOR", 1)
20
27
 
21
28
  NonceMode = Enum(
22
29
  "NonceMode",
@@ -46,8 +53,8 @@ GET_NONCE_ABI = [
46
53
  }
47
54
  ]
48
55
 
49
- NONCE_CACHE = {}
50
- RANDOM_NONCE_KEY = None
56
+ NONCE_CACHE = defaultdict(lambda: 0)
57
+ RANDOM_NONCE_KEY = local()
51
58
 
52
59
 
53
60
  def pack_two(a, b):
@@ -64,6 +71,10 @@ def _to_uint(x):
64
71
  raise RuntimeError(f"Invalid int value {x}")
65
72
 
66
73
 
74
+ def apply_factor(x, factor):
75
+ return int(_to_uint(x) * factor)
76
+
77
+
67
78
  def pack_user_operation(user_operation):
68
79
  # https://github.com/eth-infinitism/account-abstraction/blob/develop/contracts/interfaces/PackedUserOperation.sol
69
80
  return {
@@ -129,10 +140,9 @@ def fetch_nonce(w3, account, entry_point, nonce_key):
129
140
 
130
141
 
131
142
  def get_random_nonce_key():
132
- global RANDOM_NONCE_KEY
133
- if RANDOM_NONCE_KEY is None:
134
- RANDOM_NONCE_KEY = random.randint(1, 2**192 - 1)
135
- return RANDOM_NONCE_KEY
143
+ if getattr(RANDOM_NONCE_KEY, "key", None) is None:
144
+ RANDOM_NONCE_KEY.key = random.randint(1, 2**192 - 1)
145
+ return RANDOM_NONCE_KEY.key
136
146
 
137
147
 
138
148
  def get_nonce_and_key(w3, tx, nonce_mode, entry_point=AA_BUNDLER_ENTRYPOINT, fetch=False):
@@ -147,31 +157,45 @@ def get_nonce_and_key(w3, tx, nonce_mode, entry_point=AA_BUNDLER_ENTRYPOINT, fet
147
157
 
148
158
  if nonce is None:
149
159
  if fetch or nonce_mode == NonceMode.FIXED_KEY_FETCH_ALWAYS:
150
- nonce = fetch_nonce(w3, tx.get("from", AA_BUNDLER_SENDER), entry_point, nonce_key)
151
- elif nonce_key not in NONCE_CACHE:
152
- nonce = 0
160
+ nonce = fetch_nonce(w3, get_sender(tx), entry_point, nonce_key)
153
161
  else:
154
162
  nonce = NONCE_CACHE[nonce_key]
155
163
  return nonce_key, nonce
156
164
 
157
165
 
158
- def handle_response_error(resp, w3, tx, retry_nonce):
166
+ def consume_nonce(nonce_key, nonce):
167
+ NONCE_CACHE[nonce_key] = max(NONCE_CACHE[nonce_key], nonce + 1)
168
+
169
+
170
+ def check_nonce_error(resp, retry_nonce):
171
+ """Returns the next nonce if resp contains a nonce error and retries weren't exhausted
172
+ Raises RevertError otherwise
173
+ """
159
174
  if "AA25" in resp["error"]["message"] and AA_BUNDLER_MAX_GETNONCE_RETRIES > 0:
160
175
  # Retry fetching the nonce
161
176
  if retry_nonce == AA_BUNDLER_MAX_GETNONCE_RETRIES:
162
177
  raise RevertError(resp["error"]["message"])
163
178
  warn(f'{resp["error"]["message"]} error, I will retry fetching the nonce')
164
- return send_transaction(w3, tx, retry_nonce=(retry_nonce or 0) + 1)
179
+ return (retry_nonce or 0) + 1
165
180
  else:
166
181
  raise RevertError(resp["error"]["message"])
167
182
 
168
183
 
169
184
  def get_base_fee(w3):
170
185
  blk = w3.eth.get_block("latest")
171
- return blk["baseFeePerGas"]
186
+ return int(_to_uint(blk["baseFeePerGas"]) * AA_BUNDLER_BASE_GAS_PRICE_FACTOR)
172
187
 
173
188
 
174
- def send_transaction(w3, tx, retry_nonce=None):
189
+ def get_sender(tx):
190
+ if "from" not in tx or tx["from"] == ADDRESS_ZERO:
191
+ if AA_BUNDLER_SENDER is None:
192
+ raise RuntimeError("Must define AA_BUNDLER_SENDER or send 'from' in the TX")
193
+ return AA_BUNDLER_SENDER
194
+ else:
195
+ return tx["from"]
196
+
197
+
198
+ def build_user_operation(w3, tx, retry_nonce=None):
175
199
  nonce_key, nonce = get_nonce_and_key(
176
200
  w3, tx, AA_BUNDLER_NONCE_MODE, entry_point=AA_BUNDLER_ENTRYPOINT, fetch=retry_nonce is not None
177
201
  )
@@ -185,7 +209,7 @@ def send_transaction(w3, tx, retry_nonce=None):
185
209
  "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c"
186
210
  )
187
211
  user_operation = {
188
- "sender": tx.get("from", AA_BUNDLER_SENDER),
212
+ "sender": get_sender(tx),
189
213
  "nonce": hex(make_nonce(nonce_key, nonce)),
190
214
  "callData": call_data,
191
215
  "signature": dummy_signature,
@@ -196,39 +220,63 @@ def send_transaction(w3, tx, retry_nonce=None):
196
220
  "eth_estimateUserOperationGas", [user_operation, AA_BUNDLER_ENTRYPOINT]
197
221
  )
198
222
  if "error" in resp:
199
- return handle_response_error(resp, w3, tx, retry_nonce)
223
+ next_nonce = check_nonce_error(resp, retry_nonce)
224
+ return build_user_operation(w3, tx, retry_nonce=next_nonce)
200
225
 
201
226
  user_operation.update(resp["result"])
202
227
 
203
228
  resp = w3.provider.make_request("rundler_maxPriorityFeePerGas", [])
204
229
  if "error" in resp:
205
230
  raise RevertError(resp["error"]["message"])
206
- max_priority_fee_per_gas = resp["result"]
207
- user_operation["maxPriorityFeePerGas"] = max_priority_fee_per_gas
208
- user_operation["maxFeePerGas"] = hex(_to_uint(max_priority_fee_per_gas) + _to_uint(get_base_fee(w3)))
209
- elif AA_BUNDLER_PROVIDER == "gelato":
210
- user_operation.update(
211
- {
212
- "preVerificationGas": "0x00",
213
- "callGasLimit": "0x00",
214
- "verificationGasLimit": "0x00",
215
- "maxFeePerGas": "0x00",
216
- "maxPriorityFeePerGas": "0x00",
217
- }
231
+ user_operation["maxPriorityFeePerGas"] = resp["result"]
232
+ user_operation["maxFeePerGas"] = hex(int(resp["result"], 16) + get_base_fee(w3))
233
+
234
+ elif AA_BUNDLER_PROVIDER == "generic":
235
+ resp = w3.provider.make_request(
236
+ "eth_estimateUserOperationGas", [user_operation, AA_BUNDLER_ENTRYPOINT]
218
237
  )
219
- user_operation["signature"] = sign_user_operation(
220
- AA_BUNDLER_EXECUTOR_PK, user_operation, tx["chainId"], AA_BUNDLER_ENTRYPOINT
238
+ if "error" in resp:
239
+ next_nonce = check_nonce_error(resp, retry_nonce)
240
+ return build_user_operation(w3, tx, retry_nonce=next_nonce)
241
+
242
+ user_operation.update(resp["result"])
243
+
244
+ else:
245
+ warn(f"Unknown AA_BUNDLER_PROVIDER: {AA_BUNDLER_PROVIDER}")
246
+
247
+ # Apply increase factors
248
+ user_operation["verificationGasLimit"] = hex(
249
+ apply_factor(user_operation["verificationGasLimit"], AA_BUNDLER_VERIFICATION_GAS_FACTOR)
221
250
  )
251
+ if "maxPriorityFeePerGas" in user_operation:
252
+ user_operation["maxPriorityFeePerGas"] = hex(
253
+ apply_factor(user_operation["maxPriorityFeePerGas"], AA_BUNDLER_PRIORITY_GAS_PRICE_FACTOR)
254
+ )
255
+ if "callGasLimit" in user_operation:
256
+ user_operation["callGasLimit"] = hex(
257
+ apply_factor(user_operation["callGasLimit"], AA_BUNDLER_GAS_LIMIT_FACTOR)
258
+ )
259
+
222
260
  # Remove paymaster related fields
223
261
  user_operation.pop("paymaster", None)
224
262
  user_operation.pop("paymasterData", None)
225
263
  user_operation.pop("paymasterVerificationGasLimit", None)
226
264
  user_operation.pop("paymasterPostOpGasLimit", None)
227
265
 
266
+ # Consume the nonce, even if the userop may fail later
267
+ consume_nonce(nonce_key, nonce)
268
+
269
+ return user_operation
270
+
271
+
272
+ def send_transaction(w3, tx, retry_nonce=None):
273
+ user_operation = build_user_operation(w3, tx, retry_nonce)
274
+ user_operation["signature"] = sign_user_operation(
275
+ AA_BUNDLER_EXECUTOR_PK, user_operation, tx["chainId"], AA_BUNDLER_ENTRYPOINT
276
+ )
228
277
  resp = w3.provider.make_request("eth_sendUserOperation", [user_operation, AA_BUNDLER_ENTRYPOINT])
229
278
  if "error" in resp:
230
- return handle_response_error(resp, w3, tx, retry_nonce)
279
+ next_nonce = check_nonce_error(resp, retry_nonce)
280
+ return send_transaction(w3, tx, retry_nonce=next_nonce)
231
281
 
232
- # Store nonce in the cache, so next time uses a new nonce
233
- NONCE_CACHE[nonce_key] = nonce + 1
234
- return resp["result"]
282
+ return {"userOpHash": resp["result"]}
ethproto/w3wrappers.py CHANGED
@@ -375,6 +375,8 @@ class W3ETHCall(ETHCall):
375
375
  def normalize_receipt(self, wrapper, receipt):
376
376
  if W3_TRANSACT_MODE == "defender-async":
377
377
  return receipt # Don't do anything because the receipt is just a dict of not-yet-mined tx
378
+ elif W3_TRANSACT_MODE == "aa-bundler-async":
379
+ return receipt # Don't do anything because the receipt is just a dict of {"userOpHash": "..."}
378
380
  return ReceiptWrapper(receipt, wrapper.contract)
379
381
 
380
382
  def _handle_exception(self, err):
@@ -442,7 +444,7 @@ class W3Provider(BaseProvider):
442
444
  def get_events(self, eth_wrapper, event_name, filter_kwargs={}):
443
445
  """Returns a list of events given a filter, like this:
444
446
 
445
- >>> provider.get_events(currencywrapper, "Transfer", dict(fromBlock=0))
447
+ >>> provider.get_events(currencywrapper, "Transfer", dict(from_block=0))
446
448
  [AttributeDict({
447
449
  'args': AttributeDict(
448
450
  {'from': '0x0000000000000000000000000000000000000000',
@@ -461,8 +463,8 @@ class W3Provider(BaseProvider):
461
463
  """
462
464
  contract = eth_wrapper.contract
463
465
  event = getattr(contract.events, event_name)
464
- if "fromBlock" not in filter_kwargs:
465
- filter_kwargs["fromBlock"] = self.get_first_block(eth_wrapper)
466
+ if "from_block" not in filter_kwargs:
467
+ filter_kwargs["from_block"] = self.get_first_block(eth_wrapper)
466
468
  event_filter = event.create_filter(**filter_kwargs)
467
469
  return event_filter.get_all_entries()
468
470
 
@@ -488,7 +490,7 @@ class W3Provider(BaseProvider):
488
490
  constructor_params, init_params = init_params
489
491
  real_contract = self.construct(eth_contract, constructor_params, {"from": eth_wrapper.owner})
490
492
  ERC1967Proxy = self.get_contract_factory("ERC1967Proxy")
491
- init_data = eth_contract.encodeABI(fn_name="initialize", args=init_params)
493
+ init_data = eth_contract.encode_abi(abi_element_identifier="initialize", args=init_params)
492
494
  proxy_contract = self.construct(
493
495
  ERC1967Proxy,
494
496
  (real_contract.address, init_data),
@@ -1,14 +0,0 @@
1
- ethproto/__init__.py,sha256=YWkAFysBp4tZjLWWB2FFmp5yG23pUYhQvgQW9b3soXs,579
2
- ethproto/aa_bundler.py,sha256=34yXgQw_vV81Wmf9cFxcLTxLUb1jzCrBE7XOw5RIjtI,8652
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=UbSuaB5_TYF4kiGNwIzvPPE7f0A6soZ7gc5jd180mVM,21065
7
- ethproto/wadray.py,sha256=JBsu5KcyU9k70bDK03T2IY6qPVFO30WbYPhwrAHdXao,8262
8
- ethproto/wrappers.py,sha256=9qDwRDOXw3wquzvGfIsub-VPWm98GBWP7dHLFOUPWzg,17307
9
- eth_prototype-1.1.0.dist-info/AUTHORS.rst,sha256=Ui-05yYXtDZxna6o1yNcfdm8Jt68UIDQ01osiLxlYlU,95
10
- eth_prototype-1.1.0.dist-info/LICENSE.txt,sha256=U_Q6_nDYDwZPIuhttHi37hXZ2qU2-HlV2geo9hzHXFw,1087
11
- eth_prototype-1.1.0.dist-info/METADATA,sha256=uOJw45vu6Xwc3uzJ1IfWV0RW1i2ST_ZHJ775Uwbi7zk,2482
12
- eth_prototype-1.1.0.dist-info/WHEEL,sha256=UvcQYKBHoFqaQd6LKyqHw9fxEolWLQnlzP0h_LgJAfI,91
13
- eth_prototype-1.1.0.dist-info/top_level.txt,sha256=Dl0X7m6N1hxeo4JpGpSNqWC2gtsN0731g-DL1J0mpjc,9
14
- eth_prototype-1.1.0.dist-info/RECORD,,