eth-prototype 1.0.1__tar.gz → 1.1.0__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.0.1 → eth_prototype-1.1.0}/PKG-INFO +4 -8
- {eth_prototype-1.0.1 → eth_prototype-1.1.0}/setup.cfg +2 -6
- {eth_prototype-1.0.1 → eth_prototype-1.1.0}/src/eth_prototype.egg-info/PKG-INFO +4 -8
- {eth_prototype-1.0.1 → eth_prototype-1.1.0}/src/eth_prototype.egg-info/SOURCES.txt +2 -0
- {eth_prototype-1.0.1 → eth_prototype-1.1.0}/src/eth_prototype.egg-info/requires.txt +2 -7
- eth_prototype-1.1.0/src/ethproto/aa_bundler.py +234 -0
- {eth_prototype-1.0.1 → eth_prototype-1.1.0}/src/ethproto/build_artifacts.py +7 -18
- {eth_prototype-1.0.1 → eth_prototype-1.1.0}/src/ethproto/w3wrappers.py +11 -1
- eth_prototype-1.1.0/tests/test_aa_bundler.py +242 -0
- {eth_prototype-1.0.1 → eth_prototype-1.1.0}/tox.ini +4 -18
- {eth_prototype-1.0.1 → eth_prototype-1.1.0}/.coveragerc +0 -0
- {eth_prototype-1.0.1 → eth_prototype-1.1.0}/.github/workflows/publish.yaml +0 -0
- {eth_prototype-1.0.1 → eth_prototype-1.1.0}/.github/workflows/test.yaml +0 -0
- {eth_prototype-1.0.1 → eth_prototype-1.1.0}/.gitignore +0 -0
- {eth_prototype-1.0.1 → eth_prototype-1.1.0}/.isort.cfg +0 -0
- {eth_prototype-1.0.1 → eth_prototype-1.1.0}/.pre-commit-config.yaml +0 -0
- {eth_prototype-1.0.1 → eth_prototype-1.1.0}/.readthedocs.yml +0 -0
- {eth_prototype-1.0.1 → eth_prototype-1.1.0}/AUTHORS.rst +0 -0
- {eth_prototype-1.0.1 → eth_prototype-1.1.0}/CHANGELOG.rst +0 -0
- {eth_prototype-1.0.1 → eth_prototype-1.1.0}/LICENSE.txt +0 -0
- {eth_prototype-1.0.1 → eth_prototype-1.1.0}/README.md +0 -0
- {eth_prototype-1.0.1 → eth_prototype-1.1.0}/docs/Makefile +0 -0
- {eth_prototype-1.0.1 → eth_prototype-1.1.0}/docs/_static/.gitignore +0 -0
- {eth_prototype-1.0.1 → eth_prototype-1.1.0}/docs/authors.rst +0 -0
- {eth_prototype-1.0.1 → eth_prototype-1.1.0}/docs/changelog.rst +0 -0
- {eth_prototype-1.0.1 → eth_prototype-1.1.0}/docs/conf.py +0 -0
- {eth_prototype-1.0.1 → eth_prototype-1.1.0}/docs/index.rst +0 -0
- {eth_prototype-1.0.1 → eth_prototype-1.1.0}/docs/license.rst +0 -0
- {eth_prototype-1.0.1 → eth_prototype-1.1.0}/docs/readme.rst +0 -0
- {eth_prototype-1.0.1 → eth_prototype-1.1.0}/docs/requirements.txt +0 -0
- {eth_prototype-1.0.1 → eth_prototype-1.1.0}/pyproject.toml +0 -0
- {eth_prototype-1.0.1 → eth_prototype-1.1.0}/setup.py +0 -0
- {eth_prototype-1.0.1 → eth_prototype-1.1.0}/src/eth_prototype.egg-info/dependency_links.txt +0 -0
- {eth_prototype-1.0.1 → eth_prototype-1.1.0}/src/eth_prototype.egg-info/not-zip-safe +0 -0
- {eth_prototype-1.0.1 → eth_prototype-1.1.0}/src/eth_prototype.egg-info/top_level.txt +0 -0
- {eth_prototype-1.0.1 → eth_prototype-1.1.0}/src/ethproto/__init__.py +0 -0
- {eth_prototype-1.0.1 → eth_prototype-1.1.0}/src/ethproto/contracts.py +0 -0
- {eth_prototype-1.0.1 → eth_prototype-1.1.0}/src/ethproto/defender_relay.py +0 -0
- {eth_prototype-1.0.1 → eth_prototype-1.1.0}/src/ethproto/wadray.py +0 -0
- {eth_prototype-1.0.1 → eth_prototype-1.1.0}/src/ethproto/wrappers.py +0 -0
- {eth_prototype-1.0.1 → eth_prototype-1.1.0}/tests/conftest.py +0 -0
- {eth_prototype-1.0.1 → eth_prototype-1.1.0}/tests/hardhat-project/README.md +0 -0
- {eth_prototype-1.0.1 → eth_prototype-1.1.0}/tests/hardhat-project/artifacts2/TestCurrency.sol/TestCurrency.json +0 -0
- {eth_prototype-1.0.1 → eth_prototype-1.1.0}/tests/hardhat-project/contracts/Count.sol +0 -0
- {eth_prototype-1.0.1 → eth_prototype-1.1.0}/tests/hardhat-project/contracts/Counter.sol +0 -0
- {eth_prototype-1.0.1 → eth_prototype-1.1.0}/tests/hardhat-project/contracts/CounterUpgradeable.sol +0 -0
- {eth_prototype-1.0.1 → eth_prototype-1.1.0}/tests/hardhat-project/contracts/CounterUpgradeableWithLibrary.sol +0 -0
- {eth_prototype-1.0.1 → eth_prototype-1.1.0}/tests/hardhat-project/contracts/CounterWithLibrary.sol +0 -0
- {eth_prototype-1.0.1 → eth_prototype-1.1.0}/tests/hardhat-project/contracts/Datatypes.sol +0 -0
- {eth_prototype-1.0.1 → eth_prototype-1.1.0}/tests/hardhat-project/contracts/TestCurrency.sol +0 -0
- {eth_prototype-1.0.1 → eth_prototype-1.1.0}/tests/hardhat-project/contracts/TestCurrencyUUPS.sol +0 -0
- {eth_prototype-1.0.1 → eth_prototype-1.1.0}/tests/hardhat-project/contracts/TestNFT.sol +0 -0
- {eth_prototype-1.0.1 → eth_prototype-1.1.0}/tests/hardhat-project/hardhat.config.js +0 -0
- {eth_prototype-1.0.1 → eth_prototype-1.1.0}/tests/hardhat-project/package-lock.json +0 -0
- {eth_prototype-1.0.1 → eth_prototype-1.1.0}/tests/hardhat-project/package.json +0 -0
- {eth_prototype-1.0.1 → eth_prototype-1.1.0}/tests/test_build_artifacts.py +0 -0
- {eth_prototype-1.0.1 → eth_prototype-1.1.0}/tests/test_contracts.py +0 -0
- {eth_prototype-1.0.1 → eth_prototype-1.1.0}/tests/test_defender.py +0 -0
- {eth_prototype-1.0.1 → eth_prototype-1.1.0}/tests/test_time_control.py +0 -0
- {eth_prototype-1.0.1 → eth_prototype-1.1.0}/tests/test_w3.py +0 -0
- {eth_prototype-1.0.1 → eth_prototype-1.1.0}/tests/test_wadray.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: eth-prototype
|
3
|
-
Version: 1.0
|
3
|
+
Version: 1.1.0
|
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
|
@@ -20,7 +20,7 @@ Requires-Dist: requests
|
|
20
20
|
Requires-Dist: hexbytes
|
21
21
|
Requires-Dist: importlib-metadata; python_version < "3.8"
|
22
22
|
Provides-Extra: web3
|
23
|
-
Requires-Dist: web3
|
23
|
+
Requires-Dist: web3==6.*; extra == "web3"
|
24
24
|
Provides-Extra: defender
|
25
25
|
Requires-Dist: boto3; extra == "defender"
|
26
26
|
Provides-Extra: gmpy2
|
@@ -30,12 +30,8 @@ Requires-Dist: setuptools; extra == "testing"
|
|
30
30
|
Requires-Dist: pytest; extra == "testing"
|
31
31
|
Requires-Dist: gmpy2; extra == "testing"
|
32
32
|
Requires-Dist: pytest-cov; extra == "testing"
|
33
|
-
|
34
|
-
Requires-Dist:
|
35
|
-
Requires-Dist: setuptools; extra == "testing-w3"
|
36
|
-
Requires-Dist: pytest; extra == "testing-w3"
|
37
|
-
Requires-Dist: pytest-cov; extra == "testing-w3"
|
38
|
-
Requires-Dist: boto3; extra == "testing-w3"
|
33
|
+
Requires-Dist: web3[tester]==6.*; extra == "testing"
|
34
|
+
Requires-Dist: boto3; extra == "testing"
|
39
35
|
|
40
36
|
# eth-prototype
|
41
37
|
|
@@ -35,7 +35,7 @@ exclude =
|
|
35
35
|
|
36
36
|
[options.extras_require]
|
37
37
|
web3 =
|
38
|
-
web3
|
38
|
+
web3==6.*
|
39
39
|
defender =
|
40
40
|
boto3
|
41
41
|
gmpy2 =
|
@@ -45,11 +45,7 @@ testing =
|
|
45
45
|
pytest
|
46
46
|
gmpy2
|
47
47
|
pytest-cov
|
48
|
-
|
49
|
-
web3[tester]>=6
|
50
|
-
setuptools
|
51
|
-
pytest
|
52
|
-
pytest-cov
|
48
|
+
web3[tester]==6.*
|
53
49
|
boto3
|
54
50
|
|
55
51
|
[options.entry_points]
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: eth-prototype
|
3
|
-
Version: 1.0
|
3
|
+
Version: 1.1.0
|
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
|
@@ -20,7 +20,7 @@ Requires-Dist: requests
|
|
20
20
|
Requires-Dist: hexbytes
|
21
21
|
Requires-Dist: importlib-metadata; python_version < "3.8"
|
22
22
|
Provides-Extra: web3
|
23
|
-
Requires-Dist: web3
|
23
|
+
Requires-Dist: web3==6.*; extra == "web3"
|
24
24
|
Provides-Extra: defender
|
25
25
|
Requires-Dist: boto3; extra == "defender"
|
26
26
|
Provides-Extra: gmpy2
|
@@ -30,12 +30,8 @@ Requires-Dist: setuptools; extra == "testing"
|
|
30
30
|
Requires-Dist: pytest; extra == "testing"
|
31
31
|
Requires-Dist: gmpy2; extra == "testing"
|
32
32
|
Requires-Dist: pytest-cov; extra == "testing"
|
33
|
-
|
34
|
-
Requires-Dist:
|
35
|
-
Requires-Dist: setuptools; extra == "testing-w3"
|
36
|
-
Requires-Dist: pytest; extra == "testing-w3"
|
37
|
-
Requires-Dist: pytest-cov; extra == "testing-w3"
|
38
|
-
Requires-Dist: boto3; extra == "testing-w3"
|
33
|
+
Requires-Dist: web3[tester]==6.*; extra == "testing"
|
34
|
+
Requires-Dist: boto3; extra == "testing"
|
39
35
|
|
40
36
|
# eth-prototype
|
41
37
|
|
@@ -29,6 +29,7 @@ src/eth_prototype.egg-info/not-zip-safe
|
|
29
29
|
src/eth_prototype.egg-info/requires.txt
|
30
30
|
src/eth_prototype.egg-info/top_level.txt
|
31
31
|
src/ethproto/__init__.py
|
32
|
+
src/ethproto/aa_bundler.py
|
32
33
|
src/ethproto/build_artifacts.py
|
33
34
|
src/ethproto/contracts.py
|
34
35
|
src/ethproto/defender_relay.py
|
@@ -36,6 +37,7 @@ src/ethproto/w3wrappers.py
|
|
36
37
|
src/ethproto/wadray.py
|
37
38
|
src/ethproto/wrappers.py
|
38
39
|
tests/conftest.py
|
40
|
+
tests/test_aa_bundler.py
|
39
41
|
tests/test_build_artifacts.py
|
40
42
|
tests/test_contracts.py
|
41
43
|
tests/test_defender.py
|
@@ -0,0 +1,234 @@
|
|
1
|
+
import random
|
2
|
+
from warnings import warn
|
3
|
+
from enum import Enum
|
4
|
+
import requests
|
5
|
+
from environs import Env
|
6
|
+
from eth_abi import encode
|
7
|
+
from eth_account import Account
|
8
|
+
from eth_account.messages import encode_defunct
|
9
|
+
from hexbytes import HexBytes
|
10
|
+
from web3 import Web3
|
11
|
+
from .contracts import RevertError
|
12
|
+
|
13
|
+
|
14
|
+
env = Env()
|
15
|
+
|
16
|
+
AA_BUNDLER_SENDER = env.str("AA_BUNDLER_SENDER", None)
|
17
|
+
AA_BUNDLER_ENTRYPOINT = env.str("AA_BUNDLER_ENTRYPOINT", "0x0000000071727De22E5E9d8BAf0edAc6f37da032")
|
18
|
+
AA_BUNDLER_EXECUTOR_PK = env.str("AA_BUNDLER_EXECUTOR_PK", None)
|
19
|
+
AA_BUNDLER_PROVIDER = env.str("AA_BUNDLER_PROVIDER", "alchemy")
|
20
|
+
|
21
|
+
NonceMode = Enum(
|
22
|
+
"NonceMode",
|
23
|
+
[
|
24
|
+
"RANDOM_KEY", # first time initializes a random key and increments nonce locally with calling the blockchain
|
25
|
+
"FIXED_KEY_LOCAL_NONCE", # uses a fixed key, keeps nonce locally and fetches the nonce when receiving
|
26
|
+
# 'AA25 invalid account nonce'
|
27
|
+
"FIXED_KEY_FETCH_ALWAYS", # uses a fixed key, always fetches unless received as parameter
|
28
|
+
],
|
29
|
+
)
|
30
|
+
|
31
|
+
AA_BUNDLER_NONCE_MODE = env.enum("AA_BUNDLER_NONCE_MODE", default="FIXED_KEY_LOCAL_NONCE", type=NonceMode)
|
32
|
+
AA_BUNDLER_NONCE_KEY = env.int("AA_BUNDLER_NONCE_KEY", 0)
|
33
|
+
AA_BUNDLER_MAX_GETNONCE_RETRIES = env.int("AA_BUNDLER_MAX_GETNONCE_RETRIES", 3)
|
34
|
+
|
35
|
+
|
36
|
+
GET_NONCE_ABI = [
|
37
|
+
{
|
38
|
+
"inputs": [
|
39
|
+
{"internalType": "address", "name": "sender", "type": "address"},
|
40
|
+
{"internalType": "uint192", "name": "key", "type": "uint192"},
|
41
|
+
],
|
42
|
+
"name": "getNonce",
|
43
|
+
"outputs": [{"internalType": "uint256", "name": "nonce", "type": "uint256"}],
|
44
|
+
"stateMutability": "view",
|
45
|
+
"type": "function",
|
46
|
+
}
|
47
|
+
]
|
48
|
+
|
49
|
+
NONCE_CACHE = {}
|
50
|
+
RANDOM_NONCE_KEY = None
|
51
|
+
|
52
|
+
|
53
|
+
def pack_two(a, b):
|
54
|
+
a = HexBytes(a).hex()[2:]
|
55
|
+
b = HexBytes(b).hex()[2:]
|
56
|
+
return "0x" + a.zfill(32) + b.zfill(32)
|
57
|
+
|
58
|
+
|
59
|
+
def _to_uint(x):
|
60
|
+
if isinstance(x, str):
|
61
|
+
return int(x, 16)
|
62
|
+
elif isinstance(x, int):
|
63
|
+
return x
|
64
|
+
raise RuntimeError(f"Invalid int value {x}")
|
65
|
+
|
66
|
+
|
67
|
+
def pack_user_operation(user_operation):
|
68
|
+
# https://github.com/eth-infinitism/account-abstraction/blob/develop/contracts/interfaces/PackedUserOperation.sol
|
69
|
+
return {
|
70
|
+
"sender": user_operation["sender"],
|
71
|
+
"nonce": _to_uint(user_operation["nonce"]),
|
72
|
+
"initCode": "0x",
|
73
|
+
"callData": user_operation["callData"],
|
74
|
+
"accountGasLimits": pack_two(user_operation["verificationGasLimit"], user_operation["callGasLimit"]),
|
75
|
+
"preVerificationGas": _to_uint(user_operation["preVerificationGas"]),
|
76
|
+
"gasFees": pack_two(user_operation["maxPriorityFeePerGas"], user_operation["maxFeePerGas"]),
|
77
|
+
"paymasterAndData": "0x",
|
78
|
+
"signature": "0x",
|
79
|
+
}
|
80
|
+
|
81
|
+
|
82
|
+
def hash_packed_user_operation_only(packed_user_op):
|
83
|
+
# https://github.com/eth-infinitism/account-abstraction/blob/develop/contracts/core/UserOperationLib.sol#L54
|
84
|
+
hash_init_code = Web3.solidity_keccak(["bytes"], [packed_user_op["initCode"]])
|
85
|
+
hash_call_data = Web3.solidity_keccak(["bytes"], [packed_user_op["callData"]])
|
86
|
+
hash_paymaster_and_data = Web3.solidity_keccak(["bytes"], [packed_user_op["paymasterAndData"]])
|
87
|
+
return Web3.keccak(
|
88
|
+
hexstr=encode(
|
89
|
+
["address", "uint256", "bytes32", "bytes32", "bytes32", "uint256", "bytes32", "bytes32"],
|
90
|
+
[
|
91
|
+
packed_user_op["sender"],
|
92
|
+
packed_user_op["nonce"],
|
93
|
+
hash_init_code,
|
94
|
+
hash_call_data,
|
95
|
+
HexBytes(packed_user_op["accountGasLimits"]),
|
96
|
+
packed_user_op["preVerificationGas"],
|
97
|
+
HexBytes(packed_user_op["gasFees"]),
|
98
|
+
hash_paymaster_and_data,
|
99
|
+
],
|
100
|
+
).hex()
|
101
|
+
).hex()
|
102
|
+
|
103
|
+
|
104
|
+
def hash_packed_user_operation(packed_user_op, chain_id, entry_point):
|
105
|
+
return Web3.keccak(
|
106
|
+
hexstr=encode(
|
107
|
+
["bytes32", "address", "uint256"],
|
108
|
+
[HexBytes(hash_packed_user_operation_only(packed_user_op)), entry_point, chain_id],
|
109
|
+
).hex()
|
110
|
+
).hex()
|
111
|
+
|
112
|
+
|
113
|
+
def sign_user_operation(private_key, user_operation, chain_id, entry_point):
|
114
|
+
packed_user_op = pack_user_operation(user_operation)
|
115
|
+
hash = hash_packed_user_operation(packed_user_op, chain_id, entry_point)
|
116
|
+
signature = Account.sign_message(encode_defunct(hexstr=hash), private_key)
|
117
|
+
return signature.signature.hex()
|
118
|
+
|
119
|
+
|
120
|
+
def make_nonce(nonce_key, nonce):
|
121
|
+
nonce_key = _to_uint(nonce_key)
|
122
|
+
nonce = _to_uint(nonce)
|
123
|
+
return (nonce_key << 64) | nonce
|
124
|
+
|
125
|
+
|
126
|
+
def fetch_nonce(w3, account, entry_point, nonce_key):
|
127
|
+
ep = w3.eth.contract(abi=GET_NONCE_ABI, address=entry_point)
|
128
|
+
return ep.functions.getNonce(account, nonce_key).call()
|
129
|
+
|
130
|
+
|
131
|
+
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
|
136
|
+
|
137
|
+
|
138
|
+
def get_nonce_and_key(w3, tx, nonce_mode, entry_point=AA_BUNDLER_ENTRYPOINT, fetch=False):
|
139
|
+
nonce_key = tx.get("nonceKey", None)
|
140
|
+
nonce = tx.get("nonce", None)
|
141
|
+
|
142
|
+
if nonce_key is None:
|
143
|
+
if nonce_mode == NonceMode.RANDOM_KEY:
|
144
|
+
nonce_key = get_random_nonce_key()
|
145
|
+
else:
|
146
|
+
nonce_key = AA_BUNDLER_NONCE_KEY
|
147
|
+
|
148
|
+
if nonce is None:
|
149
|
+
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
|
153
|
+
else:
|
154
|
+
nonce = NONCE_CACHE[nonce_key]
|
155
|
+
return nonce_key, nonce
|
156
|
+
|
157
|
+
|
158
|
+
def handle_response_error(resp, w3, tx, retry_nonce):
|
159
|
+
if "AA25" in resp["error"]["message"] and AA_BUNDLER_MAX_GETNONCE_RETRIES > 0:
|
160
|
+
# Retry fetching the nonce
|
161
|
+
if retry_nonce == AA_BUNDLER_MAX_GETNONCE_RETRIES:
|
162
|
+
raise RevertError(resp["error"]["message"])
|
163
|
+
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)
|
165
|
+
else:
|
166
|
+
raise RevertError(resp["error"]["message"])
|
167
|
+
|
168
|
+
|
169
|
+
def get_base_fee(w3):
|
170
|
+
blk = w3.eth.get_block("latest")
|
171
|
+
return blk["baseFeePerGas"]
|
172
|
+
|
173
|
+
|
174
|
+
def send_transaction(w3, tx, retry_nonce=None):
|
175
|
+
nonce_key, nonce = get_nonce_and_key(
|
176
|
+
w3, tx, AA_BUNDLER_NONCE_MODE, entry_point=AA_BUNDLER_ENTRYPOINT, fetch=retry_nonce is not None
|
177
|
+
)
|
178
|
+
# "0xb61d27f6" = bytes4 hash of execute(address,uint256,bytes)
|
179
|
+
call_data = (
|
180
|
+
"0xb61d27f6"
|
181
|
+
+ encode(["address", "uint256", "bytes"], [tx["to"], tx["value"], HexBytes(tx["data"])]).hex()
|
182
|
+
)
|
183
|
+
dummy_signature = (
|
184
|
+
"0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007"
|
185
|
+
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c"
|
186
|
+
)
|
187
|
+
user_operation = {
|
188
|
+
"sender": tx.get("from", AA_BUNDLER_SENDER),
|
189
|
+
"nonce": hex(make_nonce(nonce_key, nonce)),
|
190
|
+
"callData": call_data,
|
191
|
+
"signature": dummy_signature,
|
192
|
+
}
|
193
|
+
|
194
|
+
if AA_BUNDLER_PROVIDER == "alchemy":
|
195
|
+
resp = w3.provider.make_request(
|
196
|
+
"eth_estimateUserOperationGas", [user_operation, AA_BUNDLER_ENTRYPOINT]
|
197
|
+
)
|
198
|
+
if "error" in resp:
|
199
|
+
return handle_response_error(resp, w3, tx, retry_nonce)
|
200
|
+
|
201
|
+
user_operation.update(resp["result"])
|
202
|
+
|
203
|
+
resp = w3.provider.make_request("rundler_maxPriorityFeePerGas", [])
|
204
|
+
if "error" in resp:
|
205
|
+
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
|
+
}
|
218
|
+
)
|
219
|
+
user_operation["signature"] = sign_user_operation(
|
220
|
+
AA_BUNDLER_EXECUTOR_PK, user_operation, tx["chainId"], AA_BUNDLER_ENTRYPOINT
|
221
|
+
)
|
222
|
+
# Remove paymaster related fields
|
223
|
+
user_operation.pop("paymaster", None)
|
224
|
+
user_operation.pop("paymasterData", None)
|
225
|
+
user_operation.pop("paymasterVerificationGasLimit", None)
|
226
|
+
user_operation.pop("paymasterPostOpGasLimit", None)
|
227
|
+
|
228
|
+
resp = w3.provider.make_request("eth_sendUserOperation", [user_operation, AA_BUNDLER_ENTRYPOINT])
|
229
|
+
if "error" in resp:
|
230
|
+
return handle_response_error(resp, w3, tx, retry_nonce)
|
231
|
+
|
232
|
+
# Store nonce in the cache, so next time uses a new nonce
|
233
|
+
NONCE_CACHE[nonce_key] = nonce + 1
|
234
|
+
return resp["result"]
|
@@ -1,6 +1,5 @@
|
|
1
1
|
"""Helper classes to use hardhat build artifacts from python"""
|
2
2
|
|
3
|
-
|
4
3
|
import json
|
5
4
|
import os
|
6
5
|
import os.path
|
@@ -26,17 +25,15 @@ class Artifact:
|
|
26
25
|
self.abi = kwargs["abi"]
|
27
26
|
self.bytecode = kwargs["bytecode"]
|
28
27
|
self.deployed_bytecode = kwargs["deployedBytecode"]
|
29
|
-
self.link_references = kwargs
|
30
|
-
self.deployed_link_references = kwargs
|
28
|
+
self.link_references = kwargs.get("linkReferences", {})
|
29
|
+
self.deployed_link_references = kwargs.get("deployedLinkReferences", {})
|
31
30
|
|
32
31
|
def link(self, libraries: dict) -> "Artifact":
|
33
32
|
"""Returns a new artifact with the external libraries linked
|
34
33
|
|
35
34
|
Libraries is a dictionary of the form {library_name: address}
|
36
35
|
"""
|
37
|
-
bytecode = self._replace_link_references(
|
38
|
-
self.bytecode, self.link_references, libraries
|
39
|
-
)
|
36
|
+
bytecode = self._replace_link_references(self.bytecode, self.link_references, libraries)
|
40
37
|
deployed_bytecode = self._replace_link_references(
|
41
38
|
self.deployed_bytecode, self.deployed_link_references, libraries
|
42
39
|
)
|
@@ -55,9 +52,7 @@ class Artifact:
|
|
55
52
|
for lib in libs.keys():
|
56
53
|
yield lib, source
|
57
54
|
|
58
|
-
def _replace_link_references(
|
59
|
-
self, bytecode: str, link_references: dict, libraries: dict
|
60
|
-
) -> str:
|
55
|
+
def _replace_link_references(self, bytecode: str, link_references: dict, libraries: dict) -> str:
|
61
56
|
# remove 0x prefix if present
|
62
57
|
bytecode = bytecode[2:] if bytecode.startswith("0x") else bytecode
|
63
58
|
|
@@ -115,17 +110,13 @@ class ArtifactLibrary:
|
|
115
110
|
|
116
111
|
if contract not in self._fullpath_cache:
|
117
112
|
for path in self.lookup_paths:
|
118
|
-
build_artifact_path = (
|
119
|
-
path / contract / contract.with_suffix(".json").name
|
120
|
-
)
|
113
|
+
build_artifact_path = path / contract / contract.with_suffix(".json").name
|
121
114
|
if build_artifact_path.exists():
|
122
115
|
with open(build_artifact_path) as f:
|
123
116
|
self._fullpath_cache[contract] = Artifact(**json.load(f))
|
124
117
|
|
125
118
|
if contract not in self._fullpath_cache:
|
126
|
-
raise FileNotFoundError(
|
127
|
-
f"Could not find artifact for {contract} on {self.lookup_paths}"
|
128
|
-
)
|
119
|
+
raise FileNotFoundError(f"Could not find artifact for {contract} on {self.lookup_paths}")
|
129
120
|
|
130
121
|
return self._fullpath_cache[contract]
|
131
122
|
|
@@ -145,8 +136,6 @@ class ArtifactLibrary:
|
|
145
136
|
self._name_cache[contract_name] = Artifact(**json.load(f))
|
146
137
|
|
147
138
|
if contract_name not in self._name_cache:
|
148
|
-
raise FileNotFoundError(
|
149
|
-
f"Could not find artifact for {contract_name} on {self.lookup_paths}"
|
150
|
-
)
|
139
|
+
raise FileNotFoundError(f"Could not find artifact for {contract_name} on {self.lookup_paths}")
|
151
140
|
|
152
141
|
return self._name_cache[contract_name]
|
@@ -108,9 +108,19 @@ def transact(provider, function, tx_kwargs):
|
|
108
108
|
elif W3_TRANSACT_MODE == "defender-async":
|
109
109
|
from .defender_relay import send_transaction
|
110
110
|
|
111
|
-
tx_kwargs
|
111
|
+
tx_kwargs |= provider.tx_kwargs
|
112
112
|
tx = function.build_transaction(tx_kwargs)
|
113
113
|
return send_transaction(tx)
|
114
|
+
elif W3_TRANSACT_MODE == "aa-bundler-async":
|
115
|
+
from .aa_bundler import send_transaction
|
116
|
+
|
117
|
+
tx_kwargs |= provider.tx_kwargs
|
118
|
+
# To avoid fetching gas and gasPrice in a standard way, when it's not relevant for user ops
|
119
|
+
tx_kwargs.update(dict(gas=0, gasPrice=0))
|
120
|
+
tx = function.build_transaction(tx_kwargs)
|
121
|
+
return send_transaction(provider.w3, tx)
|
122
|
+
else:
|
123
|
+
raise RuntimeError(f"Unknown W3_TRANSACT_MODE {W3_TRANSACT_MODE}")
|
114
124
|
|
115
125
|
return provider.w3.eth.wait_for_transaction_receipt(tx_hash)
|
116
126
|
|
@@ -0,0 +1,242 @@
|
|
1
|
+
from hexbytes import HexBytes
|
2
|
+
from ethproto import aa_bundler
|
3
|
+
from web3.constants import HASH_ZERO
|
4
|
+
from unittest.mock import MagicMock, patch
|
5
|
+
|
6
|
+
|
7
|
+
def test_pack_two():
|
8
|
+
assert aa_bundler.pack_two(0, 0) == HASH_ZERO
|
9
|
+
assert aa_bundler.pack_two(1, 2) == "0x0000000000000000000000000000000100000000000000000000000000000002"
|
10
|
+
assert (
|
11
|
+
aa_bundler.pack_two("0x1", "0x2")
|
12
|
+
== "0x0000000000000000000000000000000100000000000000000000000000000002"
|
13
|
+
)
|
14
|
+
assert aa_bundler.pack_two(HexBytes(2), HexBytes(3) == "0x{:032x}{:032x}".format(2, 3))
|
15
|
+
|
16
|
+
|
17
|
+
TEST_CALL_DATA = "0x47e1da2a000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000020000000000000000000000002791bca1f2de4661ed88a30c99a7a9449aa841740000000000000000000000002791bca1f2de4661ed88a30c99a7a9449aa8417400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000044a9059cbb00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c800000000000000000000000000000000000000000000000000000000004c4b40000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000044a9059cbb0000000000000000000000003c44cdddb6a900fa2b585dd299e03d12fa4293bc000000000000000000000000000000000000000000000000000000000098968000000000000000000000000000000000000000000000000000000000" # noqa
|
18
|
+
|
19
|
+
# Private key of index=1 of seed phrase ["test"] * 11 + ["junk"]
|
20
|
+
TEST_PRIVATE_KEY = "0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d"
|
21
|
+
CHAIN_ID = 31337
|
22
|
+
ENTRYPOINT = "0x0000000071727De22E5E9d8BAf0edAc6f37da032"
|
23
|
+
|
24
|
+
TEST_SENDER = "0x8961423b54f06bf6D57F8dD3dD1184FA6F3aac3f"
|
25
|
+
|
26
|
+
user_operation = {
|
27
|
+
"sender": TEST_SENDER,
|
28
|
+
"nonce": 0,
|
29
|
+
"initCode": "0x",
|
30
|
+
"callData": TEST_CALL_DATA,
|
31
|
+
"callGasLimit": 999999,
|
32
|
+
"verificationGasLimit": 999999,
|
33
|
+
"preVerificationGas": 999999,
|
34
|
+
"maxFeePerGas": 1000000000,
|
35
|
+
"maxPriorityFeePerGas": 1000000000,
|
36
|
+
"paymaster": "0x0000000000000000000000000000000000000000",
|
37
|
+
"paymasterData": "0x",
|
38
|
+
"paymasterVerificationGasLimit": 0,
|
39
|
+
"paymasterPostOpGasLimit": 0,
|
40
|
+
}
|
41
|
+
|
42
|
+
|
43
|
+
def test_pack_user_operation():
|
44
|
+
expected = {
|
45
|
+
"sender": TEST_SENDER,
|
46
|
+
"nonce": 0,
|
47
|
+
"initCode": "0x",
|
48
|
+
"callData": TEST_CALL_DATA,
|
49
|
+
"accountGasLimits": "0x000000000000000000000000000f423f000000000000000000000000000f423f",
|
50
|
+
"preVerificationGas": 999999,
|
51
|
+
"gasFees": "0x0000000000000000000000003b9aca000000000000000000000000003b9aca00",
|
52
|
+
"paymasterAndData": "0x",
|
53
|
+
"signature": "0x",
|
54
|
+
}
|
55
|
+
assert aa_bundler.pack_user_operation(user_operation) == expected
|
56
|
+
|
57
|
+
|
58
|
+
def test_hash_packed_user_operation():
|
59
|
+
packed = aa_bundler.pack_user_operation(user_operation)
|
60
|
+
hash = aa_bundler.hash_packed_user_operation_only(packed)
|
61
|
+
assert hash == "0xa2c19765d18b0d690c05b20061bd23d066201aff1833a51bd28af115fbd4bcd9"
|
62
|
+
hash = aa_bundler.hash_packed_user_operation(packed, CHAIN_ID, ENTRYPOINT)
|
63
|
+
assert hash == "0xb365ad4d366e9081718e926912da7a78a2faae592286fda0cc11923bd141b7cf"
|
64
|
+
|
65
|
+
|
66
|
+
def test_sign_user_operation():
|
67
|
+
signature = aa_bundler.sign_user_operation(TEST_PRIVATE_KEY, user_operation, CHAIN_ID, ENTRYPOINT)
|
68
|
+
assert (
|
69
|
+
signature
|
70
|
+
== "0xb9b872bfe4e90f4628e8ec24879a5b01045f91da8457f3ce2b417d2e5774b508261ec1147a820e75a141cb61b884a78d7e88996ceddafb9a7016cfe7a48a1f4f1b" # noqa
|
71
|
+
)
|
72
|
+
|
73
|
+
|
74
|
+
def test_sign_user_operation_gas_diff():
|
75
|
+
user_operation_2 = dict(user_operation)
|
76
|
+
user_operation_2["maxPriorityFeePerGas"] -= 1
|
77
|
+
signature = aa_bundler.sign_user_operation(TEST_PRIVATE_KEY, user_operation_2, CHAIN_ID, ENTRYPOINT)
|
78
|
+
assert (
|
79
|
+
signature
|
80
|
+
== "0x8162479d2dbd18d7fe93a2f51e283021d6e4eae4f57d20cdd553042723a0b0ea690ab3903d45126b0047da08ab53dfdf86656e4f258ac4936ba96a759ccb77f61b" # noqa
|
81
|
+
)
|
82
|
+
|
83
|
+
|
84
|
+
def test_make_nonce():
|
85
|
+
assert aa_bundler.make_nonce(0, 0) == 0
|
86
|
+
assert aa_bundler.make_nonce(0, 1) == 1
|
87
|
+
assert aa_bundler.make_nonce(1, 1) == (1 << 64) + 1
|
88
|
+
|
89
|
+
|
90
|
+
FAIL_IF_USED = object()
|
91
|
+
|
92
|
+
|
93
|
+
@patch.object(aa_bundler.random, "randint")
|
94
|
+
@patch.object(aa_bundler, "fetch_nonce")
|
95
|
+
def test_get_nonce_force_fetch(fetch_nonce_mock, randint_mock):
|
96
|
+
# Test fetch=True
|
97
|
+
fetch_nonce_mock.return_value = 123
|
98
|
+
assert aa_bundler.get_nonce_and_key(
|
99
|
+
FAIL_IF_USED,
|
100
|
+
{"nonceKey": 12, "from": TEST_SENDER},
|
101
|
+
nonce_mode=aa_bundler.NonceMode.FIXED_KEY_LOCAL_NONCE,
|
102
|
+
fetch=True,
|
103
|
+
) == (12, 123)
|
104
|
+
fetch_nonce_mock.assert_called_once_with(FAIL_IF_USED, TEST_SENDER, ENTRYPOINT, 12)
|
105
|
+
randint_mock.assert_not_called()
|
106
|
+
|
107
|
+
|
108
|
+
@patch.object(aa_bundler.random, "randint")
|
109
|
+
@patch.object(aa_bundler, "fetch_nonce")
|
110
|
+
def test_get_nonce_fetch_always_mode(fetch_nonce_mock, randint_mock):
|
111
|
+
# Test nonce_mode=NonceMode.FIXED_KEY_FETCH_ALWAYS
|
112
|
+
fetch_nonce_mock.return_value = 111
|
113
|
+
assert aa_bundler.get_nonce_and_key(
|
114
|
+
FAIL_IF_USED,
|
115
|
+
{"nonceKey": 22, "from": TEST_SENDER},
|
116
|
+
nonce_mode=aa_bundler.NonceMode.FIXED_KEY_FETCH_ALWAYS,
|
117
|
+
) == (22, 111)
|
118
|
+
fetch_nonce_mock.assert_called_once_with(FAIL_IF_USED, TEST_SENDER, ENTRYPOINT, 22)
|
119
|
+
randint_mock.assert_not_called()
|
120
|
+
fetch_nonce_mock.reset_mock()
|
121
|
+
|
122
|
+
|
123
|
+
@patch.object(aa_bundler.random, "randint")
|
124
|
+
@patch.object(aa_bundler, "fetch_nonce")
|
125
|
+
def test_get_nonce_nonce_key_in_tx(fetch_nonce_mock, randint_mock):
|
126
|
+
# Test nonce_mode=NonceMode.FIXED_KEY_LOCAL_NONCE
|
127
|
+
assert aa_bundler.get_nonce_and_key(
|
128
|
+
FAIL_IF_USED,
|
129
|
+
{"nonceKey": 22, "from": TEST_SENDER},
|
130
|
+
nonce_mode=aa_bundler.NonceMode.FIXED_KEY_LOCAL_NONCE,
|
131
|
+
) == (22, 0)
|
132
|
+
randint_mock.assert_not_called()
|
133
|
+
fetch_nonce_mock.assert_not_called()
|
134
|
+
|
135
|
+
# Same if nonce_mode=NonceMode.RANDOM_KEY but nonceKey in the tx
|
136
|
+
assert aa_bundler.get_nonce_and_key(
|
137
|
+
FAIL_IF_USED,
|
138
|
+
{"nonceKey": 22, "from": TEST_SENDER},
|
139
|
+
nonce_mode=aa_bundler.NonceMode.RANDOM_KEY,
|
140
|
+
) == (22, 0)
|
141
|
+
randint_mock.assert_not_called()
|
142
|
+
fetch_nonce_mock.assert_not_called()
|
143
|
+
|
144
|
+
|
145
|
+
@patch.object(aa_bundler.random, "randint")
|
146
|
+
@patch.object(aa_bundler, "fetch_nonce")
|
147
|
+
def test_get_nonce_random_key_mode(fetch_nonce_mock, randint_mock):
|
148
|
+
# If nonce_mode=NonceMode.RANDOM_KEY creates a random key and stores it
|
149
|
+
randint_mock.return_value = 444
|
150
|
+
assert aa_bundler.get_nonce_and_key(
|
151
|
+
FAIL_IF_USED,
|
152
|
+
{"from": TEST_SENDER},
|
153
|
+
nonce_mode=aa_bundler.NonceMode.RANDOM_KEY,
|
154
|
+
) == (444, 0)
|
155
|
+
fetch_nonce_mock.assert_not_called()
|
156
|
+
randint_mock.assert_called_with(1, 2**192 - 1)
|
157
|
+
randint_mock.reset_mock()
|
158
|
+
assert aa_bundler.RANDOM_NONCE_KEY == 444
|
159
|
+
aa_bundler.RANDOM_NONCE_KEY = None # cleanup
|
160
|
+
|
161
|
+
|
162
|
+
@patch.object(aa_bundler.random, "randint")
|
163
|
+
@patch.object(aa_bundler, "fetch_nonce")
|
164
|
+
def test_get_nonce_with_local_cache(fetch_nonce_mock, randint_mock):
|
165
|
+
with patch.object(aa_bundler, "AA_BUNDLER_NONCE_KEY", new=55), patch.object(
|
166
|
+
aa_bundler, "NONCE_CACHE", new={55: 33}
|
167
|
+
):
|
168
|
+
# Test nonce_mode=NonceMode.FIXED_KEY_LOCAL_NONCE
|
169
|
+
assert aa_bundler.get_nonce_and_key(
|
170
|
+
FAIL_IF_USED,
|
171
|
+
{"from": TEST_SENDER},
|
172
|
+
nonce_mode=aa_bundler.NonceMode.FIXED_KEY_LOCAL_NONCE,
|
173
|
+
) == (55, 33)
|
174
|
+
randint_mock.assert_not_called()
|
175
|
+
fetch_nonce_mock.assert_not_called()
|
176
|
+
|
177
|
+
|
178
|
+
@patch.object(aa_bundler, "get_base_fee")
|
179
|
+
def test_send_transaction(get_base_fee_mock):
|
180
|
+
get_base_fee_mock.return_value = 0
|
181
|
+
w3 = MagicMock()
|
182
|
+
w3.eth.chain_id = CHAIN_ID
|
183
|
+
|
184
|
+
aa_bundler.AA_BUNDLER_EXECUTOR_PK = TEST_PRIVATE_KEY
|
185
|
+
|
186
|
+
tx = {
|
187
|
+
"value": 0,
|
188
|
+
"chainId": 137,
|
189
|
+
"from": "0xE8B412158c205B0F605e0FC09dCdA27d3F140FE9",
|
190
|
+
"to": "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174",
|
191
|
+
"data": "0x095ea7b30000000000000000000000007ace242f32208d836a2245df957c08547059bf45ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", # noqa
|
192
|
+
}
|
193
|
+
|
194
|
+
def make_request(method, params):
|
195
|
+
if method == "eth_estimateUserOperationGas":
|
196
|
+
assert len(params) == 2
|
197
|
+
assert params[1] == ENTRYPOINT
|
198
|
+
assert params[0] == {
|
199
|
+
"sender": "0xE8B412158c205B0F605e0FC09dCdA27d3F140FE9",
|
200
|
+
"nonce": "0x0",
|
201
|
+
"callData": "0xb61d27f60000000000000000000000002791bca1f2de4661ed88a30c99a7a9449aa84174000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044095ea7b30000000000000000000000007ace242f32208d836a2245df957c08547059bf45ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000", # noqa
|
202
|
+
"signature": "0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c", # noqa
|
203
|
+
}
|
204
|
+
return {
|
205
|
+
"jsonrpc": "2.0",
|
206
|
+
"id": 1,
|
207
|
+
"result": {
|
208
|
+
"preVerificationGas": "0xb430",
|
209
|
+
"callGasLimit": "0xcbb8",
|
210
|
+
"verificationGasLimit": "0x13664",
|
211
|
+
"paymasterVerificationGasLimit": None,
|
212
|
+
},
|
213
|
+
}
|
214
|
+
elif method == "rundler_maxPriorityFeePerGas":
|
215
|
+
assert len(params) == 0
|
216
|
+
return {"jsonrpc": "2.0", "id": 1, "result": "0x7ffffffff"}
|
217
|
+
elif method == "eth_sendUserOperation":
|
218
|
+
assert len(params) == 2
|
219
|
+
assert params[1] == ENTRYPOINT
|
220
|
+
assert params[0] == {
|
221
|
+
"sender": "0xE8B412158c205B0F605e0FC09dCdA27d3F140FE9",
|
222
|
+
"nonce": "0x0",
|
223
|
+
"callData": "0xb61d27f60000000000000000000000002791bca1f2de4661ed88a30c99a7a9449aa84174000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044095ea7b30000000000000000000000007ace242f32208d836a2245df957c08547059bf45ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000", # noqa
|
224
|
+
"callGasLimit": "0xcbb8",
|
225
|
+
"verificationGasLimit": "0x13664",
|
226
|
+
"preVerificationGas": "0xb430",
|
227
|
+
"maxFeePerGas": "0x7ffffffff",
|
228
|
+
"maxPriorityFeePerGas": "0x7ffffffff",
|
229
|
+
"signature": "0x7980544d044bc1202fed7edec96f2fa795ab8670b439935e6bbb5104e95d84ea32af8bff187913ff7eb2b442baab06d0c300273942e312332659ab0a194bbbe81c", # noqa
|
230
|
+
}
|
231
|
+
return {
|
232
|
+
"jsonrpc": "2.0",
|
233
|
+
"id": 1,
|
234
|
+
"result": "0xa950a17ca1ed83e974fb1aa227360a007cb65f566518af117ffdbb04d8d2d524",
|
235
|
+
}
|
236
|
+
|
237
|
+
w3.provider.make_request.side_effect = make_request
|
238
|
+
|
239
|
+
ret = aa_bundler.send_transaction(w3, tx)
|
240
|
+
get_base_fee_mock.assert_called_once_with(w3)
|
241
|
+
assert aa_bundler.NONCE_CACHE[0] == 1
|
242
|
+
assert ret == "0xa950a17ca1ed83e974fb1aa227360a007cb65f566518af117ffdbb04d8d2d524"
|
@@ -4,12 +4,12 @@
|
|
4
4
|
|
5
5
|
[tox]
|
6
6
|
minversion = 3.15
|
7
|
-
envlist = {py39,py310}
|
7
|
+
envlist = {py39,py310}
|
8
8
|
|
9
9
|
[gh-actions]
|
10
10
|
python =
|
11
|
-
3.9: py39
|
12
|
-
3.10: py310
|
11
|
+
3.9: py39
|
12
|
+
3.10: py310
|
13
13
|
|
14
14
|
[testenv]
|
15
15
|
description = invoke pytest to run automated tests
|
@@ -20,26 +20,12 @@ setenv =
|
|
20
20
|
passenv =
|
21
21
|
HOME
|
22
22
|
WADRAY_USE_GMPY2
|
23
|
-
extras =
|
24
|
-
testing
|
25
|
-
commands =
|
26
|
-
pytest {posargs}
|
27
|
-
|
28
|
-
|
29
|
-
[testenv:{py39,py310}-w3]
|
30
|
-
description = invoke pytest to run automated tests
|
31
|
-
isolated_build = True
|
32
|
-
setenv =
|
33
|
-
TOXINIDIR = {toxinidir}
|
34
|
-
TEST_ENV = web3py
|
35
|
-
passenv =
|
36
|
-
HOME
|
37
23
|
W3_*
|
38
24
|
TRANSACT_MODE
|
39
25
|
WEB3_PROVIDER_URI
|
40
26
|
WEB3_*
|
41
27
|
extras =
|
42
|
-
testing
|
28
|
+
testing
|
43
29
|
deps =
|
44
30
|
warrant @ git+https://github.com/gnarvaja/warrant.git#egg=warrant
|
45
31
|
commands =
|
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
|
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.0.1 → eth_prototype-1.1.0}/tests/hardhat-project/contracts/CounterUpgradeable.sol
RENAMED
File without changes
|
File without changes
|
{eth_prototype-1.0.1 → eth_prototype-1.1.0}/tests/hardhat-project/contracts/CounterWithLibrary.sol
RENAMED
File without changes
|
File without changes
|
{eth_prototype-1.0.1 → eth_prototype-1.1.0}/tests/hardhat-project/contracts/TestCurrency.sol
RENAMED
File without changes
|
{eth_prototype-1.0.1 → eth_prototype-1.1.0}/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
|