eth-prototype 1.2.0b1__tar.gz → 1.2.1__tar.gz
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.
- {eth_prototype-1.2.0b1 → eth_prototype-1.2.1}/PKG-INFO +1 -1
- {eth_prototype-1.2.0b1 → eth_prototype-1.2.1}/src/eth_prototype.egg-info/PKG-INFO +1 -1
- {eth_prototype-1.2.0b1 → eth_prototype-1.2.1}/src/ethproto/aa_bundler.py +66 -34
- {eth_prototype-1.2.0b1 → eth_prototype-1.2.1}/src/ethproto/w3wrappers.py +9 -2
- {eth_prototype-1.2.0b1 → eth_prototype-1.2.1}/tests/test_aa_bundler.py +45 -2
- {eth_prototype-1.2.0b1 → eth_prototype-1.2.1}/.coveragerc +0 -0
- {eth_prototype-1.2.0b1 → eth_prototype-1.2.1}/.github/workflows/publish.yaml +0 -0
- {eth_prototype-1.2.0b1 → eth_prototype-1.2.1}/.github/workflows/test.yaml +0 -0
- {eth_prototype-1.2.0b1 → eth_prototype-1.2.1}/.gitignore +0 -0
- {eth_prototype-1.2.0b1 → eth_prototype-1.2.1}/.isort.cfg +0 -0
- {eth_prototype-1.2.0b1 → eth_prototype-1.2.1}/.pre-commit-config.yaml +0 -0
- {eth_prototype-1.2.0b1 → eth_prototype-1.2.1}/.readthedocs.yml +0 -0
- {eth_prototype-1.2.0b1 → eth_prototype-1.2.1}/AUTHORS.rst +0 -0
- {eth_prototype-1.2.0b1 → eth_prototype-1.2.1}/CHANGELOG.rst +0 -0
- {eth_prototype-1.2.0b1 → eth_prototype-1.2.1}/LICENSE.txt +0 -0
- {eth_prototype-1.2.0b1 → eth_prototype-1.2.1}/README.md +0 -0
- {eth_prototype-1.2.0b1 → eth_prototype-1.2.1}/docs/Makefile +0 -0
- {eth_prototype-1.2.0b1 → eth_prototype-1.2.1}/docs/_static/.gitignore +0 -0
- {eth_prototype-1.2.0b1 → eth_prototype-1.2.1}/docs/authors.rst +0 -0
- {eth_prototype-1.2.0b1 → eth_prototype-1.2.1}/docs/changelog.rst +0 -0
- {eth_prototype-1.2.0b1 → eth_prototype-1.2.1}/docs/conf.py +0 -0
- {eth_prototype-1.2.0b1 → eth_prototype-1.2.1}/docs/index.rst +0 -0
- {eth_prototype-1.2.0b1 → eth_prototype-1.2.1}/docs/license.rst +0 -0
- {eth_prototype-1.2.0b1 → eth_prototype-1.2.1}/docs/readme.rst +0 -0
- {eth_prototype-1.2.0b1 → eth_prototype-1.2.1}/docs/requirements.txt +0 -0
- {eth_prototype-1.2.0b1 → eth_prototype-1.2.1}/pyproject.toml +0 -0
- {eth_prototype-1.2.0b1 → eth_prototype-1.2.1}/setup.cfg +0 -0
- {eth_prototype-1.2.0b1 → eth_prototype-1.2.1}/setup.py +0 -0
- {eth_prototype-1.2.0b1 → eth_prototype-1.2.1}/src/eth_prototype.egg-info/SOURCES.txt +0 -0
- {eth_prototype-1.2.0b1 → eth_prototype-1.2.1}/src/eth_prototype.egg-info/dependency_links.txt +0 -0
- {eth_prototype-1.2.0b1 → eth_prototype-1.2.1}/src/eth_prototype.egg-info/not-zip-safe +0 -0
- {eth_prototype-1.2.0b1 → eth_prototype-1.2.1}/src/eth_prototype.egg-info/requires.txt +0 -0
- {eth_prototype-1.2.0b1 → eth_prototype-1.2.1}/src/eth_prototype.egg-info/top_level.txt +0 -0
- {eth_prototype-1.2.0b1 → eth_prototype-1.2.1}/src/ethproto/__init__.py +0 -0
- {eth_prototype-1.2.0b1 → eth_prototype-1.2.1}/src/ethproto/build_artifacts.py +0 -0
- {eth_prototype-1.2.0b1 → eth_prototype-1.2.1}/src/ethproto/contracts.py +0 -0
- {eth_prototype-1.2.0b1 → eth_prototype-1.2.1}/src/ethproto/defender_relay.py +0 -0
- {eth_prototype-1.2.0b1 → eth_prototype-1.2.1}/src/ethproto/wadray.py +0 -0
- {eth_prototype-1.2.0b1 → eth_prototype-1.2.1}/src/ethproto/wrappers.py +0 -0
- {eth_prototype-1.2.0b1 → eth_prototype-1.2.1}/tests/conftest.py +0 -0
- {eth_prototype-1.2.0b1 → eth_prototype-1.2.1}/tests/hardhat-project/README.md +0 -0
- {eth_prototype-1.2.0b1 → eth_prototype-1.2.1}/tests/hardhat-project/artifacts2/TestCurrency.sol/TestCurrency.json +0 -0
- {eth_prototype-1.2.0b1 → eth_prototype-1.2.1}/tests/hardhat-project/contracts/Count.sol +0 -0
- {eth_prototype-1.2.0b1 → eth_prototype-1.2.1}/tests/hardhat-project/contracts/Counter.sol +0 -0
- {eth_prototype-1.2.0b1 → eth_prototype-1.2.1}/tests/hardhat-project/contracts/CounterUpgradeable.sol +0 -0
- {eth_prototype-1.2.0b1 → eth_prototype-1.2.1}/tests/hardhat-project/contracts/CounterUpgradeableWithLibrary.sol +0 -0
- {eth_prototype-1.2.0b1 → eth_prototype-1.2.1}/tests/hardhat-project/contracts/CounterWithLibrary.sol +0 -0
- {eth_prototype-1.2.0b1 → eth_prototype-1.2.1}/tests/hardhat-project/contracts/Datatypes.sol +0 -0
- {eth_prototype-1.2.0b1 → eth_prototype-1.2.1}/tests/hardhat-project/contracts/EventLauncher.sol +0 -0
- {eth_prototype-1.2.0b1 → eth_prototype-1.2.1}/tests/hardhat-project/contracts/TestCurrency.sol +0 -0
- {eth_prototype-1.2.0b1 → eth_prototype-1.2.1}/tests/hardhat-project/contracts/TestCurrencyUUPS.sol +0 -0
- {eth_prototype-1.2.0b1 → eth_prototype-1.2.1}/tests/hardhat-project/contracts/TestNFT.sol +0 -0
- {eth_prototype-1.2.0b1 → eth_prototype-1.2.1}/tests/hardhat-project/hardhat.config.js +0 -0
- {eth_prototype-1.2.0b1 → eth_prototype-1.2.1}/tests/hardhat-project/package-lock.json +0 -0
- {eth_prototype-1.2.0b1 → eth_prototype-1.2.1}/tests/hardhat-project/package.json +0 -0
- {eth_prototype-1.2.0b1 → eth_prototype-1.2.1}/tests/test_build_artifacts.py +0 -0
- {eth_prototype-1.2.0b1 → eth_prototype-1.2.1}/tests/test_contracts.py +0 -0
- {eth_prototype-1.2.0b1 → eth_prototype-1.2.1}/tests/test_defender.py +0 -0
- {eth_prototype-1.2.0b1 → eth_prototype-1.2.1}/tests/test_time_control.py +0 -0
- {eth_prototype-1.2.0b1 → eth_prototype-1.2.1}/tests/test_w3.py +0 -0
- {eth_prototype-1.2.0b1 → eth_prototype-1.2.1}/tests/test_wadray.py +0 -0
- {eth_prototype-1.2.0b1 → eth_prototype-1.2.1}/tox.ini +0 -0
@@ -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 =
|
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
|
-
|
138
|
-
|
139
|
-
|
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
|
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
|
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
|
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
|
-
|
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
|
-
|
221
|
-
user_operation["
|
222
|
-
|
223
|
-
|
224
|
-
|
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
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
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
|
-
|
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"]}
|
@@ -49,14 +49,21 @@ class W3TimeControl:
|
|
49
49
|
return self.w3.eth.get_block("latest").timestamp
|
50
50
|
|
51
51
|
|
52
|
-
def register_w3_provider(provider_key="w3", tester=None, provider_kwargs=
|
52
|
+
def register_w3_provider(provider_key="w3", w3=None, tester=None, provider_kwargs=None):
|
53
|
+
if w3 is not None and tester is not None:
|
54
|
+
raise ValueError("Cannot inject w3 and use tester at the same time")
|
55
|
+
|
56
|
+
provider_kwargs = provider_kwargs or {}
|
53
57
|
if tester is None:
|
54
58
|
try:
|
55
59
|
import eth_tester # noqa
|
56
60
|
except ImportError:
|
57
61
|
tester = False
|
58
62
|
|
59
|
-
if
|
63
|
+
if w3 is not None:
|
64
|
+
# the provided w3 instance takes precedence over all other args
|
65
|
+
pass
|
66
|
+
elif tester:
|
60
67
|
from web3 import Web3
|
61
68
|
|
62
69
|
w3 = Web3(Web3.EthereumTesterProvider())
|
@@ -1,3 +1,5 @@
|
|
1
|
+
from queue import Queue
|
2
|
+
from threading import Event, Thread
|
1
3
|
from unittest.mock import MagicMock, patch
|
2
4
|
|
3
5
|
from hexbytes import HexBytes
|
@@ -155,8 +157,8 @@ def test_get_nonce_random_key_mode(fetch_nonce_mock, randint_mock):
|
|
155
157
|
fetch_nonce_mock.assert_not_called()
|
156
158
|
randint_mock.assert_called_with(1, 2**192 - 1)
|
157
159
|
randint_mock.reset_mock()
|
158
|
-
assert aa_bundler.RANDOM_NONCE_KEY == 444
|
159
|
-
aa_bundler.RANDOM_NONCE_KEY = None # cleanup
|
160
|
+
assert aa_bundler.RANDOM_NONCE_KEY.key == 444
|
161
|
+
aa_bundler.RANDOM_NONCE_KEY.key = None # cleanup
|
160
162
|
|
161
163
|
|
162
164
|
@patch.object(aa_bundler.random, "randint")
|
@@ -241,3 +243,44 @@ def test_send_transaction(get_base_fee_mock):
|
|
241
243
|
get_base_fee_mock.assert_called_once_with(w3)
|
242
244
|
assert aa_bundler.NONCE_CACHE[0] == 1
|
243
245
|
assert ret == {"userOpHash": "0xa950a17ca1ed83e974fb1aa227360a007cb65f566518af117ffdbb04d8d2d524"}
|
246
|
+
|
247
|
+
|
248
|
+
def test_random_key_nonces_are_thread_safe():
|
249
|
+
queue = Queue()
|
250
|
+
event = Event()
|
251
|
+
|
252
|
+
def worker():
|
253
|
+
event.wait() # Get all threads running at the same time
|
254
|
+
nonce_key, nonce = aa_bundler.get_nonce_and_key(
|
255
|
+
FAIL_IF_USED,
|
256
|
+
{"from": TEST_SENDER},
|
257
|
+
nonce_mode=aa_bundler.NonceMode.RANDOM_KEY,
|
258
|
+
)
|
259
|
+
aa_bundler.consume_nonce(nonce_key, nonce)
|
260
|
+
queue.put(
|
261
|
+
aa_bundler.get_nonce_and_key(
|
262
|
+
FAIL_IF_USED,
|
263
|
+
{"from": TEST_SENDER},
|
264
|
+
nonce_mode=aa_bundler.NonceMode.RANDOM_KEY,
|
265
|
+
)
|
266
|
+
)
|
267
|
+
|
268
|
+
threads = [Thread(target=worker) for _ in range(15)]
|
269
|
+
for thread in threads:
|
270
|
+
thread.start()
|
271
|
+
|
272
|
+
# Fire all threads at once
|
273
|
+
event.set()
|
274
|
+
for thread in threads:
|
275
|
+
thread.join()
|
276
|
+
|
277
|
+
nonces = {}
|
278
|
+
|
279
|
+
while not queue.empty():
|
280
|
+
nonce_key, nonce = queue.get_nowait()
|
281
|
+
# Each thread got a different key
|
282
|
+
assert nonce_key not in nonces
|
283
|
+
nonces[nonce_key] = nonce
|
284
|
+
|
285
|
+
# All nonces are the same
|
286
|
+
assert all(nonce == 1 for nonce in nonces.values())
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{eth_prototype-1.2.0b1 → eth_prototype-1.2.1}/src/eth_prototype.egg-info/dependency_links.txt
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{eth_prototype-1.2.0b1 → eth_prototype-1.2.1}/tests/hardhat-project/contracts/CounterUpgradeable.sol
RENAMED
File without changes
|
File without changes
|
{eth_prototype-1.2.0b1 → eth_prototype-1.2.1}/tests/hardhat-project/contracts/CounterWithLibrary.sol
RENAMED
File without changes
|
File without changes
|
{eth_prototype-1.2.0b1 → eth_prototype-1.2.1}/tests/hardhat-project/contracts/EventLauncher.sol
RENAMED
File without changes
|
{eth_prototype-1.2.0b1 → eth_prototype-1.2.1}/tests/hardhat-project/contracts/TestCurrency.sol
RENAMED
File without changes
|
{eth_prototype-1.2.0b1 → eth_prototype-1.2.1}/tests/hardhat-project/contracts/TestCurrencyUUPS.sol
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|