eth-prototype 1.2.0__py3-none-any.whl → 1.2.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
1
  Metadata-Version: 2.1
2
2
  Name: eth-prototype
3
- Version: 1.2.0
3
+ Version: 1.2.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
@@ -1,14 +1,14 @@
1
1
  ethproto/__init__.py,sha256=YWkAFysBp4tZjLWWB2FFmp5yG23pUYhQvgQW9b3soXs,579
2
- ethproto/aa_bundler.py,sha256=R4P3o6Cmt05StuVsReMRlhpSTzsGSEhj0Mcfy8HqFv8,9460
2
+ ethproto/aa_bundler.py,sha256=XcSshufDoDAZU2mGaI5Kl8BSiidyIMLNhFKSmwOj5Bk,10544
3
3
  ethproto/build_artifacts.py,sha256=xwCd5hJUHP82IA-y3sSfX6fV15kjCGtV19RxNRcoor0,5441
4
4
  ethproto/contracts.py,sha256=rNVbCK1hURy7lWKhzSdXgVWo3wx9O_Ghk-6PfgOsRNk,18662
5
5
  ethproto/defender_relay.py,sha256=05A8TfRZwiBhCpo924Pf9CjfKSir2Wvgg1p_asFxJbw,1777
6
6
  ethproto/w3wrappers.py,sha256=4ZEnJFrc8bV1qHG4dNdom4FL1gEUhgoJORY6cGzlJDk,21549
7
7
  ethproto/wadray.py,sha256=JBsu5KcyU9k70bDK03T2IY6qPVFO30WbYPhwrAHdXao,8262
8
8
  ethproto/wrappers.py,sha256=9qDwRDOXw3wquzvGfIsub-VPWm98GBWP7dHLFOUPWzg,17307
9
- eth_prototype-1.2.0.dist-info/AUTHORS.rst,sha256=Ui-05yYXtDZxna6o1yNcfdm8Jt68UIDQ01osiLxlYlU,95
10
- eth_prototype-1.2.0.dist-info/LICENSE.txt,sha256=U_Q6_nDYDwZPIuhttHi37hXZ2qU2-HlV2geo9hzHXFw,1087
11
- eth_prototype-1.2.0.dist-info/METADATA,sha256=861SU0czUjLc6u-wuWlcAeyAN8a5HSkzJDn3yqQQu88,2482
12
- eth_prototype-1.2.0.dist-info/WHEEL,sha256=cVxcB9AmuTcXqmwrtPhNK88dr7IR_b6qagTj0UvIEbY,91
13
- eth_prototype-1.2.0.dist-info/top_level.txt,sha256=Dl0X7m6N1hxeo4JpGpSNqWC2gtsN0731g-DL1J0mpjc,9
14
- eth_prototype-1.2.0.dist-info/RECORD,,
9
+ eth_prototype-1.2.1.dist-info/AUTHORS.rst,sha256=Ui-05yYXtDZxna6o1yNcfdm8Jt68UIDQ01osiLxlYlU,95
10
+ eth_prototype-1.2.1.dist-info/LICENSE.txt,sha256=U_Q6_nDYDwZPIuhttHi37hXZ2qU2-HlV2geo9hzHXFw,1087
11
+ eth_prototype-1.2.1.dist-info/METADATA,sha256=Qom0naY8c8M2mBdjBHHh9SGOsLPkhgsIOQtCA9dYVBM,2482
12
+ eth_prototype-1.2.1.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
13
+ eth_prototype-1.2.1.dist-info/top_level.txt,sha256=Dl0X7m6N1hxeo4JpGpSNqWC2gtsN0731g-DL1J0mpjc,9
14
+ eth_prototype-1.2.1.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (74.1.2)
2
+ Generator: setuptools (75.1.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
ethproto/aa_bundler.py CHANGED
@@ -1,5 +1,7 @@
1
1
  import random
2
+ from collections import defaultdict
2
3
  from enum import Enum
4
+ from threading import local
3
5
  from warnings import warn
4
6
 
5
7
  from environs import Env
@@ -22,6 +24,7 @@ AA_BUNDLER_PROVIDER = env.str("AA_BUNDLER_PROVIDER", "alchemy")
22
24
  AA_BUNDLER_GAS_LIMIT_FACTOR = env.float("AA_BUNDLER_GAS_LIMIT_FACTOR", 1)
23
25
  AA_BUNDLER_PRIORITY_GAS_PRICE_FACTOR = env.float("AA_BUNDLER_PRIORITY_GAS_PRICE_FACTOR", 1)
24
26
  AA_BUNDLER_BASE_GAS_PRICE_FACTOR = env.float("AA_BUNDLER_BASE_GAS_PRICE_FACTOR", 1)
27
+ AA_BUNDLER_VERIFICATION_GAS_FACTOR = env.float("AA_BUNDLER_VERIFICATION_GAS_FACTOR", 1)
25
28
 
26
29
  NonceMode = Enum(
27
30
  "NonceMode",
@@ -51,8 +54,8 @@ GET_NONCE_ABI = [
51
54
  }
52
55
  ]
53
56
 
54
- NONCE_CACHE = {}
55
- RANDOM_NONCE_KEY = None
57
+ NONCE_CACHE = defaultdict(lambda: 0)
58
+ RANDOM_NONCE_KEY = local()
56
59
 
57
60
 
58
61
  def pack_two(a, b):
@@ -69,6 +72,10 @@ def _to_uint(x):
69
72
  raise RuntimeError(f"Invalid int value {x}")
70
73
 
71
74
 
75
+ def apply_factor(x, factor):
76
+ return int(_to_uint(x) * factor)
77
+
78
+
72
79
  def pack_user_operation(user_operation):
73
80
  # https://github.com/eth-infinitism/account-abstraction/blob/develop/contracts/interfaces/PackedUserOperation.sol
74
81
  return {
@@ -134,10 +141,9 @@ def fetch_nonce(w3, account, entry_point, nonce_key):
134
141
 
135
142
 
136
143
  def get_random_nonce_key():
137
- global RANDOM_NONCE_KEY
138
- if RANDOM_NONCE_KEY is None:
139
- RANDOM_NONCE_KEY = random.randint(1, 2**192 - 1)
140
- return RANDOM_NONCE_KEY
144
+ if getattr(RANDOM_NONCE_KEY, "key", None) is None:
145
+ RANDOM_NONCE_KEY.key = random.randint(1, 2**192 - 1)
146
+ return RANDOM_NONCE_KEY.key
141
147
 
142
148
 
143
149
  def get_nonce_and_key(w3, tx, nonce_mode, entry_point=AA_BUNDLER_ENTRYPOINT, fetch=False):
@@ -153,20 +159,25 @@ def get_nonce_and_key(w3, tx, nonce_mode, entry_point=AA_BUNDLER_ENTRYPOINT, fet
153
159
  if nonce is None:
154
160
  if fetch or nonce_mode == NonceMode.FIXED_KEY_FETCH_ALWAYS:
155
161
  nonce = fetch_nonce(w3, get_sender(tx), entry_point, nonce_key)
156
- elif nonce_key not in NONCE_CACHE:
157
- nonce = 0
158
162
  else:
159
163
  nonce = NONCE_CACHE[nonce_key]
160
164
  return nonce_key, nonce
161
165
 
162
166
 
163
- def handle_response_error(resp, w3, tx, retry_nonce):
167
+ def consume_nonce(nonce_key, nonce):
168
+ NONCE_CACHE[nonce_key] = max(NONCE_CACHE[nonce_key], nonce + 1)
169
+
170
+
171
+ def check_nonce_error(resp, retry_nonce):
172
+ """Returns the next nonce if resp contains a nonce error and retries weren't exhausted
173
+ Raises RevertError otherwise
174
+ """
164
175
  if "AA25" in resp["error"]["message"] and AA_BUNDLER_MAX_GETNONCE_RETRIES > 0:
165
176
  # Retry fetching the nonce
166
177
  if retry_nonce == AA_BUNDLER_MAX_GETNONCE_RETRIES:
167
178
  raise RevertError(resp["error"]["message"])
168
179
  warn(f'{resp["error"]["message"]} error, I will retry fetching the nonce')
169
- return send_transaction(w3, tx, retry_nonce=(retry_nonce or 0) + 1)
180
+ return (retry_nonce or 0) + 1
170
181
  else:
171
182
  raise RevertError(resp["error"]["message"])
172
183
 
@@ -185,7 +196,7 @@ def get_sender(tx):
185
196
  return tx["from"]
186
197
 
187
198
 
188
- def send_transaction(w3, tx, retry_nonce=None):
199
+ def build_user_operation(w3, tx, retry_nonce=None):
189
200
  nonce_key, nonce = get_nonce_and_key(
190
201
  w3, tx, AA_BUNDLER_NONCE_MODE, entry_point=AA_BUNDLER_ENTRYPOINT, fetch=retry_nonce is not None
191
202
  )
@@ -210,44 +221,65 @@ def send_transaction(w3, tx, retry_nonce=None):
210
221
  "eth_estimateUserOperationGas", [user_operation, AA_BUNDLER_ENTRYPOINT]
211
222
  )
212
223
  if "error" in resp:
213
- return handle_response_error(resp, w3, tx, retry_nonce)
224
+ next_nonce = check_nonce_error(resp, retry_nonce)
225
+ return build_user_operation(w3, tx, retry_nonce=next_nonce)
214
226
 
215
227
  user_operation.update(resp["result"])
216
228
 
217
229
  resp = w3.provider.make_request("rundler_maxPriorityFeePerGas", [])
218
230
  if "error" in resp:
219
231
  raise RevertError(resp["error"]["message"])
220
- max_priority_fee_per_gas = int(_to_uint(resp["result"]) * AA_BUNDLER_PRIORITY_GAS_PRICE_FACTOR)
221
- user_operation["maxPriorityFeePerGas"] = hex(max_priority_fee_per_gas)
222
- user_operation["maxFeePerGas"] = hex(max_priority_fee_per_gas + get_base_fee(w3))
223
- user_operation["callGasLimit"] = hex(
224
- int(_to_uint(user_operation["callGasLimit"]) * AA_BUNDLER_GAS_LIMIT_FACTOR)
225
- )
226
- elif AA_BUNDLER_PROVIDER == "gelato":
227
- user_operation.update(
228
- {
229
- "preVerificationGas": "0x00",
230
- "callGasLimit": "0x00",
231
- "verificationGasLimit": "0x00",
232
- "maxFeePerGas": "0x00",
233
- "maxPriorityFeePerGas": "0x00",
234
- }
232
+ user_operation["maxPriorityFeePerGas"] = resp["result"]
233
+ user_operation["maxFeePerGas"] = hex(int(resp["result"], 16) + get_base_fee(w3))
234
+
235
+ elif AA_BUNDLER_PROVIDER == "generic":
236
+ resp = w3.provider.make_request(
237
+ "eth_estimateUserOperationGas", [user_operation, AA_BUNDLER_ENTRYPOINT]
235
238
  )
236
- user_operation["signature"] = add_0x_prefix(
237
- sign_user_operation(
238
- AA_BUNDLER_EXECUTOR_PK, user_operation, tx["chainId"], AA_BUNDLER_ENTRYPOINT
239
- ).hex()
239
+ if "error" in resp:
240
+ next_nonce = check_nonce_error(resp, retry_nonce)
241
+ return build_user_operation(w3, tx, retry_nonce=next_nonce)
242
+
243
+ user_operation.update(resp["result"])
244
+
245
+ else:
246
+ warn(f"Unknown AA_BUNDLER_PROVIDER: {AA_BUNDLER_PROVIDER}")
247
+
248
+ # Apply increase factors
249
+ user_operation["verificationGasLimit"] = hex(
250
+ apply_factor(user_operation["verificationGasLimit"], AA_BUNDLER_VERIFICATION_GAS_FACTOR)
240
251
  )
252
+ if "maxPriorityFeePerGas" in user_operation:
253
+ user_operation["maxPriorityFeePerGas"] = hex(
254
+ apply_factor(user_operation["maxPriorityFeePerGas"], AA_BUNDLER_PRIORITY_GAS_PRICE_FACTOR)
255
+ )
256
+ if "callGasLimit" in user_operation:
257
+ user_operation["callGasLimit"] = hex(
258
+ apply_factor(user_operation["callGasLimit"], AA_BUNDLER_GAS_LIMIT_FACTOR)
259
+ )
260
+
241
261
  # Remove paymaster related fields
242
262
  user_operation.pop("paymaster", None)
243
263
  user_operation.pop("paymasterData", None)
244
264
  user_operation.pop("paymasterVerificationGasLimit", None)
245
265
  user_operation.pop("paymasterPostOpGasLimit", None)
246
266
 
267
+ # Consume the nonce, even if the userop may fail later
268
+ consume_nonce(nonce_key, nonce)
269
+
270
+ return user_operation
271
+
272
+
273
+ def send_transaction(w3, tx, retry_nonce=None):
274
+ user_operation = build_user_operation(w3, tx, retry_nonce)
275
+ user_operation["signature"] = add_0x_prefix(
276
+ sign_user_operation(
277
+ AA_BUNDLER_EXECUTOR_PK, user_operation, tx["chainId"], AA_BUNDLER_ENTRYPOINT
278
+ ).hex()
279
+ )
247
280
  resp = w3.provider.make_request("eth_sendUserOperation", [user_operation, AA_BUNDLER_ENTRYPOINT])
248
281
  if "error" in resp:
249
- return handle_response_error(resp, w3, tx, retry_nonce)
282
+ next_nonce = check_nonce_error(resp, retry_nonce)
283
+ return send_transaction(w3, tx, retry_nonce=next_nonce)
250
284
 
251
- # Store nonce in the cache, so next time uses a new nonce
252
- NONCE_CACHE[nonce_key] = nonce + 1
253
285
  return {"userOpHash": resp["result"]}