Skip to content

Commit 0302d37

Browse files
Stuart Reedrolo
andauthored
Fix contract function name collision (#3147)
* Added test to demonstrate error when w3 is used as a function name in an abi. * Use function argument w3 variable rather than self member in ContractCaller incase it has been replaced. * Corrected import order. * Add integration test with contract containing w3 variable * Add async test * Fix async contract init * Add test for sync version * Add test for async contract call * Use existing fixture for abi --------- Co-authored-by: Rolo <[email protected]>
1 parent 9c76a18 commit 0302d37

File tree

8 files changed

+154
-6
lines changed

8 files changed

+154
-6
lines changed

newsfragments/3147.bugfix.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix collision of ``w3`` variable when initializing contract with function of the same name

tests/core/contracts/conftest.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@
2828
from web3._utils.contract_sources.contract_data.fallback_function_contract import (
2929
FALLBACK_FUNCTION_CONTRACT_DATA,
3030
)
31+
from web3._utils.contract_sources.contract_data.function_name_tester_contract import (
32+
FUNCTION_NAME_TESTER_CONTRACT_ABI,
33+
FUNCTION_NAME_TESTER_CONTRACT_DATA,
34+
)
3135
from web3._utils.contract_sources.contract_data.math_contract import (
3236
MATH_CONTRACT_ABI,
3337
MATH_CONTRACT_BYTECODE,
@@ -55,6 +59,24 @@
5559
TUPLE_CONTRACT_DATA,
5660
)
5761

62+
# --- function name tester contract --- #
63+
64+
65+
@pytest.fixture(scope="session")
66+
def function_name_tester_contract_abi():
67+
return FUNCTION_NAME_TESTER_CONTRACT_ABI
68+
69+
70+
@pytest.fixture
71+
def function_name_tester_contract(w3, address_conversion_func):
72+
function_name_tester_contract_factory = w3.eth.contract(
73+
**FUNCTION_NAME_TESTER_CONTRACT_DATA
74+
)
75+
return deploy(w3, function_name_tester_contract_factory, address_conversion_func)
76+
77+
78+
# --- math contract --- #
79+
5880

5981
@pytest.fixture(scope="session")
6082
def math_contract_bytecode():
@@ -81,6 +103,9 @@ def math_contract(w3, math_contract_factory, address_conversion_func):
81103
return deploy(w3, math_contract_factory, address_conversion_func)
82104

83105

106+
# --- constructor contracts --- #
107+
108+
84109
@pytest.fixture
85110
def simple_constructor_contract_factory(w3):
86111
return w3.eth.contract(**SIMPLE_CONSTRUCTOR_CONTRACT_DATA)
@@ -113,6 +138,9 @@ def contract_with_constructor_address(
113138
)
114139

115140

141+
# --- address reflector contract --- #
142+
143+
116144
@pytest.fixture
117145
def address_reflector_contract(w3, address_conversion_func):
118146
address_reflector_contract_factory = w3.eth.contract(
@@ -121,6 +149,9 @@ def address_reflector_contract(w3, address_conversion_func):
121149
return deploy(w3, address_reflector_contract_factory, address_conversion_func)
122150

123151

152+
# --- string contract --- #
153+
154+
124155
@pytest.fixture(scope="session")
125156
def string_contract_data():
126157
return STRING_CONTRACT_DATA
@@ -153,6 +184,9 @@ def non_strict_string_contract(
153184
)
154185

155186

187+
# --- emitter contract --- #
188+
189+
156190
@pytest.fixture
157191
def non_strict_emitter(
158192
w3_non_strict_abi,
@@ -180,6 +214,9 @@ def non_strict_emitter(
180214
return emitter_contract
181215

182216

217+
# --- event contract --- #
218+
219+
183220
@pytest.fixture
184221
def event_contract(
185222
w3,
@@ -223,6 +260,9 @@ def indexed_event_contract(
223260
return indexed_event_contract
224261

225262

263+
# --- arrays contract --- #
264+
265+
226266
# bytes_32 = [keccak('0'), keccak('1')]
227267
BYTES32_ARRAY = [
228268
b"\x04HR\xb2\xa6p\xad\xe5@~x\xfb(c\xc5\x1d\xe9\xfc\xb9eB\xa0q\x86\xfe:\xed\xa6\xbb\x8a\x11m", # noqa: E501
@@ -255,12 +295,18 @@ def non_strict_arrays_contract(w3_non_strict_abi, address_conversion_func):
255295
)
256296

257297

298+
# --- payable tester contract --- #
299+
300+
258301
@pytest.fixture
259302
def payable_tester_contract(w3, address_conversion_func):
260303
payable_tester_contract_factory = w3.eth.contract(**PAYABLE_TESTER_CONTRACT_DATA)
261304
return deploy(w3, payable_tester_contract_factory, address_conversion_func)
262305

263306

307+
# --- fixed reflector contract --- #
308+
309+
264310
# no matter the function selector, this will return back the 32 bytes of data supplied
265311
FIXED_REFLECTOR_CONTRACT_BYTECODE = "0x610011566020600460003760206000f3005b61000461001103610004600039610004610011036000f3" # noqa: E501
266312
# reference source used to generate it:
@@ -306,6 +352,9 @@ def fixed_reflector_contract(w3, address_conversion_func):
306352
return deploy(w3, fixed_reflector_contract_factory, address_conversion_func)
307353

308354

355+
# --- test data and functions contracts --- #
356+
357+
309358
@pytest.fixture
310359
def fallback_function_contract(w3, address_conversion_func):
311360
fallback_function_contract_factory = w3.eth.contract(
@@ -387,6 +436,9 @@ def some_address(address_conversion_func):
387436
return address_conversion_func("0x5B2063246F2191f18F2675ceDB8b28102e957458")
388437

389438

439+
# --- invoke contract --- #
440+
441+
390442
def invoke_contract(
391443
api_call_desig="call",
392444
contract=None,
@@ -444,6 +496,16 @@ async def async_math_contract(
444496
)
445497

446498

499+
@pytest_asyncio.fixture
500+
async def async_function_name_tester_contract(async_w3, address_conversion_func):
501+
function_name_tester_contract_factory = async_w3.eth.contract(
502+
**FUNCTION_NAME_TESTER_CONTRACT_DATA
503+
)
504+
return await async_deploy(
505+
async_w3, function_name_tester_contract_factory, address_conversion_func
506+
)
507+
508+
447509
@pytest.fixture
448510
def async_simple_constructor_contract_factory(async_w3):
449511
return async_w3.eth.contract(**SIMPLE_CONSTRUCTOR_CONTRACT_DATA)

tests/core/contracts/test_contract_call_interface.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,11 @@ def test_saved_method_call_with_multiple_arguments(
178178
assert result == 16
179179

180180

181+
def test_call_get_w3_value(function_name_tester_contract, call):
182+
result = call(contract=function_name_tester_contract, contract_function="w3")
183+
assert result is True
184+
185+
181186
def test_call_get_string_value(string_contract, call):
182187
result = call(contract=string_contract, contract_function="getValue")
183188
# eth_abi.decode() does not assume implicit utf-8
@@ -1295,6 +1300,14 @@ async def test_async_saved_method_call_with_multiple_arguments(
12951300
assert result == 16
12961301

12971302

1303+
@pytest.mark.asyncio
1304+
async def test_async_call_get_w3_value(async_function_name_tester_contract, async_call):
1305+
result = await async_call(
1306+
contract=async_function_name_tester_contract, contract_function="w3"
1307+
)
1308+
assert result is True
1309+
1310+
12981311
@pytest.mark.asyncio
12991312
async def test_async_call_get_string_value(async_string_contract, async_call):
13001313
result = await async_call(

tests/core/contracts/test_contract_class_construction.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,39 @@ def test_abi_as_json_string(w3, math_contract_abi, some_address):
4242
assert math.abi == math_contract_abi
4343

4444

45+
def test_contract_init_with_w3_function_name(
46+
w3,
47+
function_name_tester_contract_abi,
48+
function_name_tester_contract,
49+
):
50+
# test `w3` function name does not throw when creating the contract factory
51+
contract_factory = w3.eth.contract(abi=function_name_tester_contract_abi)
52+
53+
# re-instantiate the contract
54+
contract = contract_factory(function_name_tester_contract.address)
55+
56+
# assert the `w3` function returns true when called
57+
result = contract.functions.w3().call()
58+
assert result is True
59+
60+
61+
@pytest.mark.asyncio
62+
async def test_async_contract_init_with_w3_function_name(
63+
async_w3,
64+
function_name_tester_contract_abi,
65+
async_function_name_tester_contract,
66+
):
67+
# test `w3` function name does not throw when creating the contract factory
68+
contract_factory = async_w3.eth.contract(abi=function_name_tester_contract_abi)
69+
70+
# re-instantiate the contract
71+
contract = contract_factory(async_function_name_tester_contract.address)
72+
73+
# assert the `w3` function returns true when called
74+
result = await contract.functions.w3().call()
75+
assert result is True
76+
77+
4578
def test_error_to_call_non_existent_fallback(
4679
w3, math_contract_abi, math_contract_bytecode, math_contract_runtime
4780
):
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
pragma solidity ^0.8.23;
2+
3+
contract FunctionNameTesterContract {
4+
function w3() public returns (bool) {
5+
return true;
6+
}
7+
8+
// unused, this just needs to come after `w3` in the abi... so name it "z"
9+
function z() public returns (bool) {
10+
return false;
11+
}
12+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
"""
2+
Generated by `compile_contracts.py` script.
3+
Compiled with Solidity v0.8.23.
4+
"""
5+
6+
# source: web3/_utils/contract_sources/FunctionNameTesterContract.sol:FunctionNameTesterContract # noqa: E501
7+
FUNCTION_NAME_TESTER_CONTRACT_BYTECODE = "0x608060405234801561000f575f80fd5b5060d98061001c5f395ff3fe6080604052348015600e575f80fd5b50600436106030575f3560e01c8063a044c987146034578063c5d7802e14604e575b5f80fd5b603a6068565b60405160459190608c565b60405180910390f35b60546070565b604051605f9190608c565b60405180910390f35b5f6001905090565b5f90565b5f8115159050919050565b6086816074565b82525050565b5f602082019050609d5f830184607f565b9291505056fea264697066735822122056b76f22006829335981c36eca76f8aa0c6cf66d23990263a18b17fa27ab3db064736f6c63430008170033" # noqa: E501
8+
FUNCTION_NAME_TESTER_CONTRACT_RUNTIME = "0x6080604052348015600e575f80fd5b50600436106030575f3560e01c8063a044c987146034578063c5d7802e14604e575b5f80fd5b603a6068565b60405160459190608c565b60405180910390f35b60546070565b604051605f9190608c565b60405180910390f35b5f6001905090565b5f90565b5f8115159050919050565b6086816074565b82525050565b5f602082019050609d5f830184607f565b9291505056fea264697066735822122056b76f22006829335981c36eca76f8aa0c6cf66d23990263a18b17fa27ab3db064736f6c63430008170033" # noqa: E501
9+
FUNCTION_NAME_TESTER_CONTRACT_ABI = [
10+
{
11+
"inputs": [],
12+
"name": "w3",
13+
"outputs": [{"internalType": "bool", "name": "", "type": "bool"}],
14+
"stateMutability": "nonpayable",
15+
"type": "function",
16+
},
17+
{
18+
"inputs": [],
19+
"name": "z",
20+
"outputs": [{"internalType": "bool", "name": "", "type": "bool"}],
21+
"stateMutability": "nonpayable",
22+
"type": "function",
23+
},
24+
]
25+
FUNCTION_NAME_TESTER_CONTRACT_DATA = {
26+
"bytecode": FUNCTION_NAME_TESTER_CONTRACT_BYTECODE,
27+
"bytecode_runtime": FUNCTION_NAME_TESTER_CONTRACT_RUNTIME,
28+
"abi": FUNCTION_NAME_TESTER_CONTRACT_ABI,
29+
}

web3/contract/async_contract.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -575,7 +575,7 @@ def __init__(
575575
for func in self._functions:
576576
fn = AsyncContractFunction.factory(
577577
func["name"],
578-
w3=self.w3,
578+
w3=w3,
579579
contract_abi=self.abi,
580580
address=self.address,
581581
function_identifier=func["name"],
@@ -585,9 +585,7 @@ def __init__(
585585
# TODO: The no_extra_call method gets around the fact that we can't call
586586
# the full async method from within a class's __init__ method. We need
587587
# to see if there's a way to account for all desired elif cases.
588-
block_id = parse_block_identifier_no_extra_call(
589-
self.w3, block_identifier
590-
)
588+
block_id = parse_block_identifier_no_extra_call(w3, block_identifier)
591589
caller_method = partial(
592590
self.call_function,
593591
fn,

web3/contract/contract.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -577,14 +577,14 @@ def __init__(
577577
for func in self._functions:
578578
fn = ContractFunction.factory(
579579
func["name"],
580-
w3=self.w3,
580+
w3=w3,
581581
contract_abi=self.abi,
582582
address=self.address,
583583
function_identifier=func["name"],
584584
decode_tuples=decode_tuples,
585585
)
586586

587-
block_id = parse_block_identifier(self.w3, block_identifier)
587+
block_id = parse_block_identifier(w3, block_identifier)
588588
caller_method = partial(
589589
self.call_function,
590590
fn,

0 commit comments

Comments
 (0)