eth-prototype 1.1.0__tar.gz → 1.1.2__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.
Files changed (62) hide show
  1. {eth_prototype-1.1.0 → eth_prototype-1.1.2}/.github/workflows/test.yaml +2 -2
  2. {eth_prototype-1.1.0 → eth_prototype-1.1.2}/PKG-INFO +1 -1
  3. {eth_prototype-1.1.0 → eth_prototype-1.1.2}/src/eth_prototype.egg-info/PKG-INFO +1 -1
  4. {eth_prototype-1.1.0 → eth_prototype-1.1.2}/src/eth_prototype.egg-info/SOURCES.txt +1 -0
  5. {eth_prototype-1.1.0 → eth_prototype-1.1.2}/src/ethproto/aa_bundler.py +84 -36
  6. {eth_prototype-1.1.0 → eth_prototype-1.1.2}/src/ethproto/w3wrappers.py +6 -4
  7. eth_prototype-1.1.2/tests/hardhat-project/contracts/EventLauncher.sol +16 -0
  8. {eth_prototype-1.1.0 → eth_prototype-1.1.2}/tests/test_aa_bundler.py +53 -11
  9. {eth_prototype-1.1.0 → eth_prototype-1.1.2}/tests/test_w3.py +29 -0
  10. {eth_prototype-1.1.0 → eth_prototype-1.1.2}/.coveragerc +0 -0
  11. {eth_prototype-1.1.0 → eth_prototype-1.1.2}/.github/workflows/publish.yaml +0 -0
  12. {eth_prototype-1.1.0 → eth_prototype-1.1.2}/.gitignore +0 -0
  13. {eth_prototype-1.1.0 → eth_prototype-1.1.2}/.isort.cfg +0 -0
  14. {eth_prototype-1.1.0 → eth_prototype-1.1.2}/.pre-commit-config.yaml +0 -0
  15. {eth_prototype-1.1.0 → eth_prototype-1.1.2}/.readthedocs.yml +0 -0
  16. {eth_prototype-1.1.0 → eth_prototype-1.1.2}/AUTHORS.rst +0 -0
  17. {eth_prototype-1.1.0 → eth_prototype-1.1.2}/CHANGELOG.rst +0 -0
  18. {eth_prototype-1.1.0 → eth_prototype-1.1.2}/LICENSE.txt +0 -0
  19. {eth_prototype-1.1.0 → eth_prototype-1.1.2}/README.md +0 -0
  20. {eth_prototype-1.1.0 → eth_prototype-1.1.2}/docs/Makefile +0 -0
  21. {eth_prototype-1.1.0 → eth_prototype-1.1.2}/docs/_static/.gitignore +0 -0
  22. {eth_prototype-1.1.0 → eth_prototype-1.1.2}/docs/authors.rst +0 -0
  23. {eth_prototype-1.1.0 → eth_prototype-1.1.2}/docs/changelog.rst +0 -0
  24. {eth_prototype-1.1.0 → eth_prototype-1.1.2}/docs/conf.py +0 -0
  25. {eth_prototype-1.1.0 → eth_prototype-1.1.2}/docs/index.rst +0 -0
  26. {eth_prototype-1.1.0 → eth_prototype-1.1.2}/docs/license.rst +0 -0
  27. {eth_prototype-1.1.0 → eth_prototype-1.1.2}/docs/readme.rst +0 -0
  28. {eth_prototype-1.1.0 → eth_prototype-1.1.2}/docs/requirements.txt +0 -0
  29. {eth_prototype-1.1.0 → eth_prototype-1.1.2}/pyproject.toml +0 -0
  30. {eth_prototype-1.1.0 → eth_prototype-1.1.2}/setup.cfg +0 -0
  31. {eth_prototype-1.1.0 → eth_prototype-1.1.2}/setup.py +0 -0
  32. {eth_prototype-1.1.0 → eth_prototype-1.1.2}/src/eth_prototype.egg-info/dependency_links.txt +0 -0
  33. {eth_prototype-1.1.0 → eth_prototype-1.1.2}/src/eth_prototype.egg-info/not-zip-safe +0 -0
  34. {eth_prototype-1.1.0 → eth_prototype-1.1.2}/src/eth_prototype.egg-info/requires.txt +0 -0
  35. {eth_prototype-1.1.0 → eth_prototype-1.1.2}/src/eth_prototype.egg-info/top_level.txt +0 -0
  36. {eth_prototype-1.1.0 → eth_prototype-1.1.2}/src/ethproto/__init__.py +0 -0
  37. {eth_prototype-1.1.0 → eth_prototype-1.1.2}/src/ethproto/build_artifacts.py +0 -0
  38. {eth_prototype-1.1.0 → eth_prototype-1.1.2}/src/ethproto/contracts.py +0 -0
  39. {eth_prototype-1.1.0 → eth_prototype-1.1.2}/src/ethproto/defender_relay.py +0 -0
  40. {eth_prototype-1.1.0 → eth_prototype-1.1.2}/src/ethproto/wadray.py +0 -0
  41. {eth_prototype-1.1.0 → eth_prototype-1.1.2}/src/ethproto/wrappers.py +0 -0
  42. {eth_prototype-1.1.0 → eth_prototype-1.1.2}/tests/conftest.py +0 -0
  43. {eth_prototype-1.1.0 → eth_prototype-1.1.2}/tests/hardhat-project/README.md +0 -0
  44. {eth_prototype-1.1.0 → eth_prototype-1.1.2}/tests/hardhat-project/artifacts2/TestCurrency.sol/TestCurrency.json +0 -0
  45. {eth_prototype-1.1.0 → eth_prototype-1.1.2}/tests/hardhat-project/contracts/Count.sol +0 -0
  46. {eth_prototype-1.1.0 → eth_prototype-1.1.2}/tests/hardhat-project/contracts/Counter.sol +0 -0
  47. {eth_prototype-1.1.0 → eth_prototype-1.1.2}/tests/hardhat-project/contracts/CounterUpgradeable.sol +0 -0
  48. {eth_prototype-1.1.0 → eth_prototype-1.1.2}/tests/hardhat-project/contracts/CounterUpgradeableWithLibrary.sol +0 -0
  49. {eth_prototype-1.1.0 → eth_prototype-1.1.2}/tests/hardhat-project/contracts/CounterWithLibrary.sol +0 -0
  50. {eth_prototype-1.1.0 → eth_prototype-1.1.2}/tests/hardhat-project/contracts/Datatypes.sol +0 -0
  51. {eth_prototype-1.1.0 → eth_prototype-1.1.2}/tests/hardhat-project/contracts/TestCurrency.sol +0 -0
  52. {eth_prototype-1.1.0 → eth_prototype-1.1.2}/tests/hardhat-project/contracts/TestCurrencyUUPS.sol +0 -0
  53. {eth_prototype-1.1.0 → eth_prototype-1.1.2}/tests/hardhat-project/contracts/TestNFT.sol +0 -0
  54. {eth_prototype-1.1.0 → eth_prototype-1.1.2}/tests/hardhat-project/hardhat.config.js +0 -0
  55. {eth_prototype-1.1.0 → eth_prototype-1.1.2}/tests/hardhat-project/package-lock.json +0 -0
  56. {eth_prototype-1.1.0 → eth_prototype-1.1.2}/tests/hardhat-project/package.json +0 -0
  57. {eth_prototype-1.1.0 → eth_prototype-1.1.2}/tests/test_build_artifacts.py +0 -0
  58. {eth_prototype-1.1.0 → eth_prototype-1.1.2}/tests/test_contracts.py +0 -0
  59. {eth_prototype-1.1.0 → eth_prototype-1.1.2}/tests/test_defender.py +0 -0
  60. {eth_prototype-1.1.0 → eth_prototype-1.1.2}/tests/test_time_control.py +0 -0
  61. {eth_prototype-1.1.0 → eth_prototype-1.1.2}/tests/test_wadray.py +0 -0
  62. {eth_prototype-1.1.0 → eth_prototype-1.1.2}/tox.ini +0 -0
@@ -2,9 +2,9 @@ name: Tests
2
2
 
3
3
  on:
4
4
  push:
5
- branches: ["main"]
5
+ branches: ["main", "v1.1.x"]
6
6
  pull_request:
7
- branches: ["main"]
7
+ branches: ["main", "v1.1.x"]
8
8
 
9
9
  jobs:
10
10
  build:
@@ -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
@@ -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
@@ -55,6 +55,7 @@ tests/hardhat-project/contracts/CounterUpgradeable.sol
55
55
  tests/hardhat-project/contracts/CounterUpgradeableWithLibrary.sol
56
56
  tests/hardhat-project/contracts/CounterWithLibrary.sol
57
57
  tests/hardhat-project/contracts/Datatypes.sol
58
+ tests/hardhat-project/contracts/EventLauncher.sol
58
59
  tests/hardhat-project/contracts/TestCurrency.sol
59
60
  tests/hardhat-project/contracts/TestCurrencyUUPS.sol
60
61
  tests/hardhat-project/contracts/TestNFT.sol
@@ -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"]}
@@ -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),
@@ -0,0 +1,16 @@
1
+ // SPDX-License-Identifier: UNLICENSED
2
+ pragma solidity ^0.8.9;
3
+
4
+ contract EventLauncher {
5
+ event Event1(uint256 value);
6
+
7
+ event Event2(uint256 value);
8
+
9
+ function launchEvent1(uint256 value) public {
10
+ emit Event1(value);
11
+ }
12
+
13
+ function launchEvent2(uint256 value) public {
14
+ emit Event2(value);
15
+ }
16
+ }
@@ -1,7 +1,11 @@
1
+ from queue import Queue
2
+ from threading import Event, Thread
3
+ from unittest.mock import MagicMock, patch
4
+
1
5
  from hexbytes import HexBytes
2
- from ethproto import aa_bundler
3
6
  from web3.constants import HASH_ZERO
4
- from unittest.mock import MagicMock, patch
7
+
8
+ from ethproto import aa_bundler
5
9
 
6
10
 
7
11
  def test_pack_two():
@@ -65,9 +69,7 @@ def test_hash_packed_user_operation():
65
69
 
66
70
  def test_sign_user_operation():
67
71
  signature = aa_bundler.sign_user_operation(TEST_PRIVATE_KEY, user_operation, CHAIN_ID, ENTRYPOINT)
68
- assert (
69
- signature
70
- == "0xb9b872bfe4e90f4628e8ec24879a5b01045f91da8457f3ce2b417d2e5774b508261ec1147a820e75a141cb61b884a78d7e88996ceddafb9a7016cfe7a48a1f4f1b" # noqa
72
+ assert (signature == "0xb9b872bfe4e90f4628e8ec24879a5b01045f91da8457f3ce2b417d2e5774b508261ec1147a820e75a141cb61b884a78d7e88996ceddafb9a7016cfe7a48a1f4f1b" # noqa
71
73
  )
72
74
 
73
75
 
@@ -75,9 +77,7 @@ def test_sign_user_operation_gas_diff():
75
77
  user_operation_2 = dict(user_operation)
76
78
  user_operation_2["maxPriorityFeePerGas"] -= 1
77
79
  signature = aa_bundler.sign_user_operation(TEST_PRIVATE_KEY, user_operation_2, CHAIN_ID, ENTRYPOINT)
78
- assert (
79
- signature
80
- == "0x8162479d2dbd18d7fe93a2f51e283021d6e4eae4f57d20cdd553042723a0b0ea690ab3903d45126b0047da08ab53dfdf86656e4f258ac4936ba96a759ccb77f61b" # noqa
80
+ assert (signature == "0x8162479d2dbd18d7fe93a2f51e283021d6e4eae4f57d20cdd553042723a0b0ea690ab3903d45126b0047da08ab53dfdf86656e4f258ac4936ba96a759ccb77f61b" # noqa
81
81
  )
82
82
 
83
83
 
@@ -155,8 +155,8 @@ def test_get_nonce_random_key_mode(fetch_nonce_mock, randint_mock):
155
155
  fetch_nonce_mock.assert_not_called()
156
156
  randint_mock.assert_called_with(1, 2**192 - 1)
157
157
  randint_mock.reset_mock()
158
- assert aa_bundler.RANDOM_NONCE_KEY == 444
159
- aa_bundler.RANDOM_NONCE_KEY = None # cleanup
158
+ assert aa_bundler.RANDOM_NONCE_KEY.key == 444
159
+ aa_bundler.RANDOM_NONCE_KEY.key = None # cleanup
160
160
 
161
161
 
162
162
  @patch.object(aa_bundler.random, "randint")
@@ -175,6 +175,7 @@ def test_get_nonce_with_local_cache(fetch_nonce_mock, randint_mock):
175
175
  fetch_nonce_mock.assert_not_called()
176
176
 
177
177
 
178
+ @patch.object(aa_bundler, "AA_BUNDLER_NONCE_MODE", new=aa_bundler.NonceMode.FIXED_KEY_LOCAL_NONCE)
178
179
  @patch.object(aa_bundler, "get_base_fee")
179
180
  def test_send_transaction(get_base_fee_mock):
180
181
  get_base_fee_mock.return_value = 0
@@ -239,4 +240,45 @@ def test_send_transaction(get_base_fee_mock):
239
240
  ret = aa_bundler.send_transaction(w3, tx)
240
241
  get_base_fee_mock.assert_called_once_with(w3)
241
242
  assert aa_bundler.NONCE_CACHE[0] == 1
242
- assert ret == "0xa950a17ca1ed83e974fb1aa227360a007cb65f566518af117ffdbb04d8d2d524"
243
+ assert ret == {"userOpHash": "0xa950a17ca1ed83e974fb1aa227360a007cb65f566518af117ffdbb04d8d2d524"}
244
+
245
+
246
+ def test_random_key_nonces_are_thread_safe():
247
+ queue = Queue()
248
+ event = Event()
249
+
250
+ def worker():
251
+ event.wait() # Get all threads running at the same time
252
+ nonce_key, nonce = aa_bundler.get_nonce_and_key(
253
+ FAIL_IF_USED,
254
+ {"from": TEST_SENDER},
255
+ nonce_mode=aa_bundler.NonceMode.RANDOM_KEY,
256
+ )
257
+ aa_bundler.consume_nonce(nonce_key, nonce)
258
+ queue.put(
259
+ aa_bundler.get_nonce_and_key(
260
+ FAIL_IF_USED,
261
+ {"from": TEST_SENDER},
262
+ nonce_mode=aa_bundler.NonceMode.RANDOM_KEY,
263
+ )
264
+ )
265
+
266
+ threads = [Thread(target=worker) for _ in range(15)]
267
+ for thread in threads:
268
+ thread.start()
269
+
270
+ # Fire all threads at once
271
+ event.set()
272
+ for thread in threads:
273
+ thread.join()
274
+
275
+ nonces = {}
276
+
277
+ while not queue.empty():
278
+ nonce_key, nonce = queue.get_nowait()
279
+ # Each thread got a different key
280
+ assert nonce_key not in nonces
281
+ nonces[nonce_key] = nonce
282
+
283
+ # All nonces are the same
284
+ assert all(nonce == 1 for nonce in nonces.values())
@@ -77,3 +77,32 @@ def test_wrapper_build_from_def():
77
77
  assert counter.value() == 0
78
78
  counter.increase()
79
79
  assert counter.value() == 1
80
+
81
+
82
+ def test_get_events():
83
+ provider = wrappers.get_provider("w3")
84
+ contract_def = provider.get_contract_def("EventLauncher")
85
+ wrapper = wrappers.ETHWrapper.build_from_def(contract_def)
86
+
87
+ launcher = wrapper()
88
+
89
+ launcher.launchEvent1(1)
90
+
91
+ cutoff_block = provider.w3.eth.get_block("latest")
92
+ launcher.launchEvent2(2)
93
+ launcher.launchEvent1(3)
94
+
95
+ all_event1 = provider.get_events(launcher, "Event1", dict(from_block=0))
96
+ assert len(all_event1) == 2
97
+
98
+ first_event1_only = provider.get_events(launcher, "Event1", dict(to_block=cutoff_block.number))
99
+ assert len(first_event1_only) == 1
100
+ assert first_event1_only[0] == all_event1[0]
101
+
102
+ last_event1_only = provider.get_events(launcher, "Event1", dict(from_block=cutoff_block.number + 1))
103
+ assert len(last_event1_only) == 1
104
+ assert last_event1_only[0] == all_event1[-1]
105
+
106
+ event2 = provider.get_events(launcher, "Event2")
107
+ assert len(event2) == 1
108
+ assert event2[0].args.value == 2
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