Repository: ethereum/eth-abi

Issue:
Decoding arbitrary length bytes fails due to expected padding
 _If this is a bug report, please fill in the following sections.
If this is a feature request, delete and describe what you would like with examples._

## What was wrong?

The code is unable to decode several valid transaction calls because of a mismatch between length of the input stream being parsed and the expected padded_length in the ByteStringDecoder class.

[Here's](https://etherscan.io/vmtrace?txhash=0x614cab80a0f01f967fdb67f6dbb4be6fb0eb765d37aa7607735bdd4995f94385&type=parity#decoded) the trace I'm using as an example on Etherscan. It is the one listed as `Action[2]`. There are multiple other examples that I can provide if needed.

### Code that produced the error

```py
tx_input = '0x022c0d9f00000000000000000000000000000000000000000000003050f28c10d7d9b40c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b5c7ad3cb6506c65da01f2fac2e667dcb9e66e9c000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c904853d955acef822db058eb8505911ed77f175b99e1531c1a63a169ac75a2daae399080745fa51de4400000000000000000000000000000000000000000000003050f28c10d7d9b40c7bc2c873190bbaddefe646c35f1ae6cffbfb402059bd6774c22486d9f4fab2d448dce4f892a9ae250d0ab87046fbb341d058f17cbc4c1133f25a20a52f000db63cac384247597756545b500253ff8e607a8020010b00020f0100000e030c01990966d504030200000569b81152c5a8d35a67b32a4d3772795d96cae4da010106'
eth_abi.decode(['uint256', 'uint256', 'address', 'bytes'], bytes.fromhex(tx_input[10:]))
```

### Full error output 

```sh
---------------------------------------------------------------------------
InsufficientDataBytes                     Traceback (most recent call last)
Cell In[99], line 1
----> 1 eth_abi.decode(['uint256', 'uint256', 'address', 'bytes'], bytes.fromhex(tx_input[10:]))

File ~/miniconda3/envs/abi/lib/python3.8/site-packages/eth_abi/codec.py:210, in ABIDecoder.decode(self, types, data)
    207 decoder = TupleDecoder(decoders=decoders)
    208 stream = self.stream_class(data)
--> 210 return decoder(stream)

File ~/miniconda3/envs/abi/lib/python3.8/site-packages/eth_abi/decoding.py:127, in BaseDecoder.__call__(self, stream)
    126 def __call__(self, stream: ContextFramesBytesIO) -> Any:
--> 127     return self.decode(stream)

File ~/miniconda3/envs/abi/lib/python3.8/site-packages/eth_utils/functional.py:45, in apply_to_return_value.<locals>.outer.<locals>.inner(*args, **kwargs)
     43 @functools.wraps(fn)
     44 def inner(*args, **kwargs) -> T:  # type: ignore
---> 45     return callback(fn(*args, **kwargs))

File ~/miniconda3/envs/abi/lib/python3.8/site-packages/eth_abi/decoding.py:173, in TupleDecoder.decode(self, stream)
    170 @to_tuple
    171 def decode(self, stream):
    172     for decoder in self.decoders:
--> 173         yield decoder(stream)

File ~/miniconda3/envs/abi/lib/python3.8/site-packages/eth_abi/decoding.py:127, in BaseDecoder.__call__(self, stream)
    126 def __call__(self, stream: ContextFramesBytesIO) -> Any:
--> 127     return self.decode(stream)

File ~/miniconda3/envs/abi/lib/python3.8/site-packages/eth_abi/decoding.py:145, in HeadTailDecoder.decode(self, stream)
    142 start_pos = decode_uint_256(stream)
    144 stream.push_frame(start_pos)
--> 145 value = self.tail_decoder(stream)
    146 stream.pop_frame()
    148 return value

File ~/miniconda3/envs/abi/lib/python3.8/site-packages/eth_abi/decoding.py:127, in BaseDecoder.__call__(self, stream)
    126 def __call__(self, stream: ContextFramesBytesIO) -> Any:
--> 127     return self.decode(stream)

File ~/miniconda3/envs/abi/lib/python3.8/site-packages/eth_abi/decoding.py:198, in SingleDecoder.decode(self, stream)
    197 def decode(self, stream):
--> 198     raw_data = self.read_data_from_stream(stream)
    199     data, padding_bytes = self.split_data_and_padding(raw_data)
    200     value = self.decoder_fn(data)

File ~/miniconda3/envs/abi/lib/python3.8/site-packages/eth_abi/decoding.py:519, in ByteStringDecoder.read_data_from_stream(stream)
    516 data = stream.read(padded_length)
    518 if len(data) < padded_length:
--> 519     raise InsufficientDataBytes(
    520         "Tried to read {0} bytes.  Only got {1} bytes".format(
    521             padded_length,
    522             len(data),
    523         )
    524     )
    526 padding_bytes = data[data_length:]
    528 if padding_bytes != b'\x00' * (padded_length - data_length):

InsufficientDataBytes: Tried to read 224 bytes.  Only got 201 bytes
```

### Expected Result

I've tested decoding this data using `ethersjs` as well as on Etherscan and both pass without issue. Here's the decoded data from `ethersjs`.

```
[{"_hex":"0x3050f28c10d7d9b40c"},{"_hex":"0x00"},"0xB5c7Ad3cB6506C65DA01F2fAC2e667DcB9e66e9c","0x04853d955acef822db058eb8505911ed77f175b99e1531c1a63a169ac75a2daae399080745fa51de4400000000000000000000000000000000000000000000003050f28c10d7d9b40c7bc2c873190bbaddefe646c35f1ae6cffbfb402059bd6774c22486d9f4fab2d448dce4f892a9ae250d0ab87046fbb341d058f17cbc4c1133f25a20a52f000db63cac384247597756545b500253ff8e607a8020010b00020f0100000e030c01990966d504030200000569b81152c5a8d35a67b32a4d3772795d96cae4da010106"]
```

This is the function signature, btw:
`"swap(uint256,uint256,address,bytes)"`

### Environment

```sh
# run this:
$ python -m eth_utils

Python version:
3.8.15 | packaged by conda-forge | (default, Nov 22 2022, 08:49:06)
[Clang 14.0.6 ]

Operating System: macOS-12.6-arm64-arm-64bit

pip freeze result:
aiohttp==3.8.3
aiosignal==1.3.1
anyio @ file:///home/conda/feedstock_root/build_artifacts/anyio_1666191106763/work/dist
appnope @ file:///home/conda/feedstock_root/build_artifacts/appnope_1649077682618/work
argon2-cffi @ file:///home/conda/feedstock_root/build_artifacts/argon2-cffi_1640817743617/work
argon2-cffi-bindings @ file:///Users/runner/miniforge3/conda-bld/argon2-cffi-bindings_1666850912616/work
asttokens @ file:///home/conda/feedstock_root/build_artifacts/asttokens_1670263926556/work
async-timeout==4.0.2
attrs @ file:///home/conda/feedstock_root/build_artifacts/attrs_1659291887007/work
Babel @ file:///home/conda/feedstock_root/build_artifacts/babel_1667688356751/work
backcall @ file:///home/conda/feedstock_root/build_artifacts/backcall_1592338393461/work
backports.functools-lru-cache @ file:///home/conda/feedstock_root/build_artifacts/backports.functools_lru_cache_1618230623929/work
base58==2.1.1
beautifulsoup4 @ file:///home/conda/feedstock_root/build_artifacts/beautifulsoup4_1649463573192/work
bitarray==2.6.0
black @ file:///Users/runner/miniforge3/conda-bld/black-recipe_1666900158227/work
bleach @ file:///home/conda/feedstock_root/build_artifacts/bleach_1656355450470/work
brotlipy @ file:///Users/runner/miniforge3/conda-bld/brotlipy_1666764727380/work
certifi==2022.9.24
cffi @ file:///Users/runner/miniforge3/conda-bld/cffi_1666754795865/work
charset-normalizer @ file:///home/conda/feedstock_root/build_artifacts/charset-normalizer_1661170624537/work
click @ file:///home/conda/feedstock_root/build_artifacts/click_1666798198223/work
colorama @ file:///home/conda/feedstock_root/build_artifacts/colorama_1666700638685/work
cryptography @ file:///Users/runner/miniforge3/conda-bld/cryptography_1669593229961/work
cytoolz==0.12.0
debugpy @ file:///Users/runner/miniforge3/conda-bld/debugpy_1669709939034/work
decorator @ file:///home/conda/feedstock_root/build_artifacts/decorator_1641555617451/work
defusedxml @ file:///home/conda/feedstock_root/build_artifacts/defusedxml_1615232257335/work
entrypoints @ file:///home/conda/feedstock_root/build_artifacts/entrypoints_1643888246732/work
eth-abi==2.2.0
eth-account==0.5.9
eth-hash==0.5.1
eth-keyfile==0.5.1
eth-keys==0.3.4
eth-rlp==0.2.1
eth-typing==2.3.0
eth-utils==1.9.5
evm-decoder @ file:///Users/rohit/code/trm-blockchain-etl/evm_decoder
exceptiongroup @ file:///home/conda/feedstock_root/build_artifacts/exceptiongroup_1668523704481/work
executing @ file:///home/conda/feedstock_root/build_artifacts/executing_1667317341051/work
fastjsonschema @ file:///home/conda/feedstock_root/build_artifacts/python-fastjsonschema_1663619548554/work/dist
flit_core @ file:///home/conda/feedstock_root/build_artifacts/flit-core_1667734568827/work/source/flit_core
frozenlist==1.3.3
hexbytes==0.3.0
idna @ file:///home/conda/feedstock_root/build_artifacts/idna_1663625384323/work
importlib-metadata @ file:///home/conda/feedstock_root/build_artifacts/importlib-metadata_1669312071043/work
importlib-resources @ file:///home/conda/feedstock_root/build_artifacts/importlib_resources_1670346715028/work
iniconfig @ file:///home/conda/feedstock_root/build_artifacts/iniconfig_1603384189793/work
ipfshttpclient==0.8.0a2
ipykernel @ file:///Users/runner/miniforge3/conda-bld/ipykernel_1668027175059/work
ipython @ file:///Users/runner/miniforge3/conda-bld/ipython_1669904367927/work
ipython-genutils==0.2.0
jedi @ file:///home/conda/feedstock_root/build_artifacts/jedi_1669134318875/work
Jinja2 @ file:///home/conda/feedstock_root/build_artifacts/jinja2_1654302431367/work
json5 @ file:///home/conda/feedstock_root/build_artifacts/json5_1600692310011/work
jsonschema @ file:///home/conda/feedstock_root/build_artifacts/jsonschema-meta_1669810440410/work
jupyter-server @ file:///home/conda/feedstock_root/build_artifacts/jupyter_server_1669064535452/work
jupyter_client @ file:///home/conda/feedstock_root/build_artifacts/jupyter_client_1670253809910/work
jupyter_core @ file:///Users/runner/miniforge3/conda-bld/jupyter_core_1669776266759/work
jupyterlab @ file:///home/conda/feedstock_root/build_artifacts/jupyterlab_1670246025605/work
jupyterlab-pygments @ file:///home/conda/feedstock_root/build_artifacts/jupyterlab_pygments_1649936611996/work
jupyterlab_server @ file:///home/conda/feedstock_root/build_artifacts/jupyterlab_server_1668171221202/work
lru-dict==1.1.8
MarkupSafe @ file:///Users/runner/miniforge3/conda-bld/markupsafe_1666770321497/work
matplotlib-inline @ file:///home/conda/feedstock_root/build_artifacts/matplotlib-inline_1660814786464/work
mistune @ file:///home/conda/feedstock_root/build_artifacts/mistune_1657892024508/work
multiaddr==0.0.9
multidict==6.0.3
mypy-extensions @ file:///Users/runner/miniforge3/conda-bld/mypy_extensions_1666795208530/work
nbclassic @ file:///home/conda/feedstock_root/build_artifacts/nbclassic_1667492839781/work
nbclient @ file:///home/conda/feedstock_root/build_artifacts/nbclient_1669795076334/work
nbconvert @ file:///home/conda/feedstock_root/build_artifacts/nbconvert-meta_1670253564810/work
nbformat @ file:///home/conda/feedstock_root/build_artifacts/nbformat_1665426034066/work
nest-asyncio @ file:///home/conda/feedstock_root/build_artifacts/nest-asyncio_1664684991461/work
netaddr==0.8.0
notebook @ file:///home/conda/feedstock_root/build_artifacts/notebook_1667565639349/work
notebook_shim @ file:///home/conda/feedstock_root/build_artifacts/notebook-shim_1667478401171/work
packaging @ file:///home/conda/feedstock_root/build_artifacts/packaging_1637239678211/work
pandocfilters @ file:///home/conda/feedstock_root/build_artifacts/pandocfilters_1631603243851/work
parsimonious==0.8.1
parso @ file:///home/conda/feedstock_root/build_artifacts/parso_1638334955874/work
pathspec @ file:///home/conda/feedstock_root/build_artifacts/pathspec_1668325009666/work
pexpect @ file:///home/conda/feedstock_root/build_artifacts/pexpect_1667297516076/work
pickleshare @ file:///home/conda/feedstock_root/build_artifacts/pickleshare_1602536217715/work
pkgutil_resolve_name @ file:///home/conda/feedstock_root/build_artifacts/pkgutil-resolve-name_1633981968097/work
platformdirs @ file:///home/conda/feedstock_root/build_artifacts/platformdirs_1657729053205/work
pluggy @ file:///home/conda/feedstock_root/build_artifacts/pluggy_1667232663820/work
prometheus-client @ file:///home/conda/feedstock_root/build_artifacts/prometheus_client_1665692535292/work
prompt-toolkit @ file:///home/conda/feedstock_root/build_artifacts/prompt-toolkit_1669057097528/work
protobuf==3.19.5
psutil @ file:///Users/runner/miniforge3/conda-bld/psutil_1667885969940/work
ptyprocess @ file:///home/conda/feedstock_root/build_artifacts/ptyprocess_1609419310487/work/dist/ptyprocess-0.7.0-py2.py3-none-any.whl
pure-eval @ file:///home/conda/feedstock_root/build_artifacts/pure_eval_1642875951954/work
pycparser @ file:///home/conda/feedstock_root/build_artifacts/pycparser_1636257122734/work
pycryptodome==3.16.0
Pygments @ file:///home/conda/feedstock_root/build_artifacts/pygments_1660666458521/work
pyOpenSSL @ file:///home/conda/feedstock_root/build_artifacts/pyopenssl_1665350324128/work
pyparsing @ file:///home/conda/feedstock_root/build_artifacts/pyparsing_1652235407899/work
pyrsistent @ file:///Users/runner/miniforge3/conda-bld/pyrsistent_1667498802628/work
PySocks @ file:///home/conda/feedstock_root/build_artifacts/pysocks_1661604839144/work
pytest==7.2.0
python-dateutil @ file:///home/conda/feedstock_root/build_artifacts/python-dateutil_1626286286081/work
pytz @ file:///home/conda/feedstock_root/build_artifacts/pytz_1667391478166/work
pyzmq @ file:///Users/runner/miniforge3/conda-bld/pyzmq_1666828576791/work
requests @ file:///home/conda/feedstock_root/build_artifacts/requests_1661872987712/work
rlp==2.0.1
Send2Trash @ file:///home/conda/feedstock_root/build_artifacts/send2trash_1628511208346/work
six @ file:///home/conda/feedstock_root/build_artifacts/six_1620240208055/work
sniffio @ file:///home/conda/feedstock_root/build_artifacts/sniffio_1662051266223/work
soupsieve @ file:///home/conda/feedstock_root/build_artifacts/soupsieve_1658207591808/work
stack-data @ file:///home/conda/feedstock_root/build_artifacts/stack_data_1669632077133/work
terminado @ file:///Users/runner/miniforge3/conda-bld/terminado_1670254106711/work
tinycss2 @ file:///home/conda/feedstock_root/build_artifacts/tinycss2_1666100256010/work
tomli @ file:///home/conda/feedstock_root/build_artifacts/tomli_1644342247877/work
toolz==0.12.0
tornado @ file:///Users/runner/miniforge3/conda-bld/tornado_1666788903714/work
traitlets @ file:///home/conda/feedstock_root/build_artifacts/traitlets_1669796852779/work
typing_extensions @ file:///home/conda/feedstock_root/build_artifacts/typing_extensions_1665144421445/work
urllib3 @ file:///home/conda/feedstock_root/build_artifacts/urllib3_1669259737463/work
varint==1.0.2
wcwidth @ file:///home/conda/feedstock_root/build_artifacts/wcwidth_1600965781394/work
web3==5.31.3
webencodings==0.5.1
websocket-client @ file:///home/conda/feedstock_root/build_artifacts/websocket-client_1667568040382/work
websockets==9.1
yarl==1.8.2
zipp @ file:///home/conda/feedstock_root/build_artifacts/zipp_1669453021653/work
``` 

## How can it be fixed?

The root of the issue lies [here](https://github.com/ethereum/eth-abi/blob/master/eth_abi/decoding.py#L515), with the call to `ceil32`. I'm not sure why we're padding the expected input length. In this example, I stepped through the code and also compared it line by line to how `ethersjs` is handling this in JS. The `eth_abi` code successfully parses the last input to determine its length as `201 bytes` (which is exactly how many bytes remain for reading in the stream), and is then able to decode and store the result. However, the padded length after the call to `ceil32` is `224 bytes`, which of course exceeds the remaining input, causing the exception to be raised. Perhaps I'm missing something here, but it seems like this is a bug, especially when comparing against how `web3` and `ethersjs` are handling this on the JS side of the ecosystem.


Discussion:
fselmo: @jad-elmourad, pushed up 👍🏼. Let me know if you see anything that needs attention.

@pacrob + @reedsa, this is ready for a review. See the linked issue for a bit more context.

Patch:
diff --git a/docs/decoding.rst b/docs/decoding.rst
index 1740115..9948a02 100644
--- a/docs/decoding.rst
+++ b/docs/decoding.rst
@@ -29,3 +29,32 @@ The :py:meth:`eth_abi.decoding.BaseDecoder.decode` function provides an API for
 decoding binary values for ABI types into python values. It accepts a sequence of
 ABI type strings as the first argument and the binary data to be decoded, as a python
 ``bytes`` or ``bytearray`` value, as the second argument.
+
+Strict Mode
+-----------
+
+By default, the decoder will raise an exception if the binary data to be decoded
+is not padded to the next 32-byte boundary. This behavior can be disabled for the
+``ByteStringDecoder`` (the default decoder for ``bytes`` and ``string`` types) by
+passing ``strict=False`` to the :py:meth:`eth_abi.abi.decode` method. Turning off
+strict mode will also ignore any trailing bytes beyond the data size specified. This
+means that if there is any padding, the validation for only empty bytes in the padding
+area is also ignored.
+
+.. doctest::
+
+    >>> from eth_abi import abi
+
+    >>> # decode a bytes value without strict mode
+    >>> hex_val = (
+    ...     # offset to data is 32 bytes:
+    ...     "0000000000000000000000000000000000000000000000000000000000000020"
+    ...     # length of data is 1 byte:
+    ...     "0000000000000000000000000000000000000000000000000000000000000001"
+    ...     # b"\x01" with less than 32 bytes of padding
+    ...     # and not strictly padded with only zero bytes:
+    ...     "0100000000000001020300"
+    ... )
+    >>> (decoded,) = abi.decode(['bytes'], bytes.fromhex(hex_val), strict=False)
+    >>> decoded
+    b'\x01'
diff --git a/eth_abi/codec.py b/eth_abi/codec.py
index 25c7246..0e311d6 100644
--- a/eth_abi/codec.py
+++ b/eth_abi/codec.py
@@ -129,6 +129,7 @@ class ABIDecoder(BaseABICoder):
         self,
         types: Iterable[TypeStr],
         data: Decodable,
+        strict: bool = True,
     ) -> Tuple[Any, ...]:
         """
         Decodes the binary value ``data`` as a sequence of values of the ABI types
@@ -138,6 +139,10 @@ class ABIDecoder(BaseABICoder):
         :param types: A list or tuple of string representations of the ABI types that
             will be used for decoding e.g. ``('uint256', 'bytes[]', '(int,int)')``
         :param data: The binary value to be decoded.
+        :param strict: If ``False``, dynamic-type decoders will ignore validations such
+            as making sure the data is padded to a multiple of 32 bytes or checking that
+            padding bytes are zero / empty. ``False`` is how the Solidity ABI decoder
+            currently works. However, ``True`` is the default for the eth-abi library.
 
         :returns: A tuple of equivalent python values for the ABI values
             represented in ``data``.
@@ -146,7 +151,9 @@ class ABIDecoder(BaseABICoder):
         validate_list_like_param(types, "types")
         validate_bytes_param(data, "data")
 
-        decoders = [self._registry.get_decoder(type_str) for type_str in types]
+        decoders = [
+            self._registry.get_decoder(type_str, strict=strict) for type_str in types
+        ]
 
         decoder = TupleDecoder(decoders=decoders)
         stream = self.stream_class(data)
diff --git a/eth_abi/decoding.py b/eth_abi/decoding.py
index 517de02..be71e3c 100644
--- a/eth_abi/decoding.py
+++ b/eth_abi/decoding.py
@@ -115,6 +115,8 @@ class BaseDecoder(BaseCoder, metaclass=abc.ABCMeta):
     :any:`BaseCoder.from_type_str`.
     """
 
+    strict = True
+
     @abc.abstractmethod
     def decode(self, stream: ContextFramesBytesIO) -> Any:  # pragma: no cover
         """
@@ -303,10 +305,8 @@ class FixedByteSizeDecoder(SingleDecoder):
 
         if len(data) != self.data_byte_size:
             raise InsufficientDataBytes(
-                "Tried to read {0} bytes.  Only got {1} bytes".format(
-                    self.data_byte_size,
-                    len(data),
-                )
+                f"Tried to read {self.data_byte_size} bytes, "
+                f"only got {len(data)} bytes."
             )
 
         return data
@@ -330,7 +330,7 @@ class FixedByteSizeDecoder(SingleDecoder):
 
         if padding_bytes != b"\x00" * padding_size:
             raise NonEmptyPaddingBytes(
-                "Padding bytes were not empty: {0}".format(repr(padding_bytes))
+                f"Padding bytes were not empty: {repr(padding_bytes)}"
             )
 
     def _get_value_byte_size(self):
@@ -411,7 +411,7 @@ class SignedIntegerDecoder(Fixed32ByteSizeDecoder):
 
         if padding_bytes != expected_padding_bytes:
             raise NonEmptyPaddingBytes(
-                "Padding bytes were not empty: {0}".format(repr(padding_bytes))
+                f"Padding bytes were not empty: {repr(padding_bytes)}"
             )
 
     @parse_type_str("int")
@@ -488,7 +488,7 @@ class SignedFixedDecoder(BaseFixedDecoder):
 
         if padding_bytes != expected_padding_bytes:
             raise NonEmptyPaddingBytes(
-                "Padding bytes were not empty: {0}".format(repr(padding_bytes))
+                f"Padding bytes were not empty: {repr(padding_bytes)}"
             )
 
     @parse_type_str("fixed")
@@ -508,27 +508,23 @@ class ByteStringDecoder(SingleDecoder):
     def decoder_fn(data):
         return data
 
-    @staticmethod
-    def read_data_from_stream(stream):
+    def read_data_from_stream(self, stream):
         data_length = decode_uint_256(stream)
         padded_length = ceil32(data_length)
 
         data = stream.read(padded_length)
 
-        if len(data) < padded_length:
-            raise InsufficientDataBytes(
-                "Tried to read {0} bytes.  Only got {1} bytes".format(
-                    padded_length,
-                    len(data),
+        if self.strict:
+            if len(data) < padded_length:
+                raise InsufficientDataBytes(
+                    f"Tried to read {padded_length} bytes, only got {len(data)} bytes"
                 )
-            )
-
-        padding_bytes = data[data_length:]
 
-        if padding_bytes != b"\x00" * (padded_length - data_length):
-            raise NonEmptyPaddingBytes(
-                "Padding bytes were not empty: {0}".format(repr(padding_bytes))
-            )
+            padding_bytes = data[data_length:]
+            if padding_bytes != b"\x00" * (padded_length - data_length):
+                raise NonEmptyPaddingBytes(
+                    f"Padding bytes were not empty: {repr(padding_bytes)}"
+                )
 
         return data[:data_length]
 
diff --git a/eth_abi/registry.py b/eth_abi/registry.py
index 3f97a13..432ab85 100644
--- a/eth_abi/registry.py
+++ b/eth_abi/registry.py
@@ -485,8 +485,16 @@ class ABIRegistry(Copyable, BaseRegistry):
         return True
 
     @functools.lru_cache(maxsize=None)
-    def get_decoder(self, type_str):
-        return self._get_registration(self._decoders, type_str)
+    def get_decoder(self, type_str, strict=True):
+        decoder = self._get_registration(self._decoders, type_str)
+
+        if hasattr(decoder, "is_dynamic") and decoder.is_dynamic:
+            # Set a transient flag each time a call is made to ``get_decoder()``.
+            # Only dynamic decoders should be allowed these looser constraints. All
+            # other decoders should keep the default value of ``True``.
+            decoder.strict = strict
+
+        return decoder
 
     def copy(self):
         """
diff --git a/newsfragments/198.feature.rst b/newsfragments/198.feature.rst
new file mode 100644
index 0000000..a60dcb8
--- /dev/null
+++ b/newsfragments/198.feature.rst
@@ -0,0 +1,1 @@
+Allow turning off abi decoder "strict mode" when calling ``abi.decode()``.


Test patch:
diff --git a/tests/abi/test_decode.py b/tests/abi/test_decode.py
index 0a154d3..f56045e 100644
--- a/tests/abi/test_decode.py
+++ b/tests/abi/test_decode.py
@@ -20,35 +20,44 @@ from ..common.unit import (
 )
 
 
+@pytest.mark.parametrize("strict", (True, False))
 @pytest.mark.parametrize(
     "type_str,expected,abi_encoding,_",
     CORRECT_TUPLE_ENCODINGS,
 )
-def test_abi_decode_for_multiple_types_as_list(type_str, expected, abi_encoding, _):
+def test_abi_decode_for_multiple_types_as_list(
+    type_str, expected, abi_encoding, _, strict
+):
     abi_type = parse(type_str)
     if abi_type.arrlist is not None:
         pytest.skip("ABI coding functions do not support array types")
 
     types = [t.to_type_str() for t in abi_type.components]
 
-    actual = decode(types, abi_encoding)
+    actual = decode(types, abi_encoding, strict=strict)
     assert actual == expected
 
 
+@pytest.mark.parametrize("strict", (True, False))
 @pytest.mark.parametrize(
     "type_str,expected,abi_encoding,_",
     CORRECT_STATIC_ENCODINGS,
 )
-def test_abi_decode_for_single_static_types(type_str, expected, abi_encoding, _):
-    (actual,) = decode([type_str], abi_encoding)
+def test_abi_decode_for_single_static_types(
+    type_str, expected, abi_encoding, _, strict
+):
+    (actual,) = decode([type_str], abi_encoding, strict=strict)
     assert actual == expected
 
 
+@pytest.mark.parametrize("strict", (True, False))
 @pytest.mark.parametrize(
     "type_str,expected,abi_encoding,_",
     CORRECT_DYNAMIC_ENCODINGS,
 )
-def test_abi_decode_for_single_dynamic_types(type_str, expected, abi_encoding, _):
+def test_abi_decode_for_single_dynamic_types(
+    type_str, expected, abi_encoding, _, strict
+):
     # Tests set up list values but encoders return sequences as tuples.
     # i.e. [b'\xde\xad\xbe\xef'] vs encoder return type (b'\xde\xad\xbe\xef',)
     expected = tuple(expected) if isinstance(expected, list) else expected
@@ -58,25 +67,28 @@ def test_abi_decode_for_single_dynamic_types(type_str, expected, abi_encoding, _
         b"".join([words("20"), abi_encoding])
     )
 
-    (actual,) = decode([type_str], abi_encoding)
+    (actual,) = decode([type_str], abi_encoding, strict=strict)
 
     assert actual == expected
 
 
+@pytest.mark.parametrize("strict", (True, False))
 @pytest.mark.parametrize("data", (b"", bytearray()))
-def test_abi_decode_empty_data_raises(data):
+def test_abi_decode_empty_data_raises(data, strict):
     with pytest.raises(InsufficientDataBytes):
-        decode(["uint"], data)
+        decode(["uint"], data, strict=strict)
 
 
+@pytest.mark.parametrize("strict", (True, False))
 @pytest.mark.parametrize("data", ("", 123, 0x123, [b"\x01"], (b"\x01",), {b"\x01"}))
-def test_abi_decode_wrong_data_param_type_raises(data):
+def test_abi_decode_wrong_data_param_type_raises(data, strict):
     with pytest.raises(
         TypeError, match=f"The `data` value must be of bytes type. Got {type(data)}"
     ):
-        decode(["uint32", "uint32"], data)
+        decode(["uint32", "uint32"], data, strict=strict)
 
 
+@pytest.mark.parametrize("strict", (True, False))
 @pytest.mark.parametrize(
     "types",
     (
@@ -90,21 +102,99 @@ def test_abi_decode_wrong_data_param_type_raises(data):
         {1, 2},
     ),
 )
-def test_abi_decode_wrong_types_param_type_raises(types):
+def test_abi_decode_wrong_types_param_type_raises(types, strict):
     with pytest.raises(
         TypeError,
         match=f"The `types` value type must be one of list or tuple. Got {type(types)}",
     ):
-        decode(types, b"\x00" * 32)
+        decode(types, b"\x00" * 32, strict=strict)
 
 
+@pytest.mark.parametrize("strict", (True, False))
 @pytest.mark.parametrize(
     "zero_sized_tuple_type",
     ("()[]", "()", "(int,())", "(int,((),))", "(int,(),int)"),
 )
-def test_abi_decode_raises_for_zero_sized_tuple_type(zero_sized_tuple_type):
+def test_abi_decode_raises_for_zero_sized_tuple_type(zero_sized_tuple_type, strict):
     with pytest.raises(
         ValueError,
         match=re.escape('Zero-sized tuple types "()" are not supported.'),
     ):
-        decode([zero_sized_tuple_type], b"bytes data shouldn't matter for validation")
+        decode(
+            [zero_sized_tuple_type],
+            b"bytes data shouldn't matter for validation",
+            strict=strict,
+        )
+
+
+@pytest.mark.parametrize(
+    "types,hex_data,expected",
+    (
+        (
+            ["string"],
+            (
+                "0000000000000000000000000000000000000000000000000000000000000020"
+                "0000000000000000000000000000000000000000000000000000000000000011"
+                "6e6f2070616464696e67206e6565646564"
+            ),
+            ("no padding needed",),
+        ),
+        (
+            ["bytes"],
+            (
+                # offset to data is 32 bytes:
+                "0000000000000000000000000000000000000000000000000000000000000020"
+                # length of data is 1 byte:
+                "0000000000000000000000000000000000000000000000000000000000000001"
+                # b"\x01" with less than 32 bytes of padding:
+                "01000000000000000000"
+            ),
+            (b"\x01",),
+        ),
+        (
+            # example from issue #198
+            ["uint256", "uint256", "address", "bytes"],
+            (
+                # uint256 with value of 891276594009425425420:
+                "00000000000000000000000000000000000000000000003050f28c10d7d9b40c"
+                # uint256 with value of 0:
+                "0000000000000000000000000000000000000000000000000000000000000000"
+                # address:
+                "000000000000000000000000b5c7ad3cb6506c65da01f2fac2e667dcb9e66e9c"
+                # data starts at 128 bytes into the byte array:
+                "0000000000000000000000000000000000000000000000000000000000000080"
+                # 201 bytes of data (padded would've been 224 bytes):
+                "00000000000000000000000000000000000000000000000000000000000000c9"
+                "04853d955acef822db058eb8505911ed77f175b99e1531c1a63a169ac75a2daa"
+                "e399080745fa51de440000000000000000000000000000000000000000000000"
+                "3050f28c10d7d9b40c7bc2c873190bbaddefe646c35f1ae6cffbfb402059bd67"
+                "74c22486d9f4fab2d448dce4f892a9ae250d0ab87046fbb341d058f17cbc4c11"
+                "33f25a20a52f000db63cac384247597756545b500253ff8e607a8020010b0002"
+                "0f0100000e030c01990966d504030200000569b81152c5a8d35a67b32a4d3772"
+                "795d96cae4da010106"
+            ),
+            (
+                891276594009425425420,
+                0,
+                "0xb5c7ad3cb6506c65da01f2fac2e667dcb9e66e9c",
+                b'\x04\x85=\x95Z\xce\xf8"\xdb\x05\x8e\xb8PY\x11\xedw\xf1u\xb9\x9e'
+                b"\x151\xc1\xa6:\x16\x9a\xc7Z-\xaa\xe3\x99\x08\x07E\xfaQ\xdeD\x00"
+                b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+                b"\x00\x00\x00\x00\x00\x000P\xf2\x8c\x10\xd7\xd9\xb4\x0c{\xc2\xc8s"
+                b"\x19\x0b\xba\xdd\xef\xe6F\xc3_\x1a\xe6\xcf\xfb\xfb@ Y\xbdgt\xc2$"
+                b"\x86\xd9\xf4\xfa\xb2\xd4H\xdc\xe4\xf8\x92\xa9\xae%\r\n\xb8pF\xfb"
+                b"\xb3A\xd0X\xf1|\xbcL\x113\xf2Z \xa5/\x00\r\xb6<\xac8BGYwVT[P\x02S"
+                b"\xff\x8e`z\x80 \x01\x0b\x00\x02\x0f\x01\x00\x00\x0e\x03\x0c\x01"
+                b"\x99\tf\xd5\x04\x03\x02\x00\x00\x05i\xb8\x11R\xc5\xa8\xd3Zg"
+                b"\xb3*M7ry]\x96\xca\xe4\xda\x01\x01\x06",
+            ),
+        ),
+    ),
+)
+def test_abi_decode_with_shorter_data_than_32_bytes(types, hex_data, expected):
+    assert decode(types, bytes.fromhex(hex_data), strict=False) == expected
+
+    # assert flag is transient and does not affect other calls to ``decode()``
+    # without the flag set (i.e. assert the default behavior is always ``strict=True``).
+    with pytest.raises(InsufficientDataBytes):
+        decode(types, bytes.fromhex(hex_data))