Summary
The Python contract wrappers resolve ABI functions by name only. When an ABI contains overloads, _find_function() always returns the first same-name entry, regardless of the supplied arguments. This can make valid overloads unusable and can also select the wrong selector, smart-routing decision, or output schema.
Affected commit and files
Commit: d99e46cdc2d7ec8c049452ba72127f0540582f53
Expected behavior
Given a valid ABI containing lookup(uint256) and lookup(address), calling contract.tread.lookup(address_value) should select lookup(address), encode with the address ABI encoder, and use the lookup(address) selector. This is also how the pinned web3.py dependency resolves the same ABI and arguments.
Actual behavior
The first ABI entry is always selected. If lookup(uint256) appears first, the valid address call fails before RPC dispatch:
eth_abi.exceptions.EncodingTypeError: Value ... cannot be encoded by UnsignedIntegerEncoder
Reversing the ABI order merely changes which overload is unusable.
Minimal local regression
def test_tread_resolves_overloaded_function_from_arguments() -> None:
abi = [
{
"type": "function",
"name": "lookup",
"inputs": [{"name": "value", "type": "uint256"}],
"outputs": [{"name": "", "type": "bool"}],
"stateMutability": "view",
},
{
"type": "function",
"name": "lookup",
"inputs": [{"name": "account", "type": "address"}],
"outputs": [{"name": "", "type": "bool"}],
"stateMutability": "view",
},
]
w3 = MagicMock()
w3.eth.call.return_value = encode(["bool"], [True])
result = PublicContract(w3, ADDRESS, abi).tread.lookup(ARGUMENT_ADDRESS)
assert result is True
request = w3.eth.call.call_args.args[0]
assert bytes(request["data"][:4]) == keccak(b"lookup(address)")[:4]
Test command and observed output
uv run pytest tests/test_contract_audit_regressions.py::test_tread_resolves_overloaded_function_from_arguments -vv
Observed: 1 failed; EncodingTypeError from UnsignedIntegerEncoder. The mock provider was never reached.
Control: web3.py 7.14.1 encoded the same ABI/arguments with selector 0xd4b6b5da, equal to keccak("lookup(address)")[:4].
Root cause
_find_function(abi, function_name) stops at the first matching name. It has no argument-count/encodability/signature disambiguation, and all encoding/routing/decoding helpers use that same name-only lookup.
User impact
Applications cannot reliably call overloaded functions through any Python contract namespace. ABI order determines behavior, and overloads with different shieldedness or outputs can additionally route or decode incorrectly.
Duplicate-check evidence
I checked all 17 issues, 277 PRs, 42 discussion comments, 33 review comments, current open-PR file lists, and the affected file history. I found no report or patch for Python overload resolution. PR #36 introduced the shared name-only lookup but did not discuss overloads.
Suggested fix direction
Resolve one ABI entry from both function name and supplied arguments, and reuse that exact resolved entry for shielded detection, selector construction, argument encoding, and output decoding. A signature-qualified escape hatch would handle ambiguous encodable overloads. Add transparent, shielded-routing, and distinct-output overload regressions.
Summary
The Python contract wrappers resolve ABI functions by name only. When an ABI contains overloads,
_find_function()always returns the first same-name entry, regardless of the supplied arguments. This can make valid overloads unusable and can also select the wrong selector, smart-routing decision, or output schema.Affected commit and files
Commit:
d99e46cdc2d7ec8c049452ba72127f0540582f53clients/py/src/seismic_web3/contract/abi.py#L150-L166clients/py/src/seismic_web3/contract/abi.py#L169-L220clients/py/src/seismic_web3/contract/shielded.pyExpected behavior
Given a valid ABI containing
lookup(uint256)andlookup(address), callingcontract.tread.lookup(address_value)should selectlookup(address), encode with the address ABI encoder, and use thelookup(address)selector. This is also how the pinned web3.py dependency resolves the same ABI and arguments.Actual behavior
The first ABI entry is always selected. If
lookup(uint256)appears first, the valid address call fails before RPC dispatch:eth_abi.exceptions.EncodingTypeError: Value ... cannot be encoded by UnsignedIntegerEncoderReversing the ABI order merely changes which overload is unusable.
Minimal local regression
Test command and observed output
Observed:
1 failed;EncodingTypeErrorfromUnsignedIntegerEncoder. The mock provider was never reached.Control: web3.py 7.14.1 encoded the same ABI/arguments with selector
0xd4b6b5da, equal tokeccak("lookup(address)")[:4].Root cause
_find_function(abi, function_name)stops at the first matching name. It has no argument-count/encodability/signature disambiguation, and all encoding/routing/decoding helpers use that same name-only lookup.User impact
Applications cannot reliably call overloaded functions through any Python contract namespace. ABI order determines behavior, and overloads with different shieldedness or outputs can additionally route or decode incorrectly.
Duplicate-check evidence
I checked all 17 issues, 277 PRs, 42 discussion comments, 33 review comments, current open-PR file lists, and the affected file history. I found no report or patch for Python overload resolution. PR #36 introduced the shared name-only lookup but did not discuss overloads.
Suggested fix direction
Resolve one ABI entry from both function name and supplied arguments, and reuse that exact resolved entry for shielded detection, selector construction, argument encoding, and output decoding. A signature-qualified escape hatch would handle ambiguous encodable overloads. Add transparent, shielded-routing, and distinct-output overload regressions.