Skip to content

Commit dd4c369

Browse files
committed
Update disabling strict byte check dynamics and add docs
- Make disabling / enabling strict bytes checking a flag that can be toggled on and off. - Update documentation based on these recent changes and fix tests.
1 parent 184796a commit dd4c369

File tree

10 files changed

+133
-151
lines changed

10 files changed

+133
-151
lines changed

conftest.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ def w3():
9191
@pytest.fixture(scope="module")
9292
def w3_non_strict_abi():
9393
w3 = Web3(EthereumTesterProvider())
94-
w3.disable_strict_bytes_type_checking()
94+
w3.strict_bytes_type_checking = False
9595
return w3
9696

9797

docs/abi_types.rst

+8-13
Original file line numberDiff line numberDiff line change
@@ -25,22 +25,17 @@ All addresses must be supplied in one of three ways:
2525
<https://github.com/ethereum/EIPs/blob/master/EIPS/eip-55.md>`_ spec.
2626
* A 20-byte binary address.
2727

28-
Strict Bytes Type Checking
29-
--------------------------
28+
Disabling Strict Bytes Type Checking
29+
------------------------------------
3030

31-
.. note ::
32-
33-
In version 6, this will be the default behavior
34-
35-
There is a method on web3 that will enable stricter bytes type checking.
36-
The default is to allow Python strings, and to allow bytestrings less
37-
than the specified byte size. To enable stricter checks, use
38-
``w3.enable_strict_bytes_type_checking()``. This method will cause the web3
31+
There is a method on web3 that will disable strict bytes type checking.
32+
This allows bytes values of Python strings and allows bytestrings less
33+
than the specified byte size. To disable stricter checks, set the
34+
``w3.strict_bytes_type_checking`` flag to ``False``. This will no longer cause the web3
3935
instance to raise an error if a Python string is passed in without a "0x"
40-
prefix. It will also raise an error if the byte string or hex string is not
36+
prefix. It will also render valid byte strings or hex strings that are below
4137
the exact number of bytes specified by the ABI type. See the
42-
:ref:`enable-strict-byte-check` section
43-
for an example and more details.
38+
:ref:`disable-strict-byte-check` section for an example and more details.
4439

4540
Types by Example
4641
----------------

docs/web3.contract.rst

+78-118
Original file line numberDiff line numberDiff line change
@@ -444,100 +444,76 @@ and the arguments are ambiguous.
444444
1
445445
446446
447-
.. _enable-strict-byte-check:
447+
.. _disable-strict-byte-check:
448448

449-
Enabling Strict Checks for Bytes Types
450-
--------------------------------------
449+
Disabling Strict Checks for Bytes Types
450+
---------------------------------------
451451

452-
By default, web3 is not very strict when it comes to hex and bytes values.
453-
A bytes type will take a hex string, a bytestring, or a regular python
454-
string that can be decoded as a hex.
455-
Additionally, if an abi specifies a byte size, but the value that gets
456-
passed in is less than the specified size, web3 will automatically pad the value.
457-
For example, if an abi specifies a type of ``bytes4``, web3 will handle all of the following values:
452+
By default, web3 is strict when it comes to hex and bytes values, as of ``v6``.
453+
If an abi specifies a byte size, but the value that gets passed in is not the specified
454+
size, web3 will invalidate the value. For example, if an abi specifies a type of
455+
``bytes4``, web3 will invalidate the following values:
458456

459-
.. list-table:: Valid byte and hex strings for a bytes4 type
460-
:widths: 25 75
461-
:header-rows: 1
462-
463-
* - Input
464-
- Normalizes to
465-
* - ``''``
466-
- ``b'\x00\x00\x00\x00'``
467-
* - ``'0x'``
468-
- ``b'\x00\x00\x00\x00'``
469-
* - ``b''``
470-
- ``b'\x00\x00\x00\x00'``
471-
* - ``b'ab'``
472-
- ``b'ab\x00\x00'``
473-
* - ``'0xab'``
474-
- ``b'\xab\x00\x00\x00'``
475-
* - ``'1234'``
476-
- ``b'\x124\x00\x00'``
477-
* - ``'0x61626364'``
478-
- ``b'abcd'``
479-
* - ``'1234'``
480-
- ``b'1234'``
481-
482-
483-
The following values will raise an error by default:
484-
485-
.. list-table:: Invalid byte and hex strings for a bytes4 type
457+
.. list-table:: Invalid byte and hex strings with strict (default) bytes4 type checking
486458
:widths: 25 75
487459
:header-rows: 1
488460

489461
* - Input
490462
- Reason
491-
* - ``b'abcde'``
492-
- Bytestring with more than 4 bytes
493-
* - ``'0x6162636423'``
494-
- Hex string with more than 4 bytes
463+
* - ``''``
464+
- Needs to be prefixed with a "0x" to be interpreted as an empty hex string
495465
* - ``2``
496466
- Wrong type
497467
* - ``'ah'``
498468
- String is not valid hex
469+
* - ``'1234'``
470+
- Needs to either be a bytestring (b'1234') or be a hex value of the right size, prefixed with 0x (in this case: '0x31323334')
471+
* - ``b''``
472+
- Needs to have exactly 4 bytes
473+
* - ``b'ab'``
474+
- Needs to have exactly 4 bytes
475+
* - ``'0xab'``
476+
- Needs to have exactly 4 bytes
477+
* - ``'0x6162636464'``
478+
- Needs to have exactly 4 bytes
479+
499480

500-
However, you may want to be stricter with acceptable values for bytes types.
501-
For this you can use the :meth:`w3.enable_strict_bytes_type_checking()` method,
502-
which is available on the web3 instance. A web3 instance which has had this method
503-
invoked will enforce a stricter set of rules on which values are accepted.
481+
However, you may want to be less strict with acceptable values for bytes types.
482+
This may prove useful if you trust that values coming through are what they are
483+
meant to be with respects to the respective ABI. In this case, the automatic padding
484+
might be convenient for inferred types. For this, you can set the
485+
:meth:`w3.strict_bytes_type_checking` flag to ``False``, which is available on the
486+
Web3 instance. A Web3 instance which has this flag set to ``False`` will have a less
487+
strict set of rules on which values are accepted. A ``bytes`` type will allow values as
488+
a hex string, a bytestring, or a regular Python string that can be decoded as a hex.
489+
0x-prefixed hex strings are also not required.
504490

505-
- A Python string that is not prefixed with ``0x`` will throw an error.
506-
- A bytestring whose length is not exactly the specified byte size
507-
will raise an error.
491+
- A Python string that is not prefixed with ``0x`` is valid.
492+
- A bytestring whose length is less than the specified byte size is valid.
508493

509-
.. list-table:: Valid byte and hex strings for a strict bytes4 type
494+
.. list-table:: Valid byte and hex strings for a non-strict bytes4 type
510495
:widths: 25 75
511496
:header-rows: 1
512497

513498
* - Input
514499
- Normalizes to
500+
* - ``''``
501+
- ``b'\x00\x00\x00\x00'``
515502
* - ``'0x'``
516503
- ``b'\x00\x00\x00\x00'``
504+
* - ``b''``
505+
- ``b'\x00\x00\x00\x00'``
506+
* - ``b'ab'``
507+
- ``b'ab\x00\x00'``
508+
* - ``'0xab'``
509+
- ``b'\xab\x00\x00\x00'``
510+
* - ``'1234'``
511+
- ``b'\x124\x00\x00'``
517512
* - ``'0x61626364'``
518513
- ``b'abcd'``
519514
* - ``'1234'``
520515
- ``b'1234'``
521516

522-
.. list-table:: Invalid byte and hex strings with strict bytes4 type checking
523-
:widths: 25 75
524-
:header-rows: 1
525-
526-
* - Input
527-
- Reason
528-
* - ``''``
529-
- Needs to be prefixed with a "0x" to be interpreted as an empty hex string
530-
* - ``'1234'``
531-
- Needs to either be a bytestring (b'1234') or be a hex value of the right size, prefixed with 0x (in this case: '0x31323334')
532-
* - ``b''``
533-
- Needs to have exactly 4 bytes
534-
* - ``b'ab'``
535-
- Needs to have exactly 4 bytes
536-
* - ``'0xab'``
537-
- Needs to have exactly 4 bytes
538-
* - ``'0x6162636464'``
539-
- Needs to have exactly 4 bytes
540-
541517

542518
Taking the following contract code as an example:
543519

@@ -636,69 +612,53 @@ Taking the following contract code as an example:
636612

637613
>>> ArraysContract = w3.eth.contract(abi=abi, bytecode=bytecode)
638614

639-
>>> tx_hash = ArraysContract.constructor([b'b']).transact()
640-
>>> tx_receipt = w3.eth.wait_for_transaction_receipt(tx_hash)
641-
642-
>>> array_contract = w3.eth.contract(
643-
... address=tx_receipt.contractAddress,
644-
... abi=abi
645-
... )
646-
647-
>>> array_contract.functions.getBytes2Value().call()
648-
[b'b\x00']
649-
>>> array_contract.functions.setBytes2Value([b'a']).transact({'gas': 420000, 'gasPrice': Web3.to_wei(1, 'gwei')})
650-
HexBytes('0xc5377ba25224bd763ceedc0ee455cc14fc57b23dbc6b6409f40a557a009ff5f4')
651-
>>> array_contract.functions.getBytes2Value().call()
652-
[b'a\x00']
653-
>>> w3.disable_strict_bytes_type_checking()
654-
>>> array_contract.functions.setBytes2Value([b'a']).transact()
655-
Traceback (most recent call last):
656-
...
657-
ValidationError:
658-
Could not identify the intended function with name
659-
660-
>>> tx_hash = ArraysContract.constructor([b'b']).transact()
661-
>>> tx_receipt = w3.eth.wait_for_transaction_receipt(tx_hash)
662-
663-
>>> array_contract = w3.eth.contract(
664-
... address=tx_receipt.contractAddress,
665-
... abi=abi
666-
... )
667-
668-
>>> array_contract.functions.getBytes2Value().call()
669-
[b'b\x00']
670-
>>> array_contract.functions.setBytes2Value([b'a']).transact({'gas': 420000, 'gasPrice': Web3.to_wei(1, 'gwei')})
671-
HexBytes('0xc5377ba25224bd763ceedc0ee455cc14fc57b23dbc6b6409f40a557a009ff5f4')
672-
>>> array_contract.functions.getBytes2Value().call()
673-
[b'a\x00']
674-
>>> w3.disable_strict_bytes_type_checking()
675-
>>> array_contract.functions.setBytes2Value([b'a']).transact()
676-
Traceback (most recent call last):
677-
...
678-
ValidationError:
679-
Could not identify the intended function with name
615+
>>> tx_hash = ArraysContract.constructor([b'bb']).transact()
616+
>>> tx_receipt = w3.eth.wait_for_transaction_receipt(tx_hash)
617+
>>> array_contract = w3.eth.contract(
618+
... address=tx_receipt.contractAddress,
619+
... abi=abi
620+
... )
621+
>>> array_contract.functions.getBytes2Value().call()
622+
[b'bb']
623+
624+
>>> # set value with appropriate byte size
625+
>>> array_contract.functions.setBytes2Value([b'aa']).transact({'gas': 420000, "maxPriorityFeePerGas": 10 ** 9, "maxFeePerGas": 10 ** 9})
626+
HexBytes('0xcb95151142ea56dbf2753d70388aef202a7bb5a1e323d448bc19f1d2e1fe3dc9')
627+
>>> # check value
628+
>>> array_contract.functions.getBytes2Value().call()
629+
[b'aa']
630+
631+
>>> # trying to set value without appropriate size (bytes2) is not valid
632+
>>> array_contract.functions.setBytes2Value([b'b']).transact()
633+
Traceback (most recent call last):
634+
...
635+
web3.exceptions.Web3ValidationError:
636+
Could not identify the intended function with name
637+
>>> # check value is still b'aa'
638+
>>> array_contract.functions.getBytes2Value().call()
639+
[b'aa']
640+
641+
>>> # disabling strict byte checking...
642+
>>> w3.strict_bytes_type_checking = False
680643

681644
>>> tx_hash = ArraysContract.constructor([b'b']).transact()
682645
>>> tx_receipt = w3.eth.wait_for_transaction_receipt(tx_hash)
683-
684646
>>> array_contract = w3.eth.contract(
685647
... address=tx_receipt.contractAddress,
686648
... abi=abi
687649
... )
688-
650+
>>> # check value is zero-padded... i.e. b'b\x00'
689651
>>> array_contract.functions.getBytes2Value().call()
690652
[b'b\x00']
691-
>>> array_contract.functions.setBytes2Value([b'a']).transact({'gas': 420000, 'gasPrice': Web3.to_wei(1, 'gwei')})
692-
HexBytes('0xc5377ba25224bd763ceedc0ee455cc14fc57b23dbc6b6409f40a557a009ff5f4')
693-
>>> array_contract.functions.getBytes2Value().call()
694-
[b'a\x00']
695-
>>> w3.enable_strict_bytes_type_checking()
653+
654+
>>> # set the flag back to True
655+
>>> w3.strict_bytes_type_checking = True
656+
696657
>>> array_contract.functions.setBytes2Value([b'a']).transact()
697658
Traceback (most recent call last):
698659
...
699-
Web3ValidationError:
700-
Could not identify the intended function with name `setBytes2Value`
701-
660+
web3.exceptions.Web3ValidationError:
661+
Could not identify the intended function with name
702662

703663
.. _contract-functions:
704664

docs/web3.main.rst

+23-12
Original file line numberDiff line numberDiff line change
@@ -316,36 +316,47 @@ Check Encodability
316316
>>> from web3.auto.gethdev import w3
317317
>>> w3.is_encodable('bytes2', b'12')
318318
True
319-
>>> w3.is_encodable('bytes2', b'1')
320-
True
321319
>>> w3.is_encodable('bytes2', '0x1234')
322320
True
323-
>>> w3.is_encodable('bytes2', b'123')
321+
>>> w3.is_encodable('bytes2', '1234') # not 0x-prefixed, no assumptions will be made
322+
False
323+
>>> w3.is_encodable('bytes2', b'1') # does not match specified bytes size
324+
False
325+
>>> w3.is_encodable('bytes2', b'123') # does not match specified bytes size
324326
False
325327
326-
.. py:method:: w3.enable_strict_bytes_type_checking()
328+
.. py:attribute:: w3.strict_bytes_type_checking
327329
328-
Enables stricter bytes type checking. For more examples see :ref:`enable-strict-byte-check`
330+
Disable the stricter bytes type checking that is loaded by default. For more
331+
examples, see :ref:`disable-strict-byte-check`
329332

330333
.. doctest::
331334

332335
>>> from web3.auto.gethdev import w3
333-
>>> w3.disable_strict_bytes_type_checking()
334-
>>> w3.is_encodable('bytes2', b'12')
335-
True
336-
>>> w3.is_encodable('bytes2', b'1')
337-
False
338-
>>> w3.enable_strict_bytes_type_checking()
336+
339337
>>> w3.is_encodable('bytes2', b'12')
340338
True
339+
340+
>>> # not of exact size bytes2
341+
>>> w3.is_encodable('bytes2', b'1')
342+
False
343+
344+
>>> w3.strict_bytes_type_checking = False
345+
346+
>>> # zero-padded, so encoded to: b'1\x00'
347+
>>> w3.is_encodable('bytes2', b'1')
348+
True
349+
350+
>>> # re-enable it
351+
>>> w3.strict_bytes_type_checking = True
341352
>>> w3.is_encodable('bytes2', b'1')
342353
False
343354

344355

345356
RPC API Modules
346357
~~~~~~~~~~~~~~~
347358

348-
Each ``Web3`` instance also exposes these namespaced API modules.
359+
Each ``Web3`` instance also exposes these name-spaced API modules.
349360

350361

351362
.. py:attribute:: Web3.eth

newsfragments/2788.breaking.rst

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Strict bytes type checking is now default for ``web3.py``. This change also adds a boolean flag for turning this feature on and off.

tests/core/conftest.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,6 @@ async def async_w3():
129129
async def async_w3_non_strict_abi():
130130
provider = AsyncEthereumTesterProvider()
131131
w3 = Web3(provider, modules={"eth": [AsyncEth]}, middlewares=provider.middlewares)
132-
w3.disable_strict_bytes_type_checking()
132+
w3.strict_bytes_type_checking = False
133133
w3.eth.default_account = await w3.eth.coinbase
134134
return w3

tests/core/utilities/test_abi_is_encodable.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,6 @@ def test_is_encodable(w3, value, _type, expected):
9797
),
9898
)
9999
def test_is_encodable_non_strict(w3, value, _type, expected):
100-
w3.disable_strict_bytes_type_checking()
100+
w3.strict_bytes_type_checking = False
101101
actual = w3.is_encodable(_type, value)
102102
assert actual is expected
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
def test_strict_bytes_type_checking_turns_on_and_off(w3):
2+
# TODO: write this test
3+
pass

web3/_utils/abi.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -823,7 +823,7 @@ def strip_abi_type(elements: Any) -> Any:
823823
return elements
824824

825825

826-
def build_default_registry() -> ABIRegistry:
826+
def build_non_strict_registry() -> ABIRegistry:
827827
# We make a copy here just to make sure that eth-abi's default registry is not
828828
# affected by our custom encoder subclasses
829829
registry = default_registry.copy()

0 commit comments

Comments
 (0)