Skip to content

Commit

Permalink
Allow ContractFunctions to be called via combomethod style (#3444)
Browse files Browse the repository at this point in the history
* Allow ContractFunction to be called via reference

* Add docs
  • Loading branch information
kclowes authored Aug 23, 2024
1 parent 6e5a554 commit 9194222
Show file tree
Hide file tree
Showing 8 changed files with 129 additions and 16 deletions.
10 changes: 10 additions & 0 deletions docs/web3.contract.rst
Original file line number Diff line number Diff line change
Expand Up @@ -679,6 +679,16 @@ If you have the function name in a variable, you might prefer this alternative:
contract_func = myContract.functions[func_to_call]
twentyone = contract_func(3).call()
You can also interact with contract functions without parentheses if the function doesn't
take any arguments. For example:

.. code-block:: python
>>> myContract.functions.return13.call()
13
>>> myContract.functions.return13().call()
13
:py:class:`ContractFunction` provides methods to interact with contract functions.
Positional and keyword arguments supplied to the contract function subclass
will be used to find the contract function by signature,
Expand Down
1 change: 1 addition & 0 deletions newsfragments/3444.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Allow user to call ContractFunctions without parentheses
29 changes: 29 additions & 0 deletions tests/core/contracts/test_contract_build_transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,20 @@ def test_build_transaction_with_contract_no_arguments(
}


def test_build_transaction_with_contract_no_arguments_no_parens(
w3, math_contract, build_transaction
):
txn = math_contract.functions.incrementCounter.build_transaction()
assert dissoc(txn, "gas") == {
"to": math_contract.address,
"data": "0x5b34b966",
"value": 0,
"maxFeePerGas": 2750000000,
"maxPriorityFeePerGas": 10**9,
"chainId": 131277322940537,
}


def test_build_transaction_with_contract_fallback_function(
w3, fallback_function_contract
):
Expand Down Expand Up @@ -309,6 +323,21 @@ async def test_async_build_transaction_with_contract_no_arguments(
}


@pytest.mark.asyncio
async def test_async_build_transaction_with_contract_no_arguments_no_parens(
async_w3, async_math_contract, async_build_transaction
):
txn = await async_math_contract.functions.incrementCounter.build_transaction()
assert dissoc(txn, "gas") == {
"to": async_math_contract.address,
"data": "0x5b34b966",
"value": 0,
"maxFeePerGas": 2750000000,
"maxPriorityFeePerGas": 10**9,
"chainId": 131277322940537,
}


@pytest.mark.asyncio
async def test_async_build_transaction_with_contract_fallback_function(
async_w3, async_fallback_function_contract
Expand Down
10 changes: 10 additions & 0 deletions tests/core/contracts/test_contract_call_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,10 @@ def test_call_with_no_arguments(math_contract, call):
assert result == 13


def test_call_no_arguments_no_parens(math_contract):
assert math_contract.functions.return13.call() == 13


def test_call_with_one_argument(math_contract, call):
result = call(contract=math_contract, contract_function="multiply7", func_args=[3])
assert result == 21
Expand Down Expand Up @@ -2287,6 +2291,12 @@ async def test_async_call_with_no_arguments(async_math_contract, call):
assert result == 13


@pytest.mark.asyncio
async def test_async_call_with_no_arguments_no_parens(async_math_contract, call):
result = await async_math_contract.functions.return13.call()
assert result == 13


@pytest.mark.asyncio
async def test_async_call_with_one_argument(async_math_contract, call):
result = await async_math_contract.functions.multiply7(3).call()
Expand Down
31 changes: 31 additions & 0 deletions tests/core/contracts/test_contract_estimate_gas.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,19 @@ def test_contract_estimate_gas(w3, math_contract, estimate_gas, transact):
assert abs(gas_estimate - gas_used) < 21000


def test_estimate_gas_can_be_called_without_parens(
w3, math_contract, estimate_gas, transact
):
gas_estimate = math_contract.functions.incrementCounter.estimate_gas()

txn_hash = transact(contract=math_contract, contract_function="incrementCounter")

txn_receipt = w3.eth.wait_for_transaction_receipt(txn_hash)
gas_used = txn_receipt.get("gasUsed")

assert abs(gas_estimate - gas_used) < 21000


def test_contract_fallback_estimate_gas(w3, fallback_function_contract):
gas_estimate = fallback_function_contract.fallback.estimate_gas()

Expand Down Expand Up @@ -197,6 +210,24 @@ async def test_async_estimate_gas_accepts_latest_block(
assert abs(gas_estimate - gas_used) < 21000


@pytest.mark.asyncio
async def test_async_estimate_gas_can_be_called_without_parens(
async_w3, async_math_contract, async_transact
):
gas_estimate = await async_math_contract.functions.counter.estimate_gas(
block_identifier="latest"
)

txn_hash = await async_transact(
contract=async_math_contract, contract_function="incrementCounter"
)

txn_receipt = await async_w3.eth.wait_for_transaction_receipt(txn_hash)
gas_used = txn_receipt.get("gasUsed")

assert abs(gas_estimate - gas_used) < 21000


@pytest.mark.asyncio
async def test_async_estimate_gas_block_identifier_unique_estimates(
async_w3, async_math_contract, async_transact
Expand Down
30 changes: 30 additions & 0 deletions tests/core/contracts/test_contract_transact_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,17 @@ def test_receive_contract_with_fallback_function(receive_function_contract, call
assert final_value == "receive"


def test_contract_can_transact_without_fn_parens(w3, math_contract, call):
initial_value = call(contract=math_contract, contract_function="counter")
txn_hash = math_contract.functions.incrementCounter.transact()
txn_receipt = w3.eth.wait_for_transaction_receipt(txn_hash)
assert txn_receipt is not None

final_value = call(contract=math_contract, contract_function="counter")

assert final_value - initial_value == 1


@pytest.mark.asyncio
async def test_async_transacting_with_contract_no_arguments(
async_w3, async_math_contract, async_transact, async_call
Expand All @@ -306,6 +317,25 @@ async def test_async_transacting_with_contract_no_arguments(
assert final_value - initial_value == 1


@pytest.mark.asyncio
async def test_async_transacting_with_contract_no_arguments_no_parens(
async_w3, async_math_contract, async_transact, async_call
):
initial_value = await async_call(
contract=async_math_contract, contract_function="counter"
)

txn_hash = await async_math_contract.functions.incrementCounter.transact()
txn_receipt = await async_w3.eth.wait_for_transaction_receipt(txn_hash)
assert txn_receipt is not None

final_value = await async_call(
contract=async_math_contract, contract_function="counter"
)

assert final_value - initial_value == 1


@pytest.mark.asyncio
async def test_async_transact_not_sending_ether_to_nonpayable_function(
async_w3, async_payable_tester_contract, async_transact, async_call
Expand Down
16 changes: 8 additions & 8 deletions web3/contract/async_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -319,8 +319,8 @@ async def call(
state_override,
ccip_read_enabled,
self.decode_tuples,
*self.args,
**self.kwargs,
*self.args or (),
**self.kwargs or {},
)

async def transact(self, transaction: Optional[TxParams] = None) -> HexBytes:
Expand All @@ -332,8 +332,8 @@ async def transact(self, transaction: Optional[TxParams] = None) -> HexBytes:
setup_transaction,
self.contract_abi,
self.abi,
*self.args,
**self.kwargs,
*self.args or (),
**self.kwargs or {},
)

async def estimate_gas(
Expand All @@ -352,8 +352,8 @@ async def estimate_gas(
self.abi,
block_identifier,
state_override,
*self.args,
**self.kwargs,
*self.args or (),
**self.kwargs or {},
)

async def build_transaction(
Expand All @@ -367,8 +367,8 @@ async def build_transaction(
built_transaction,
self.contract_abi,
self.abi,
*self.args,
**self.kwargs,
*self.args or (),
**self.kwargs or {},
)

@staticmethod
Expand Down
18 changes: 10 additions & 8 deletions web3/contract/contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -318,21 +318,22 @@ def call(
state_override,
ccip_read_enabled,
self.decode_tuples,
*self.args,
**self.kwargs,
*self.args or (),
**self.kwargs or {},
)

def transact(self, transaction: Optional[TxParams] = None) -> HexBytes:
setup_transaction = self._transact(transaction)

return transact_with_contract_function(
self.address,
self.w3,
self.abi_element_identifier,
setup_transaction,
self.contract_abi,
self.abi,
*self.args,
**self.kwargs,
*self.args or (),
**self.kwargs or {},
)

def estimate_gas(
Expand All @@ -351,21 +352,22 @@ def estimate_gas(
self.abi,
block_identifier,
state_override,
*self.args,
**self.kwargs,
*self.args or (),
**self.kwargs or {},
)

def build_transaction(self, transaction: Optional[TxParams] = None) -> TxParams:
built_transaction = self._build_transaction(transaction)

return build_transaction_for_function(
self.address,
self.w3,
self.abi_element_identifier,
built_transaction,
self.contract_abi,
self.abi,
*self.args,
**self.kwargs,
*self.args or (),
**self.kwargs or {},
)

@staticmethod
Expand Down

0 comments on commit 9194222

Please sign in to comment.