Repository: ethereum/web3.py
Pull request #2607 (2022-08-11 16:47:54)

Update eth-account requirement to prevent ReDOS attacks
### What was wrong?
eth-account 0.5.9 introduces a fix for a Regex DOS attack vector. See eth-account PR [#178](https://github.com/ethereum/eth-account/pull/178)

Related to Issue #2606

### How was it fixed?
Bumped requirement.

### Todo:
- [x] Add entry to the [release notes](https://github.com/ethereum/web3.py/blob/master/newsfragments/README.md)

#### Cute Animal Picture

![Put a link to a cute animal picture inside the parenthesis-->](https://static.boredpanda.com/blog/wp-content/uploads/2019/12/5e04aa216b89f_5n8HL1dwsvV9SWfkxzqO5enZeIcb9UbFEZw0ZFVONg4__700.jpg)



Patch:
diff --git a/.bumpversion.cfg b/.bumpversion.cfg
index 65d691c4..5ad85c2f 100644
--- a/.bumpversion.cfg
+++ b/.bumpversion.cfg
@@ -3,14 +3,14 @@ current_version = 5.30.0
 commit = True
 tag = True
 parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(-(?P<stage>[^.]*)\.(?P<devnum>\d+))?
-serialize = 
+serialize =
 	{major}.{minor}.{patch}-{stage}.{devnum}
 	{major}.{minor}.{patch}
 
 [bumpversion:part:stage]
 optional_value = stable
 first_value = stable
-values = 
+values =
 	alpha
 	beta
 	stable
@@ -18,5 +18,5 @@ values =
 [bumpversion:part:devnum]
 
 [bumpversion:file:setup.py]
-search = version='{current_version}',
-replace = version='{new_version}',
+search = version="{current_version}",
+replace = version="{new_version}",
diff --git a/docs/conf.py b/docs/conf.py
index fd50c0c2..e141df1e 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -14,46 +14,46 @@
 # If extensions (or modules to document with autodoc) are in another directory,
 # add these directories to sys.path here. If the directory is relative to the
 # documentation root, use os.path.abspath to make it absolute, like shown here.
-#sys.path.insert(0, os.path.abspath('.'))
+# sys.path.insert(0, os.path.abspath('.'))
 
 import os
 
-DIR = os.path.dirname('__file__')
-with open (os.path.join(DIR, '../setup.py'), 'r') as f:
+DIR = os.path.dirname("__file__")
+with open(os.path.join(DIR, "../setup.py"), "r") as f:
     for line in f:
-        if 'version=' in line:
-            setup_version = line.split('\'')[1]
+        if "version=" in line:
+            setup_version = line.split('"')[1]
             break
 
 # -- General configuration ------------------------------------------------
 
 # If your documentation needs a minimal Sphinx version, state it here.
-#needs_sphinx = '1.0'
+# needs_sphinx = '1.0'
 
 # Add any Sphinx extension module names here, as strings. They can be
 # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
 # ones.
 extensions = [
-    'sphinx.ext.autodoc',
-    'sphinx.ext.doctest',
-    'sphinx.ext.intersphinx',
+    "sphinx.ext.autodoc",
+    "sphinx.ext.doctest",
+    "sphinx.ext.intersphinx",
 ]
 
 # Add any paths that contain templates here, relative to this directory.
-templates_path = ['_templates']
+templates_path = ["_templates"]
 
 # The suffix of source filenames.
-source_suffix = '.rst'
+source_suffix = ".rst"
 
 # The encoding of source files.
-#source_encoding = 'utf-8-sig'
+# source_encoding = 'utf-8-sig'
 
 # The master toctree document.
-master_doc = 'index'
+master_doc = "index"
 
 # General information about the project.
-project = u'Web3.py'
-copyright = u'2018, Piper Merriam, Jason Carver'
+project = "Web3.py"
+copyright = "2018, Piper Merriam, Jason Carver"
 
 __version__ = setup_version
 # The version info for the project you're documenting, acts as replacement for
@@ -61,95 +61,95 @@ __version__ = setup_version
 # built documents.
 #
 # The short X.Y version.
-version = '.'.join(__version__.split('.')[:2])
+version = ".".join(__version__.split(".")[:2])
 # The full version, including alpha/beta/rc tags.
 release = __version__
 
 # The language for content autogenerated by Sphinx. Refer to documentation
 # for a list of supported languages.
-#language = None
+# language = None
 
 # There are two options for replacing |today|: either, you set today to some
 # non-false value, then it is used:
-#today = ''
+# today = ''
 # Else, today_fmt is used as the format for a strftime call.
-#today_fmt = '%B %d, %Y'
+# today_fmt = '%B %d, %Y'
 
 # List of patterns, relative to source directory, that match files and
 # directories to ignore when looking for source files.
 exclude_patterns = [
-    '_build',
-    'web3.rst',
-    'modules.rst',
-    'web3.auto.rst',
-    'web3.auto.infura.rst',
-    'web3.gas_strategies.rst',
-    'web3.middleware.rst',
-    'web3.providers.rst',
-    'web3.providers.eth_tester.rst',
-    'web3.scripts.*',
-    'web3.testing.rst',
-    'web3.tools.*',
+    "_build",
+    "web3.rst",
+    "modules.rst",
+    "web3.auto.rst",
+    "web3.auto.infura.rst",
+    "web3.gas_strategies.rst",
+    "web3.middleware.rst",
+    "web3.providers.rst",
+    "web3.providers.eth_tester.rst",
+    "web3.scripts.*",
+    "web3.testing.rst",
+    "web3.tools.*",
 ]
 
 # The reST default role (used for this markup: `text`) to use for all
 # documents.
-#default_role = None
+# default_role = None
 
 # If true, '()' will be appended to :func: etc. cross-reference text.
-#add_function_parentheses = True
+# add_function_parentheses = True
 
 # If true, the current module name will be prepended to all description
 # unit titles (such as .. function::).
-#add_module_names = True
+# add_module_names = True
 
 # If true, sectionauthor and moduleauthor directives will be shown in the
 # output. They are ignored by default.
-#show_authors = False
+# show_authors = False
 
 # The name of the Pygments (syntax highlighting) style to use.
-pygments_style = 'sphinx'
+pygments_style = "sphinx"
 
 # A list of ignored prefixes for module index sorting.
-#modindex_common_prefix = []
+# modindex_common_prefix = []
 
 # If true, keep warnings as "system message" paragraphs in the built documents.
-#keep_warnings = False
+# keep_warnings = False
 
 
 # -- Options for HTML output ----------------------------------------------
 
 # The theme to use for HTML and HTML Help pages.  See the documentation for
 # a list of builtin themes.
-html_theme = 'sphinx_rtd_theme'
+html_theme = "sphinx_rtd_theme"
 
 # Theme options are theme-specific and customize the look and feel of a theme
 # further.  For a list of options available for each theme, see the
 # documentation.
-#html_theme_options = {}
+# html_theme_options = {}
 
 # Add any paths that contain custom themes here, relative to this directory.
 
 # The name for this set of Sphinx documents.  If None, it defaults to
 # "<project> v<release> documentation".
-#html_title = None
+# html_title = None
 
 # A shorter title for the navigation bar.  Default is the same as html_title.
-#html_short_title = None
+# html_short_title = None
 
 # The name of an image file (relative to this directory) to place at the top
 # of the sidebar.
-#html_logo = None
+# html_logo = None
 
 # The name of an image file (within the static path) to use as favicon of the
 # docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32
 # pixels large.
-#html_favicon = None
+# html_favicon = None
 
 # Add any paths that contain custom static files (such as style sheets) here,
 # relative to this directory. They are copied after the builtin static files,
 # so a file named "default.css" will overwrite the builtin "default.css".
-html_static_path = ['_static']
+html_static_path = ["_static"]
 
 html_css_files = ["css/toggle.css"]
 
@@ -162,102 +162,96 @@ html_extra_path = ["_static/css"]
 
 # If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
 # using the given strftime format.
-#html_last_updated_fmt = '%b %d, %Y'
+# html_last_updated_fmt = '%b %d, %Y'
 
 # If true, SmartyPants will be used to convert quotes and dashes to
 # typographically correct entities.
-#html_use_smartypants = True
+# html_use_smartypants = True
 
 # Custom sidebar templates, maps document names to template names.
-#html_sidebars = {}
+# html_sidebars = {}
 
 # Additional templates that should be rendered to pages, maps page names to
 # template names.
-#html_additional_pages = {}
+# html_additional_pages = {}
 
 # If false, no module index is generated.
-#html_domain_indices = True
+# html_domain_indices = True
 
 # If false, no index is generated.
-#html_use_index = True
+# html_use_index = True
 
 # If true, the index is split into individual pages for each letter.
-#html_split_index = False
+# html_split_index = False
 
 # If true, links to the reST sources are added to the pages.
-#html_show_sourcelink = True
+# html_show_sourcelink = True
 
 # If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
-#html_show_sphinx = True
+# html_show_sphinx = True
 
 # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
-#html_show_copyright = True
+# html_show_copyright = True
 
 # If true, an OpenSearch description file will be output, and all pages will
 # contain a <link> tag referring to it.  The value of this option must be the
 # base URL from which the finished HTML is served.
-#html_use_opensearch = ''
+# html_use_opensearch = ''
 
 # This is the file name suffix for HTML files (e.g. ".xhtml").
-#html_file_suffix = None
+# html_file_suffix = None
 
 # Output file base name for HTML help builder.
-htmlhelp_basename = 'Populusdoc'
+htmlhelp_basename = "Populusdoc"
 
 
 # -- Options for LaTeX output ---------------------------------------------
 
 latex_elements = {
-# The paper size ('letterpaper' or 'a4paper').
-#'papersize': 'letterpaper',
-
-# The font size ('10pt', '11pt' or '12pt').
-#'pointsize': '10pt',
-
-# Additional stuff for the LaTeX preamble.
-#'preamble': '',
+    # The paper size ('letterpaper' or 'a4paper').
+    #'papersize': 'letterpaper',
+    # The font size ('10pt', '11pt' or '12pt').
+    #'pointsize': '10pt',
+    # Additional stuff for the LaTeX preamble.
+    #'preamble': '',
 }
 
 # Grouping the document tree into LaTeX files. List of tuples
 # (source start file, target name, title,
 #  author, documentclass [howto, manual, or own class]).
 latex_documents = [
-  ('index', 'Populus.tex', u'Populus Documentation',
-   u'Piper Merriam', 'manual'),
+    ("index", "Populus.tex", "Populus Documentation", "Piper Merriam", "manual"),
 ]
 
 # The name of an image file (relative to this directory) to place at the top of
 # the title page.
-#latex_logo = None
+# latex_logo = None
 
 # For "manual" documents, if this is true, then toplevel headings are parts,
 # not chapters.
-#latex_use_parts = False
+# latex_use_parts = False
 
 # If true, show page references after internal links.
-#latex_show_pagerefs = False
+# latex_show_pagerefs = False
 
 # If true, show URL addresses after external links.
-#latex_show_urls = False
+# latex_show_urls = False
 
 # Documents to append as an appendix to all manuals.
-#latex_appendices = []
+# latex_appendices = []
 
 # If false, no module index is generated.
-#latex_domain_indices = True
+# latex_domain_indices = True
 
 
 # -- Options for manual page output ---------------------------------------
 
 # One entry per manual page. List of tuples
 # (source start file, name, description, authors, manual section).
-man_pages = [
-    ('index', 'web3', u'Web3.py Documentation',
-     [u'Piper Merriam'], 1)
-]
+man_pages = [("index", "web3", "Web3.py Documentation", ["Piper Merriam"], 1)]
 
 # If true, show URL addresses after external links.
-#man_show_urls = False
+# man_show_urls = False
 
 
 # -- Options for Texinfo output -------------------------------------------
@@ -266,30 +260,36 @@ man_pages = [
 # (source start file, target name, title, author,
 #  dir menu entry, description, category)
 texinfo_documents = [
-  ('index', 'Web3.py', u'Web3.py Documentation',
-   u'Piper Merriam', 'Web3.py', 'Backend agnostic Ethereum client interactions.',
-   'Miscellaneous'),
+    (
+        "index",
+        "Web3.py",
+        "Web3.py Documentation",
+        "Piper Merriam",
+        "Web3.py",
+        "Backend agnostic Ethereum client interactions.",
+        "Miscellaneous",
+    ),
 ]
 
 # Documents to append as an appendix to all manuals.
-#texinfo_appendices = []
+# texinfo_appendices = []
 
 # If false, no module index is generated.
-#texinfo_domain_indices = True
+# texinfo_domain_indices = True
 
 # How to display URL addresses: 'footnote', 'no', or 'inline'.
-#texinfo_show_urls = 'footnote'
+# texinfo_show_urls = 'footnote'
 
 # If true, do not generate a @detailmenu in the "Top" node's menu.
-#texinfo_no_detailmenu = False
+# texinfo_no_detailmenu = False
 
 # -- Intersphinx configuration ------------------------------------------------
 
 intersphinx_mapping = {
-    'python': ('https://docs.python.org/3.5', None),
-    'eth-account': ('http://eth-account.readthedocs.io/en/latest/', None),
+    "python": ("https://docs.python.org/3.5", None),
+    "eth-account": ("http://eth-account.readthedocs.io/en/latest/", None),
     # does not exist yet: 'eth-tester': ('http://eth-tester.readthedocs.io/en/latest/', None),
-    'hexbytes': ('http://hexbytes.readthedocs.io/en/latest/', None),
+    "hexbytes": ("http://hexbytes.readthedocs.io/en/latest/", None),
 }
 
 autodoc_member_order = "bysource"
@@ -298,7 +298,8 @@ autodoc_member_order = "bysource"
 
 import doctest
 
-doctest_default_flags = (0
+doctest_default_flags = (
+    0
     | doctest.DONT_ACCEPT_TRUE_FOR_1
     | doctest.ELLIPSIS
     | doctest.IGNORE_EXCEPTION_DETAIL
diff --git a/newsfragments/2607.bugfix.rst b/newsfragments/2607.bugfix.rst
new file mode 100644
index 00000000..0cc583cf
--- /dev/null
+++ b/newsfragments/2607.bugfix.rst
@@ -0,0 +1,1 @@
+Bump eth-account requirement to >=0.5.9,<0.6.0 to incorporate fix for ReDOS-able regex
diff --git a/newsfragments/2609.bugfix.rst b/newsfragments/2609.bugfix.rst
new file mode 100644
index 00000000..663e8e36
--- /dev/null
+++ b/newsfragments/2609.bugfix.rst
@@ -0,0 +1,1 @@
+Allow hex for ``value`` field when validating via ``validate_payable()`` contracts method
diff --git a/setup.py b/setup.py
index 96ddf218..46526da4 100644
--- a/setup.py
+++ b/setup.py
@@ -5,11 +5,11 @@ from setuptools import (
 )
 
 extras_require = {
-    'tester': [
+    "tester": [
         "eth-tester[py-evm]==v0.6.0-beta.6",
         "py-geth>=3.8.0,<4",
     ],
-    'linter': [
+    "linter": [
         "flake8==3.8.3",
         "isort>=4.2.15,<4.3.5",
         "mypy==0.910",
@@ -17,7 +17,7 @@ extras_require = {
         "types-requests>=2.26.1,<3",
         "types-protobuf==3.19.13",
     ],
-    'docs': [
+    "docs": [
         "mock",
         "sphinx-better-theme>=0.1.4",
         "click>=5.1",
@@ -32,9 +32,9 @@ extras_require = {
         "towncrier==18.5.0",
         "urllib3",
         "wheel",
-        "Jinja2<=3.0.3", # Jinja v3.1.0 dropped support for python 3.6
+        "Jinja2<=3.0.3",  # Jinja v3.1.0 dropped support for python 3.6
     ],
-    'dev': [
+    "dev": [
         "bumpversion",
         "flaky>=3.7.0,<4",
         "hypothesis>=3.31.2,<6",
@@ -49,35 +49,35 @@ extras_require = {
         "tqdm>4.32,<5",
         "twine>=1.13,<2",
         "pluggy==0.13.1",
-        "when-changed>=0.3.0,<0.4"
-    ]
+        "when-changed>=0.3.0,<0.4",
+    ],
 }
 
-extras_require['dev'] = (
-    extras_require['tester']
-    + extras_require['linter']
-    + extras_require['docs']
-    + extras_require['dev']
+extras_require["dev"] = (
+    extras_require["tester"]
+    + extras_require["linter"]
+    + extras_require["docs"]
+    + extras_require["dev"]
 )
 
-with open('./README.md') as readme:
+with open("./README.md") as readme:
     long_description = readme.read()
 
 setup(
-    name='web3',
+    name="web3",
     # *IMPORTANT*: Don't manually change the version here. Use the 'bumpversion' utility.
-    version='5.30.0',
+    version="5.30.0",
     description="""Web3.py""",
-    long_description_content_type='text/markdown',
+    long_description_content_type="text/markdown",
     long_description=long_description,
-    author='Piper Merriam',
-    author_email='pipermerriam@gmail.com',
-    url='https://github.com/ethereum/web3.py',
+    author="Piper Merriam",
+    author_email="pipermerriam@gmail.com",
+    url="https://github.com/ethereum/web3.py",
     include_package_data=True,
     install_requires=[
         "aiohttp>=3.7.4.post0,<4",
         "eth-abi>=2.0.0b6,<3.0.0",
-        "eth-account>=0.5.7,<0.6.0",
+        "eth-account>=0.5.9,<0.6.0",
         "eth-hash[pycryptodome]>=0.2.0,<1.0.0",
         # eth-account allows too broad of an eth-rlp dependency.
         # This eth-rlp pin can be removed once it gets tightened up in eth-account
@@ -95,24 +95,24 @@ setup(
         "typing-extensions>=3.7.4.1,<5;python_version<'3.8'",
         "websockets>=9.1,<10",
     ],
-    python_requires='>=3.6,<4',
+    python_requires=">=3.6,<4",
     extras_require=extras_require,
-    py_modules=['web3', 'ens', 'ethpm'],
+    py_modules=["web3", "ens", "ethpm"],
     entry_points={"pytest11": ["pytest_ethereum = web3.tools.pytest_ethereum.plugins"]},
     license="MIT",
     zip_safe=False,
-    keywords='ethereum',
+    keywords="ethereum",
     packages=find_packages(exclude=["tests", "tests.*"]),
     package_data={"web3": ["py.typed"]},
     classifiers=[
-        'Development Status :: 5 - Production/Stable',
-        'Intended Audience :: Developers',
-        'License :: OSI Approved :: MIT License',
-        'Natural Language :: English',
-        'Programming Language :: Python :: 3',
-        'Programming Language :: Python :: 3.6',
-        'Programming Language :: Python :: 3.7',
-        'Programming Language :: Python :: 3.8',
-        'Programming Language :: Python :: 3.9',
+        "Development Status :: 5 - Production/Stable",
+        "Intended Audience :: Developers",
+        "License :: OSI Approved :: MIT License",
+        "Natural Language :: English",
+        "Programming Language :: Python :: 3",
+        "Programming Language :: Python :: 3.6",
+        "Programming Language :: Python :: 3.7",
+        "Programming Language :: Python :: 3.8",
+        "Programming Language :: Python :: 3.9",
     ],
 )
diff --git a/web3/_utils/contracts.py b/web3/_utils/contracts.py
index c7d6f458..65a865d4 100644
--- a/web3/_utils/contracts.py
+++ b/web3/_utils/contracts.py
@@ -53,6 +53,9 @@ from web3._utils.function_identifiers import (
     FallbackFn,
     ReceiveFn,
 )
+from web3._utils.method_formatters import (
+    to_integer_if_hex,
+)
 from web3._utils.normalizers import (
     abi_address_to_hex,
     abi_bytes_to_bytes,
@@ -313,10 +316,10 @@ def get_function_info(
 
 def validate_payable(transaction: TxParams, abi: ABIFunction) -> None:
     """Raise ValidationError if non-zero ether
-    is sent to a non payable function.
+    is sent to a non-payable function.
     """
     if 'value' in transaction:
-        if transaction['value'] != 0:
+        if to_integer_if_hex(transaction['value']) != 0:
             if "payable" in abi and not abi["payable"]:
                 raise ValidationError(
                     "Sending non-zero ether to a contract function "


Test patch:
diff --git a/tests/core/contracts/test_contract_util_functions.py b/tests/core/contracts/test_contract_util_functions.py
index 382a0e4d..ed9fad0b 100644
--- a/tests/core/contracts/test_contract_util_functions.py
+++ b/tests/core/contracts/test_contract_util_functions.py
@@ -1,3 +1,8 @@
+import pytest
+
+from web3._utils.contracts import (
+    validate_payable,
+)
 from web3.contract import (
     parse_block_identifier_int,
 )
@@ -11,3 +16,10 @@ from web3.contract import (
 def test_parse_block_identifier_int(web3):
     last_num = web3.eth.get_block('latest').number
     assert 0 == parse_block_identifier_int(web3, -1 - last_num)
+
+
+@pytest.mark.parametrize("value", (0, "0x0", "0x00"))
+def test_validate_payable(value):
+    tx = {"value": value}
+    abi = {"payable": False}
+    validate_payable(tx, abi)