Skip to content

fix: accept hex-encoded block number as contract call block_identifier - #3868

Open
cristianizzo wants to merge 2 commits into
ApeWorX:mainfrom
cristianizzo:fix/3646-hex-block-identifier
Open

fix: accept hex-encoded block number as contract call block_identifier#3868
cristianizzo wants to merge 2 commits into
ApeWorX:mainfrom
cristianizzo:fix/3646-hex-block-identifier

Conversation

@cristianizzo

@cristianizzo cristianizzo commented Jul 26, 2026

Copy link
Copy Markdown

What was wrong?

Related to Issue #3646
Closes #3646

Passing a hex-encoded block number string (e.g. "0x1510FA4") as a contract call block_identifier raised BlockNumberOutOfRange:

deposit_contract.get_deposit_root("0x1510FA4")
# ...
#   File ".../web3/_utils/contracts.py", line 337, in parse_block_identifier
#     raise BlockNumberOutOfRange
# web3.exceptions.BlockNumberOutOfRange

parse_block_identifier (and its async twin) handled int, the named blocks, and block hashes, but had no case for a hex-encoded block number, so those values fell through to else: raise BlockNumberOutOfRange — even though HexStr is part of the BlockIdentifier union and select_method_for_block_identifier already accepts this form.

How was it fixed?

Added a branch to both parse_block_identifier and async_parse_block_identifier that resolves a 0x-prefixed hex block number to its integer BlockNumber — the same output type the plain-int branch produces, so it flows through eth_call unchanged with no extra RPC round-trip.

The branch is gated on a private _is_hex_block_number helper rather than the existing is_hex_encoded_block_number. That distinction is deliberate and is the important review point:

  • is_hex_encoded_block_number is a routing predicate. select_method_for_block_identifier uses it to pick an endpoint and then forwards the raw string to the node, which performs final validation — so it does not require a 0x prefix (it just attempts int(value, 16)).
  • This branch instead converts locally, which removes the node's chance to reject bad input. Reusing the lax predicate would silently reinterpret values: the decimal string "20" would resolve to block 0x20 (32), "abc" to 2748, and " 0x1a " / "0x1_a" / "+0x1a" would all be accepted.

So _is_hex_block_number additionally requires text, a 0x/0X prefix, and canonical hex. I kept it local rather than tightening is_hex_encoded_block_number, because that shared predicate feeds 12 Method declarations across web3/eth/eth.py and web3/eth/async_eth.py (eth_getBlockBy*, eth_getUncle*, etc.); tightening it would be a behavior change to those public methods and belongs in its own PR. Happy to go that route instead if you'd prefer.

Ordering is preserved: the new branch sits after the block-hash check, so a 64-character hex string is still treated as a block hash.

Tests: parametrized sync + async cases for "0x0", mixed-case "0XABC", and the odd-length "0x1510FA4" from the issue; plus negative cases ("abc", "20", "1000000", " 0x1a ", "0x1_a", "+0x1a", "0b11", "0x") asserting those still raise.

Not changed: a block hash still resolves via eth_getBlockByHash. Per your note on the issue, passing a hash straight through to eth_call is a breaking change better suited to the next major version, so it's out of scope here.

Possible follow-up (not included, to keep this focused): raise BlockNumberOutOfRange is raised bare, so the error names neither the rejected value nor the accepted formats. Happy to add a message in a separate PR.

Todo:

  • Clean up commit history
  • Add or update documentation related to these changes
  • Add entry to the release notes

Cute Animal Picture

cute animal

A hex-encoded block number string (e.g. "0x1510FA4") is documented as a
valid block_identifier, and the codebase already exposes an
is_hex_encoded_block_number helper. However parse_block_identifier (and
its async twin) never used it, so such values fell through to
BlockNumberOutOfRange. Route them through the helper and resolve to the
integer block number, matching the documented behavior.

Fixes ApeWorX#3646
Copilot AI review requested due to automatic review settings July 26, 2026 23:42

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes contract call block identifier parsing so hex-encoded block numbers (e.g. "0x1510FA4") are accepted and converted to an integer BlockNumber, aligning runtime behavior with the documented accepted block_identifier formats and avoiding a BlockNumberOutOfRange error.

Changes:

  • Add handling for hex-encoded block-number strings in parse_block_identifier and async_parse_block_identifier.
  • Add sync + async parametrized tests for common and edge-case hex block-number inputs (including odd-length and mixed-case).
  • Add a release note fragment for the bugfix.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
web3/_utils/contracts.py Extends block identifier parsing to recognize hex-encoded block numbers and convert them to BlockNumber.
tests/core/contracts/test_contract_util_functions.py Adds sync/async tests validating hex-string block numbers are parsed to the expected integers.
newsfragments/3646.bugfix.rst Documents the bugfix in release notes.
Comments suppressed due to low confidence (1)

web3/_utils/contracts.py:379

  • Same concern as the sync variant: is_hex_encoded_block_number() accepts non-0x-prefixed strings (e.g. "10"), so this will silently reinterpret such inputs as hex block numbers. Consider requiring an explicit 0x/0X prefix to avoid surprising behavior changes and to match JSON-RPC quantity formatting.
    elif is_hex_encoded_block_number(block_identifier):
        return BlockNumber(int(block_identifier, 16))

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread web3/_utils/contracts.py Outdated
is_hex_encoded_block_number is a routing predicate for requests that
forward the raw string to the node, so it does not require a 0x prefix.
Reusing it here was wrong: this branch converts to an int locally, so
loose input silently changed meaning -- the decimal string "20" resolved
to block 0x20 (32), and "abc" to 2748, rather than raising. That also
regressed the existing invalid-block-identifier caller tests.

Gate the branch on a stricter local predicate requiring 0x-prefixed
canonical hex, and cover the loose forms with negative tests.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Call contract method with blockhash requires fetching block number

2 participants