eth-prototype 1.3.5__tar.gz → 1.4.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.3.5 → eth_prototype-1.4.0}/.github/workflows/publish.yaml +1 -1
- {eth_prototype-1.3.5 → eth_prototype-1.4.0}/.github/workflows/test.yaml +1 -1
- {eth_prototype-1.3.5 → eth_prototype-1.4.0}/.pre-commit-config.yaml +4 -4
- {eth_prototype-1.3.5 → eth_prototype-1.4.0}/PKG-INFO +1 -1
- {eth_prototype-1.3.5 → eth_prototype-1.4.0}/src/eth_prototype.egg-info/PKG-INFO +1 -1
- {eth_prototype-1.3.5 → eth_prototype-1.4.0}/src/ethproto/build_artifacts.py +4 -0
- {eth_prototype-1.3.5 → eth_prototype-1.4.0}/src/ethproto/contracts.py +132 -33
- {eth_prototype-1.3.5 → eth_prototype-1.4.0}/tests/test_build_artifacts.py +8 -0
- {eth_prototype-1.3.5 → eth_prototype-1.4.0}/tests/test_contracts.py +20 -4
- {eth_prototype-1.3.5 → eth_prototype-1.4.0}/tox.ini +2 -2
- {eth_prototype-1.3.5 → eth_prototype-1.4.0}/.coveragerc +0 -0
- {eth_prototype-1.3.5 → eth_prototype-1.4.0}/.gitignore +0 -0
- {eth_prototype-1.3.5 → eth_prototype-1.4.0}/.isort.cfg +0 -0
- {eth_prototype-1.3.5 → eth_prototype-1.4.0}/.readthedocs.yml +0 -0
- {eth_prototype-1.3.5 → eth_prototype-1.4.0}/AUTHORS.rst +0 -0
- {eth_prototype-1.3.5 → eth_prototype-1.4.0}/CHANGELOG.rst +0 -0
- {eth_prototype-1.3.5 → eth_prototype-1.4.0}/LICENSE.txt +0 -0
- {eth_prototype-1.3.5 → eth_prototype-1.4.0}/README.md +0 -0
- {eth_prototype-1.3.5 → eth_prototype-1.4.0}/docs/Makefile +0 -0
- {eth_prototype-1.3.5 → eth_prototype-1.4.0}/docs/_static/.gitignore +0 -0
- {eth_prototype-1.3.5 → eth_prototype-1.4.0}/docs/authors.rst +0 -0
- {eth_prototype-1.3.5 → eth_prototype-1.4.0}/docs/changelog.rst +0 -0
- {eth_prototype-1.3.5 → eth_prototype-1.4.0}/docs/conf.py +0 -0
- {eth_prototype-1.3.5 → eth_prototype-1.4.0}/docs/index.rst +0 -0
- {eth_prototype-1.3.5 → eth_prototype-1.4.0}/docs/license.rst +0 -0
- {eth_prototype-1.3.5 → eth_prototype-1.4.0}/docs/readme.rst +0 -0
- {eth_prototype-1.3.5 → eth_prototype-1.4.0}/docs/requirements.txt +0 -0
- {eth_prototype-1.3.5 → eth_prototype-1.4.0}/pyproject.toml +0 -0
- {eth_prototype-1.3.5 → eth_prototype-1.4.0}/setup.cfg +0 -0
- {eth_prototype-1.3.5 → eth_prototype-1.4.0}/setup.py +0 -0
- {eth_prototype-1.3.5 → eth_prototype-1.4.0}/src/eth_prototype.egg-info/SOURCES.txt +0 -0
- {eth_prototype-1.3.5 → eth_prototype-1.4.0}/src/eth_prototype.egg-info/dependency_links.txt +0 -0
- {eth_prototype-1.3.5 → eth_prototype-1.4.0}/src/eth_prototype.egg-info/not-zip-safe +0 -0
- {eth_prototype-1.3.5 → eth_prototype-1.4.0}/src/eth_prototype.egg-info/requires.txt +0 -0
- {eth_prototype-1.3.5 → eth_prototype-1.4.0}/src/eth_prototype.egg-info/top_level.txt +0 -0
- {eth_prototype-1.3.5 → eth_prototype-1.4.0}/src/ethproto/__init__.py +0 -0
- {eth_prototype-1.3.5 → eth_prototype-1.4.0}/src/ethproto/aa_bundler.py +0 -0
- {eth_prototype-1.3.5 → eth_prototype-1.4.0}/src/ethproto/defender_relay.py +0 -0
- {eth_prototype-1.3.5 → eth_prototype-1.4.0}/src/ethproto/test_utils/__init__.py +0 -0
- {eth_prototype-1.3.5 → eth_prototype-1.4.0}/src/ethproto/test_utils/factories.py +0 -0
- {eth_prototype-1.3.5 → eth_prototype-1.4.0}/src/ethproto/test_utils/hardhat.py +0 -0
- {eth_prototype-1.3.5 → eth_prototype-1.4.0}/src/ethproto/test_utils/vcr_utils.py +0 -0
- {eth_prototype-1.3.5 → eth_prototype-1.4.0}/src/ethproto/w3wrappers.py +0 -0
- {eth_prototype-1.3.5 → eth_prototype-1.4.0}/src/ethproto/wadray.py +0 -0
- {eth_prototype-1.3.5 → eth_prototype-1.4.0}/src/ethproto/wrappers.py +0 -0
- {eth_prototype-1.3.5 → eth_prototype-1.4.0}/tests/__init__.py +0 -0
- {eth_prototype-1.3.5 → eth_prototype-1.4.0}/tests/cassettes/test_aa_bundler/test_build_user_operation.yaml +0 -0
- {eth_prototype-1.3.5 → eth_prototype-1.4.0}/tests/conftest.py +0 -0
- {eth_prototype-1.3.5 → eth_prototype-1.4.0}/tests/hardhat-project/README.md +0 -0
- {eth_prototype-1.3.5 → eth_prototype-1.4.0}/tests/hardhat-project/artifacts2/TestCurrency.sol/TestCurrency.json +0 -0
- {eth_prototype-1.3.5 → eth_prototype-1.4.0}/tests/hardhat-project/contracts/Count.sol +0 -0
- {eth_prototype-1.3.5 → eth_prototype-1.4.0}/tests/hardhat-project/contracts/Counter.sol +0 -0
- {eth_prototype-1.3.5 → eth_prototype-1.4.0}/tests/hardhat-project/contracts/CounterUpgradeable.sol +0 -0
- {eth_prototype-1.3.5 → eth_prototype-1.4.0}/tests/hardhat-project/contracts/CounterUpgradeableWithLibrary.sol +0 -0
- {eth_prototype-1.3.5 → eth_prototype-1.4.0}/tests/hardhat-project/contracts/CounterWithLibrary.sol +0 -0
- {eth_prototype-1.3.5 → eth_prototype-1.4.0}/tests/hardhat-project/contracts/Datatypes.sol +0 -0
- {eth_prototype-1.3.5 → eth_prototype-1.4.0}/tests/hardhat-project/contracts/EventLauncher.sol +0 -0
- {eth_prototype-1.3.5 → eth_prototype-1.4.0}/tests/hardhat-project/contracts/TestCurrency.sol +0 -0
- {eth_prototype-1.3.5 → eth_prototype-1.4.0}/tests/hardhat-project/contracts/TestCurrencyUUPS.sol +0 -0
- {eth_prototype-1.3.5 → eth_prototype-1.4.0}/tests/hardhat-project/contracts/TestNFT.sol +0 -0
- {eth_prototype-1.3.5 → eth_prototype-1.4.0}/tests/hardhat-project/hardhat.config.js +0 -0
- {eth_prototype-1.3.5 → eth_prototype-1.4.0}/tests/hardhat-project/package-lock.json +0 -0
- {eth_prototype-1.3.5 → eth_prototype-1.4.0}/tests/hardhat-project/package.json +0 -0
- {eth_prototype-1.3.5 → eth_prototype-1.4.0}/tests/hardhat-project/verifiable-binaries/@anotherOrg/aPkg/1.0.2/build/contracts/TestCurrency.sol/TestCurrency.json +0 -0
- {eth_prototype-1.3.5 → eth_prototype-1.4.0}/tests/hardhat-project/verifiable-binaries/@org/pkg/0.2.1/build/contracts/TestCurrency.json +0 -0
- {eth_prototype-1.3.5 → eth_prototype-1.4.0}/tests/hardhat-project/verifiable-binaries/@org/pkg/0.3.0/build/contracts/TestCurrency.json +0 -0
- {eth_prototype-1.3.5 → eth_prototype-1.4.0}/tests/test_aa_bundler.py +0 -0
- {eth_prototype-1.3.5 → eth_prototype-1.4.0}/tests/test_defender.py +0 -0
- {eth_prototype-1.3.5 → eth_prototype-1.4.0}/tests/test_time_control.py +0 -0
- {eth_prototype-1.3.5 → eth_prototype-1.4.0}/tests/test_w3.py +0 -0
- {eth_prototype-1.3.5 → eth_prototype-1.4.0}/tests/test_wadray.py +0 -0
@@ -2,7 +2,7 @@ exclude: "^docs/conf.py"
|
|
2
2
|
|
3
3
|
repos:
|
4
4
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
5
|
-
rev: v4.
|
5
|
+
rev: v4.4.0
|
6
6
|
hooks:
|
7
7
|
- id: trailing-whitespace
|
8
8
|
- id: check-added-large-files
|
@@ -23,19 +23,19 @@ repos:
|
|
23
23
|
- id: isort
|
24
24
|
|
25
25
|
- repo: https://github.com/psf/black
|
26
|
-
rev:
|
26
|
+
rev: 23.3.0
|
27
27
|
hooks:
|
28
28
|
- id: black
|
29
29
|
language_version: python3
|
30
30
|
|
31
31
|
- repo: https://github.com/PyCQA/flake8
|
32
|
-
rev:
|
32
|
+
rev: 6.0.0
|
33
33
|
hooks:
|
34
34
|
- id: flake8
|
35
35
|
## You can add flake8 plugins via `additional_dependencies`:
|
36
36
|
# additional_dependencies: [flake8-bugbear]
|
37
37
|
|
38
38
|
- repo: https://github.com/zricethezav/gitleaks
|
39
|
-
rev: v8.
|
39
|
+
rev: v8.16.3
|
40
40
|
hooks:
|
41
41
|
- id: gitleaks-docker
|
@@ -250,6 +250,10 @@ class ArtifactLibrary:
|
|
250
250
|
Calling with contract_ref <ContractClass>@local is the same as calling get_artifact_by_name(<ContractClass>).
|
251
251
|
"""
|
252
252
|
ref = self._find_ref(contract_ref)
|
253
|
+
if ref is None:
|
254
|
+
by_name = self.get_artifact_by_name(contract_ref)
|
255
|
+
if by_name is not None:
|
256
|
+
return by_name
|
253
257
|
|
254
258
|
if ref is None:
|
255
259
|
raise FileNotFoundError(f"Could not find artifact for {contract_ref} on {self.lookup_paths}")
|
@@ -4,6 +4,7 @@ from contextlib import contextmanager
|
|
4
4
|
from decimal import Decimal
|
5
5
|
from functools import wraps
|
6
6
|
|
7
|
+
from environs import Env
|
7
8
|
from m9g import Model
|
8
9
|
from m9g.fields import DictField, IntField, ListField, StringField, TupleField
|
9
10
|
|
@@ -13,11 +14,24 @@ __author__ = "Guillermo M. Narvaja"
|
|
13
14
|
__copyright__ = "Guillermo M. Narvaja"
|
14
15
|
__license__ = "MIT"
|
15
16
|
|
17
|
+
env = Env()
|
18
|
+
|
19
|
+
USE_CUSTOM_ERRORS = env.bool("USE_CUSTOM_ERRORS", False)
|
20
|
+
|
16
21
|
|
17
22
|
class RevertError(Exception):
|
18
23
|
pass
|
19
24
|
|
20
25
|
|
26
|
+
class RevertCustomError(RevertError):
|
27
|
+
def __init__(self, error, *args):
|
28
|
+
self.error = error
|
29
|
+
self.args = args
|
30
|
+
|
31
|
+
def __str__(self):
|
32
|
+
return f"{self.error}({', '.join(map(str, self.args))})"
|
33
|
+
|
34
|
+
|
21
35
|
class WadField(IntField):
|
22
36
|
FIELD_TYPE = Wad
|
23
37
|
|
@@ -144,9 +158,9 @@ class ROTransaction:
|
|
144
158
|
def _on_end(self):
|
145
159
|
while self.modified_contracts:
|
146
160
|
contract = self.modified_contracts.pop()
|
147
|
-
assert
|
148
|
-
contract.contract_id
|
149
|
-
|
161
|
+
assert (
|
162
|
+
contract.serialize("pydict") == self.serialized_contracts[contract.contract_id]
|
163
|
+
), f"Contract {contract.contract_id} modified in view"
|
150
164
|
del self.serialized_contracts[contract.contract_id]
|
151
165
|
|
152
166
|
def archive(self):
|
@@ -198,10 +212,11 @@ def only_role(*roles):
|
|
198
212
|
if self.has_role(role, self.running_as):
|
199
213
|
break
|
200
214
|
else:
|
201
|
-
|
215
|
+
self._error("AccessControlUnauthorizedAccount", self.running_as, role)
|
202
216
|
return method(self, *args, **kwargs)
|
203
217
|
|
204
218
|
return inner
|
219
|
+
|
205
220
|
return decorator
|
206
221
|
|
207
222
|
|
@@ -229,10 +244,14 @@ class Contract(Model):
|
|
229
244
|
def __init__(self, contract_id=None, **kwargs):
|
230
245
|
if contract_id is None:
|
231
246
|
contract_id = f"{self.__class__.__name__}-{id(self)}"
|
247
|
+
self.use_custom_errors = kwargs.pop("use_custom_errors", USE_CUSTOM_ERRORS)
|
232
248
|
super().__init__(contract_id=contract_id, **kwargs)
|
233
249
|
self._versions = []
|
234
250
|
self.manager.add_contract(self.contract_id, self)
|
235
251
|
|
252
|
+
def _error(self, error_class, *args) -> RevertError:
|
253
|
+
return RevertCustomError(error_class, *args)
|
254
|
+
|
236
255
|
@contextmanager
|
237
256
|
def as_(self, user):
|
238
257
|
"Dummy as method to do the same with the wrapper"
|
@@ -272,11 +291,7 @@ class Contract(Model):
|
|
272
291
|
|
273
292
|
class AccessControlContract(Contract):
|
274
293
|
owner = AddressField(default="owner")
|
275
|
-
roles = DictField(
|
276
|
-
StringField(),
|
277
|
-
TupleField((ListField(AddressField()), StringField())),
|
278
|
-
default={}
|
279
|
-
)
|
294
|
+
roles = DictField(StringField(), TupleField((ListField(AddressField()), StringField())), default={})
|
280
295
|
|
281
296
|
set_attr_roles = {}
|
282
297
|
|
@@ -298,6 +313,14 @@ class AccessControlContract(Contract):
|
|
298
313
|
self._running_as = self.owner
|
299
314
|
self.roles[""] = ([self.owner], "") # Add owner as default_admin
|
300
315
|
|
316
|
+
def _error(self, error_class, *args) -> RevertError:
|
317
|
+
if error_class == "AccessControlUnauthorizedAccount":
|
318
|
+
if self.use_custom_errors:
|
319
|
+
return RevertCustomError(error_class, args[0], args[1])
|
320
|
+
else:
|
321
|
+
return RevertError(f"AccessControl: account {args[0]} is missing role {args[1]}")
|
322
|
+
return super()._error(error_class, *args)
|
323
|
+
|
301
324
|
@contextmanager
|
302
325
|
def _disable_role_validation(self):
|
303
326
|
self._role_validation_disabled = True
|
@@ -320,8 +343,10 @@ class AccessControlContract(Contract):
|
|
320
343
|
members, admin_role = self.roles[role]
|
321
344
|
else:
|
322
345
|
members, admin_role = [], ""
|
323
|
-
require(
|
324
|
-
|
346
|
+
require(
|
347
|
+
self.has_role(admin_role, self._running_as),
|
348
|
+
self._error("AccessControlUnauthorizedAccount", self._running_as, admin_role),
|
349
|
+
)
|
325
350
|
|
326
351
|
if user not in members:
|
327
352
|
members.append(user)
|
@@ -337,13 +362,16 @@ class AccessControlContract(Contract):
|
|
337
362
|
if attr_name in self.set_attr_roles:
|
338
363
|
require(
|
339
364
|
self.has_role(self.set_attr_roles[attr_name], self._running_as),
|
340
|
-
|
341
|
-
|
365
|
+
self._error(
|
366
|
+
"AccessControlUnauthorizedAccount", self._running_as, self.set_attr_roles[attr_name]
|
367
|
+
),
|
342
368
|
)
|
343
369
|
|
344
370
|
|
345
371
|
def require(condition, message=None):
|
346
372
|
if not condition:
|
373
|
+
if isinstance(message, RevertError):
|
374
|
+
raise message
|
347
375
|
raise RevertError(message or "required condition not met")
|
348
376
|
|
349
377
|
|
@@ -354,14 +382,43 @@ class ERC20Token(AccessControlContract):
|
|
354
382
|
symbol = StringField(default="")
|
355
383
|
decimals = IntField(default=18)
|
356
384
|
balances = DictField(AddressField(), WadField(), default={})
|
357
|
-
allowances = DictField(
|
358
|
-
TupleField((AddressField(), AddressField())),
|
359
|
-
WadField(),
|
360
|
-
default={}
|
361
|
-
)
|
385
|
+
allowances = DictField(TupleField((AddressField(), AddressField())), WadField(), default={})
|
362
386
|
|
363
387
|
_total_supply = WadField(default=ZERO)
|
364
388
|
|
389
|
+
_arg_count_by_error = {
|
390
|
+
"ERC20InsufficientBalance": 3,
|
391
|
+
"ERC20InvalidSender": 1,
|
392
|
+
"ERC20InvalidReceiver": 1,
|
393
|
+
"ERC20InsufficientAllowance": 3,
|
394
|
+
"ERC20InvalidApprover": 1,
|
395
|
+
"ERC20InvalidSpender": 1,
|
396
|
+
}
|
397
|
+
|
398
|
+
_message_by_error = {
|
399
|
+
"ERC20InsufficientBalance": "ERC20: transfer amount exceeds balance",
|
400
|
+
"ERC20InvalidSender": "ERC20: transfer from the zero address",
|
401
|
+
"ERC20InvalidReceiver": "ERC20: transfer to the zero address",
|
402
|
+
"ERC20InsufficientAllowance": "ERC20: insufficient allowance",
|
403
|
+
"ERC20InvalidApprover": "ERC20: approve from the zero address",
|
404
|
+
"ERC20InvalidSpender": "ERC20: approve to the zero address",
|
405
|
+
}
|
406
|
+
|
407
|
+
def _error(self, error_class, *args) -> RevertError:
|
408
|
+
if self.use_custom_errors:
|
409
|
+
arg_count = self._arg_count_by_error.get(error_class, None)
|
410
|
+
if arg_count == 1:
|
411
|
+
return RevertCustomError(
|
412
|
+
error_class, args[0] if args else "0x0000000000000000000000000000000000000000"
|
413
|
+
)
|
414
|
+
elif arg_count is not None:
|
415
|
+
return RevertCustomError(error_class, *args[:arg_count])
|
416
|
+
else:
|
417
|
+
message = self._message_by_error.get(error_class, None)
|
418
|
+
if message is not None:
|
419
|
+
return RevertError(message)
|
420
|
+
return super()._error(error_class, *args)
|
421
|
+
|
365
422
|
def __init__(self, **kwargs):
|
366
423
|
if "initial_supply" in kwargs:
|
367
424
|
initial_supply = kwargs.pop("initial_supply")
|
@@ -410,7 +467,7 @@ class ERC20Token(AccessControlContract):
|
|
410
467
|
def _transfer(self, sender, recipient, amount):
|
411
468
|
sender, recipient = self._parse_accounts(sender, recipient)
|
412
469
|
if self.balance_of(sender) < amount:
|
413
|
-
raise
|
470
|
+
raise self._error("ERC20InsufficientBalance", sender, self.balance_of(sender), amount)
|
414
471
|
elif self.balances[sender] == amount:
|
415
472
|
del self.balances[sender]
|
416
473
|
else:
|
@@ -425,8 +482,8 @@ class ERC20Token(AccessControlContract):
|
|
425
482
|
|
426
483
|
def _approve(self, owner, spender, amount):
|
427
484
|
owner, spender = self._parse_accounts(owner, spender)
|
428
|
-
require(owner is not None, "
|
429
|
-
require(spender is not None, "
|
485
|
+
require(owner is not None, self._error("ERC20InvalidApprover"))
|
486
|
+
require(spender is not None, self._error("ERC20InvalidSpender", spender))
|
430
487
|
if amount == self.ZERO:
|
431
488
|
try:
|
432
489
|
del self.allowances[(owner, spender)]
|
@@ -447,7 +504,7 @@ class ERC20Token(AccessControlContract):
|
|
447
504
|
def decrease_allowance(self, sender, spender, amount):
|
448
505
|
sender, spender = self._parse_accounts(sender, spender)
|
449
506
|
allowance = self.allowances.get((sender, spender), self.ZERO)
|
450
|
-
require(allowance >= amount, "
|
507
|
+
require(allowance >= amount, self._error("ERC20InsufficientAllowance", spender, allowance, amount))
|
451
508
|
self._approve(sender, spender, allowance - amount)
|
452
509
|
|
453
510
|
@external
|
@@ -455,7 +512,7 @@ class ERC20Token(AccessControlContract):
|
|
455
512
|
spender, sender, recipient = self._parse_accounts(spender, sender, recipient)
|
456
513
|
allowance = self.allowances.get((sender, spender), self.ZERO)
|
457
514
|
if allowance < amount:
|
458
|
-
raise
|
515
|
+
raise self._error("ERC20InsufficientAllowance", spender, allowance, amount)
|
459
516
|
self._transfer(sender, recipient, amount)
|
460
517
|
self._approve(sender, spender, allowance - amount)
|
461
518
|
return True
|
@@ -464,7 +521,7 @@ class ERC20Token(AccessControlContract):
|
|
464
521
|
return self._total_supply
|
465
522
|
|
466
523
|
|
467
|
-
class ERC721Token(AccessControlContract):
|
524
|
+
class ERC721Token(AccessControlContract): # NFT
|
468
525
|
ZERO = Wad(0)
|
469
526
|
|
470
527
|
name = StringField()
|
@@ -477,13 +534,49 @@ class ERC721Token(AccessControlContract): # NFT
|
|
477
534
|
|
478
535
|
_token_count = IntField(default=0)
|
479
536
|
|
537
|
+
_arg_count_by_error = {
|
538
|
+
"ERC721InvalidOwner": 1,
|
539
|
+
"ERC721NonexistentToken": 1,
|
540
|
+
"ERC721IncorrectOwner": 3,
|
541
|
+
"ERC721InvalidSender": 1,
|
542
|
+
"ERC721InvalidReceiver": 1,
|
543
|
+
"ERC721InsufficientApproval": 2,
|
544
|
+
}
|
545
|
+
|
546
|
+
_message_by_error = {
|
547
|
+
"ERC721InvalidOwner": "ERC721: address zero is not a valid owner",
|
548
|
+
"ERC721NonexistentToken": "ERC721: invalid token ID",
|
549
|
+
"ERC721IncorrectOwner": "ERC721: transfer from incorrect owner",
|
550
|
+
"ERC721InvalidSender": "ERC721: transfer from incorrect owner",
|
551
|
+
"ERC721InvalidReceiver": "ERC721: transfer to the zero address",
|
552
|
+
"ERC721InsufficientApproval": "ERC721: caller is not token owner nor approved",
|
553
|
+
}
|
554
|
+
|
555
|
+
def _error(self, error_class, *args) -> RevertError:
|
556
|
+
if self.use_custom_errors:
|
557
|
+
arg_count = self._arg_count_by_error.get(error_class, None)
|
558
|
+
if arg_count == 1:
|
559
|
+
return RevertCustomError(
|
560
|
+
error_class, args[0] if args else "0x0000000000000000000000000000000000000000"
|
561
|
+
)
|
562
|
+
elif arg_count is not None:
|
563
|
+
return RevertCustomError(error_class, *args[:arg_count])
|
564
|
+
else:
|
565
|
+
message = self._message_by_error.get(error_class, None)
|
566
|
+
if message is not None:
|
567
|
+
return RevertError(message)
|
568
|
+
return super()._error(error_class, *args)
|
569
|
+
|
480
570
|
@external
|
481
571
|
def mint(self, to, token_id):
|
482
572
|
if token_id is None:
|
483
573
|
self._token_count += 1
|
484
574
|
token_id = self._token_count
|
485
575
|
if token_id in self.owners:
|
486
|
-
|
576
|
+
if self.use_custom_errors:
|
577
|
+
raise RevertError("ERC721: token already minted")
|
578
|
+
else:
|
579
|
+
raise self._error("ERC721InvalidSender")
|
487
580
|
self.balances[to] = self.balances.get(to, 0) + 1
|
488
581
|
self.owners[token_id] = to
|
489
582
|
|
@@ -503,7 +596,7 @@ class ERC721Token(AccessControlContract): # NFT
|
|
503
596
|
@view
|
504
597
|
def owner_of(self, token_id):
|
505
598
|
if token_id not in self.owners:
|
506
|
-
raise
|
599
|
+
raise self._error("ERC721NonexistentToken", token_id)
|
507
600
|
return self.owners[token_id]
|
508
601
|
|
509
602
|
# def token_uri
|
@@ -536,23 +629,29 @@ class ERC721Token(AccessControlContract): # NFT
|
|
536
629
|
@external
|
537
630
|
def transfer_from(self, sender, from_, to, token_id):
|
538
631
|
owner = self.owners[token_id]
|
539
|
-
if
|
540
|
-
|
541
|
-
|
632
|
+
if (
|
633
|
+
sender != owner
|
634
|
+
and self.token_approvals.get(token_id, None) != sender
|
635
|
+
and sender not in self.operator_approvals.get(owner, [])
|
636
|
+
):
|
637
|
+
raise self._error("ERC721InsufficientApproval", sender, token_id)
|
542
638
|
return self._transfer(from_, to, token_id)
|
543
639
|
|
544
640
|
@external
|
545
641
|
def safe_transfer_from(self, sender, from_, to, token_id):
|
546
642
|
owner = self.owners[token_id]
|
547
|
-
if
|
548
|
-
|
549
|
-
|
643
|
+
if (
|
644
|
+
sender != owner
|
645
|
+
and self.token_approvals.get(token_id, None) != sender
|
646
|
+
and sender not in self.operator_approvals.get(owner, [])
|
647
|
+
):
|
648
|
+
raise self._error("ERC721InsufficientApproval", sender, token_id)
|
550
649
|
# TODO: if `to` is contract, call onERC721Received
|
551
650
|
return self._transfer(from_, to, token_id)
|
552
651
|
|
553
652
|
def _transfer(self, from_, to, token_id):
|
554
653
|
if self.owners[token_id] != from_:
|
555
|
-
raise
|
654
|
+
raise self._error("ERC721InvalidOwner", from_)
|
556
655
|
if token_id in self.token_approvals:
|
557
656
|
del self.token_approvals[token_id]
|
558
657
|
self.balances[from_] -= 1
|
@@ -125,6 +125,14 @@ def test_artifact_libraries_generator():
|
|
125
125
|
assert libraries == []
|
126
126
|
|
127
127
|
|
128
|
+
def test_artifact_library_ref_lookup_fallback():
|
129
|
+
library = ArtifactLibrary(
|
130
|
+
os.path.join(HARDHAT_PROJECT, "artifacts", "contracts", "Counter.sol"),
|
131
|
+
)
|
132
|
+
|
133
|
+
assert library.get_artifact_by_ref("Counter") == library.get_artifact_by_name("Counter")
|
134
|
+
|
135
|
+
|
128
136
|
def test_artifact_library_ref_lookup():
|
129
137
|
library = ArtifactLibrary(
|
130
138
|
os.path.join(HARDHAT_PROJECT, "artifacts"),
|
@@ -140,6 +140,8 @@ def _connected_contract_address(eth_wrapper_class, *args, **kwargs):
|
|
140
140
|
|
141
141
|
|
142
142
|
ERC20TokenAlternatives = [ERC20Token]
|
143
|
+
ERC20TokenAlternatives.append(partial(ERC20Token, use_custom_errors=False))
|
144
|
+
ERC20TokenAlternatives.append(partial(ERC20Token, use_custom_errors=True))
|
143
145
|
|
144
146
|
if "web3py" in TEST_ENV:
|
145
147
|
ERC20TokenAlternatives.append(partial(TestCurrency, provider_key="w3"))
|
@@ -215,8 +217,13 @@ class TestERC20Token:
|
|
215
217
|
token.transfer_from("Spender", "owner", "Giacomo", _W(300))
|
216
218
|
assert token.allowance("owner", "Spender") == _W(0)
|
217
219
|
|
218
|
-
with pytest.raises(RevertError):
|
219
|
-
|
220
|
+
with pytest.raises(RevertError, match="allowance|ERC20InsufficientAllowance"):
|
221
|
+
try:
|
222
|
+
token.transfer_from("Spender", "owner", "Luca", _W(1))
|
223
|
+
except RevertError as err:
|
224
|
+
if getattr(token, "use_custom_errors", False):
|
225
|
+
assert str(err).startswith("ERC20InsufficientAllowance(")
|
226
|
+
raise
|
220
227
|
|
221
228
|
assert token.balance_of("Guillo") == _W(200)
|
222
229
|
assert token.balance_of("owner") == _W(1500)
|
@@ -225,6 +232,8 @@ class TestERC20Token:
|
|
225
232
|
|
226
233
|
|
227
234
|
ERC721TokenAlternatives = [ERC721Token]
|
235
|
+
ERC721TokenAlternatives.append(partial(ERC721Token, use_custom_errors=False))
|
236
|
+
ERC721TokenAlternatives.append(partial(ERC721Token, use_custom_errors=True))
|
228
237
|
if "web3py" in TEST_ENV:
|
229
238
|
ERC721TokenAlternatives.append(partial(TestNFT, provider_key="w3"))
|
230
239
|
|
@@ -242,7 +251,7 @@ class TestERC721Token:
|
|
242
251
|
assert nft.owner_of(1235) == "CUST1"
|
243
252
|
nft.burn("CUST1", 1235)
|
244
253
|
assert nft.balance_of("CUST1") == 1
|
245
|
-
with pytest.raises(RevertError, match="ERC721: invalid token ID"):
|
254
|
+
with pytest.raises(RevertError, match="ERC721: invalid token ID|ERC721NonexistentToken"):
|
246
255
|
nft.owner_of(1235)
|
247
256
|
nft.burn("CUST1", 1234)
|
248
257
|
assert nft.balance_of("CUST1") == 0
|
@@ -297,5 +306,12 @@ class TestERC721Token:
|
|
297
306
|
assert nft.balance_of("CUST2") == 2
|
298
307
|
nft.set_approval_for_all("CUST1", "SPEND", False)
|
299
308
|
|
300
|
-
with pytest.raises(
|
309
|
+
with pytest.raises(
|
310
|
+
RevertError,
|
311
|
+
match=(
|
312
|
+
"ERC721InsufficientApproval"
|
313
|
+
if getattr(nft, "use_custom_errors", False)
|
314
|
+
else "ERC721: caller is not token owner"
|
315
|
+
),
|
316
|
+
):
|
301
317
|
nft.transfer_from("SPEND", "CUST1", "CUST2", 1235)
|
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
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{eth_prototype-1.3.5 → eth_prototype-1.4.0}/tests/hardhat-project/contracts/CounterUpgradeable.sol
RENAMED
File without changes
|
File without changes
|
{eth_prototype-1.3.5 → eth_prototype-1.4.0}/tests/hardhat-project/contracts/CounterWithLibrary.sol
RENAMED
File without changes
|
File without changes
|
{eth_prototype-1.3.5 → eth_prototype-1.4.0}/tests/hardhat-project/contracts/EventLauncher.sol
RENAMED
File without changes
|
{eth_prototype-1.3.5 → eth_prototype-1.4.0}/tests/hardhat-project/contracts/TestCurrency.sol
RENAMED
File without changes
|
{eth_prototype-1.3.5 → eth_prototype-1.4.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
|
File without changes
|
File without changes
|