Skip to content

Commit

Permalink
reconcile changes with rebased async contract
Browse files Browse the repository at this point in the history
  • Loading branch information
fselmo committed May 25, 2022
1 parent 32e997f commit 72701ba
Show file tree
Hide file tree
Showing 5 changed files with 110 additions and 88 deletions.
8 changes: 8 additions & 0 deletions tests/integration/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,14 @@ def offchain_lookup_contract_factory(w3):
return contract_factory


@pytest.fixture(scope="module")
def async_offchain_lookup_contract_factory(async_w3):
contract_factory = async_w3.eth.contract(
abi=OFFCHAIN_LOOKUP_ABI, bytecode=OFFCHAIN_LOOKUP_BYTECODE
)
return contract_factory


@pytest.fixture(scope="module")
def event_loop(request):
loop = asyncio.get_event_loop_policy().new_event_loop()
Expand Down
7 changes: 7 additions & 0 deletions tests/integration/go_ethereum/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -241,3 +241,10 @@ def revert_contract(revert_contract_factory, geth_fixture_data):
@pytest.fixture(scope="module")
def offchain_lookup_contract(offchain_lookup_contract_factory, geth_fixture_data):
return offchain_lookup_contract_factory(address=geth_fixture_data['offchain_lookup_address'])


@pytest.fixture(scope="module")
def async_offchain_lookup_contract(async_offchain_lookup_contract_factory, geth_fixture_data):
return async_offchain_lookup_contract_factory(
address=geth_fixture_data['offchain_lookup_address']
)
17 changes: 8 additions & 9 deletions web3/_utils/async_transactions.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,30 +13,29 @@
from eth_typing import (
URI,
)
from eth_utils.toolz import (
curry,
merge,
)

from web3._utils.request import (
async_get_json_from_client_response,
async_get_response_from_get_request,
async_get_response_from_post_request,
)
from web3._utils.type_conversion_utils import (
from web3._utils.type_conversion import (
to_bytes_if_hex,
to_hex_if_bytes,
)
from web3.exceptions import (
ValidationError,
)
from eth_utils.toolz import (
curry,
merge,
)

from web3._utils.utility_methods import (
any_in_dict,
)
from web3.constants import (
DYNAMIC_FEE_TXN_PARAMS,
)
from web3.exceptions import (
ValidationError,
)
from web3.types import (
BlockIdentifier,
TxParams,
Expand Down
127 changes: 63 additions & 64 deletions web3/_utils/module_testing/eth_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -760,91 +760,97 @@ async def test_eth_call_revert_without_msg(
async def test_eth_call_offchain_lookup(
self,
async_w3: "Web3",
offchain_lookup_contract: "Contract",
async_offchain_lookup_contract: "Contract",
unlocked_account: ChecksumAddress,
monkeypatch: "MonkeyPatch",
) -> None:
normalized_contract_address = to_hex_if_bytes(offchain_lookup_contract.address).lower()
normalized_contract_address = to_hex_if_bytes(
async_offchain_lookup_contract.address
).lower()

async_mock_offchain_lookup_request_response(
monkeypatch,
mocked_request_url=f'https://web3.py/gateway/{normalized_contract_address}/{OFFCHAIN_LOOKUP_TEST_DATA}.json', # noqa: E501
mocked_json_data=WEB3PY_AS_HEXBYTES,
)
# TODO: change to contract call when async Contract is supported
tx = {
'to': offchain_lookup_contract.address,
'data': '0x6337ed58000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001474657374206f6666636861696e206c6f6f6b7570000000000000000000000000' # noqa: E501
}
response = await async_w3.eth.call(tx) # type: ignore
response_as_bytes = async_w3.codec.decode_abi(['bytes'], response)[0]
decoded_as_string = async_w3.codec.decode_abi(['string'], response_as_bytes)[0]
assert decoded_as_string == 'web3py'
response_caller = await async_offchain_lookup_contract.caller().testOffchainLookup( # noqa: E501 type: ignore
OFFCHAIN_LOOKUP_TEST_DATA
)
response_function_call = await async_offchain_lookup_contract.functions.testOffchainLookup( # noqa: E501 type: ignore
OFFCHAIN_LOOKUP_TEST_DATA
).call()
assert async_w3.codec.decode_abi(['string'], response_caller)[0] == 'web3py'
assert async_w3.codec.decode_abi(['string'], response_function_call)[0] == 'web3py'

@pytest.mark.asyncio
async def test_eth_call_offchain_lookup_raises_when_ccip_read_is_disabled(
self, async_w3: "Web3", offchain_lookup_contract: "Contract",
async def test_eth_call_offchain_lookup_raises_when_ccip_read_is_disabled_yy(
self, async_w3: "Web3", async_offchain_lookup_contract: "Contract",
) -> None:
# TODO: change to contract call when async Contract is supported
tx = {
'to': offchain_lookup_contract.address,
'data': '0x6337ed58000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001474657374206f6666636861696e206c6f6f6b7570000000000000000000000000' # noqa: E501
}
with pytest.raises(OffchainLookup):
await async_w3.eth.call(tx, ccip_read_enabled=False) # type: ignore
# test AsyncContractCaller
await async_offchain_lookup_contract.caller(ccip_read_enabled=False).testOffchainLookup( # noqa: E501 type: ignore
OFFCHAIN_LOOKUP_TEST_DATA
)
with pytest.raises(OffchainLookup):
# test AsyncContractFunction call
await async_offchain_lookup_contract.functions.testOffchainLookup(
OFFCHAIN_LOOKUP_TEST_DATA
).call(ccip_read_enabled=False)

@pytest.mark.asyncio
@pytest.mark.parametrize("max_redirects", range(-4, 4))
async def test_eth_call_offchain_lookup_raises_if_max_redirects_is_less_than_4(
self,
async_w3: "Web3",
offchain_lookup_contract: "Contract",
async_offchain_lookup_contract: "Contract",
max_redirects: int,
) -> None:
default_max_redirects = async_w3.provider.ccip_read_max_redirects
tx = {
'to': offchain_lookup_contract.address,
'data': '0x6337ed58000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001474657374206f6666636861696e206c6f6f6b7570000000000000000000000000' # noqa: E501
}

async_w3.provider.ccip_read_max_redirects = max_redirects
with pytest.raises(ValueError, match="at least 4"):
await async_w3.eth.call(tx) # type: ignore
await async_offchain_lookup_contract.caller().testOffchainLookup(
OFFCHAIN_LOOKUP_TEST_DATA
)

async_w3.provider.ccip_read_max_redirects = default_max_redirects # cleanup

@pytest.mark.asyncio
async def test_eth_call_offchain_lookup_raises_for_improperly_formatted_rest_request_response(
self,
async_w3: "Web3",
offchain_lookup_contract: "Contract",
async_offchain_lookup_contract: "Contract",
unlocked_account: ChecksumAddress,
monkeypatch: "MonkeyPatch",
) -> None:
normalized_contract_address = to_hex_if_bytes(offchain_lookup_contract.address).lower()
normalized_contract_address = to_hex_if_bytes(
async_offchain_lookup_contract.address
).lower()

async_mock_offchain_lookup_request_response(
monkeypatch,
mocked_request_url=f'https://web3.py/gateway/{normalized_contract_address}/{OFFCHAIN_LOOKUP_TEST_DATA}.json', # noqa: E501
mocked_json_data=WEB3PY_AS_HEXBYTES,
json_data_field='not_data',
)
# TODO: change to contract call when async Contract is supported
tx = {
'to': offchain_lookup_contract.address,
'data': '0x6337ed58000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001474657374206f6666636861696e206c6f6f6b7570000000000000000000000000' # noqa: E501
}
with pytest.raises(ValidationError, match="missing 'data' field"):
await async_w3.eth.call(tx) # type: ignore
await async_offchain_lookup_contract.caller().testOffchainLookup(
OFFCHAIN_LOOKUP_TEST_DATA
)

@pytest.mark.asyncio
@pytest.mark.parametrize('status_code_non_4xx_error', [100, 300, 500, 600])
async def test_eth_call_offchain_lookup_tries_next_url_for_non_4xx_error_status_and_tests_POST(
self,
async_w3: "Web3",
offchain_lookup_contract: "Contract",
async_offchain_lookup_contract: "Contract",
unlocked_account: ChecksumAddress,
monkeypatch: "MonkeyPatch",
status_code_non_4xx_error: int,
) -> None:
normalized_contract_address = to_hex_if_bytes(offchain_lookup_contract.address).lower()
normalized_contract_address = to_hex_if_bytes(
async_offchain_lookup_contract.address
).lower()

# The next url in our test contract doesn't contain '{data}', triggering the POST request
# logic. The idea here is to return a bad status for the first url (GET) and a success
Expand All @@ -865,73 +871,66 @@ async def test_eth_call_offchain_lookup_tries_next_url_for_non_4xx_error_status_
sender=normalized_contract_address,
calldata=OFFCHAIN_LOOKUP_TEST_DATA,
)
# TODO: change to contract call when async Contract is supported
tx = {
'to': offchain_lookup_contract.address,
'data': '0x6337ed58000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001474657374206f6666636861696e206c6f6f6b7570000000000000000000000000' # noqa: E501
}
response = await async_w3.eth.call(tx) # type: ignore
response_as_bytes = async_w3.codec.decode_abi(['bytes'], response)[0]
decoded_as_string = async_w3.codec.decode_abi(['string'], response_as_bytes)[0]
assert decoded_as_string == 'web3py'
response = await async_offchain_lookup_contract.caller().testOffchainLookup(
OFFCHAIN_LOOKUP_TEST_DATA
)
assert async_w3.codec.decode_abi(['string'], response)[0] == 'web3py'

@pytest.mark.asyncio
@pytest.mark.parametrize('status_code_4xx_error', [400, 410, 450, 499])
async def test_eth_call_offchain_lookup_calls_raise_for_status_for_4xx_status_code(
self,
async_w3: "Web3",
offchain_lookup_contract: "Contract",
async_offchain_lookup_contract: "Contract",
unlocked_account: ChecksumAddress,
monkeypatch: "MonkeyPatch",
status_code_4xx_error: int,
) -> None:
normalized_contract_address = to_hex_if_bytes(offchain_lookup_contract.address).lower()
normalized_contract_address = to_hex_if_bytes(
async_offchain_lookup_contract.address
).lower()

async_mock_offchain_lookup_request_response(
monkeypatch,
mocked_request_url=f'https://web3.py/gateway/{normalized_contract_address}/{OFFCHAIN_LOOKUP_TEST_DATA}.json', # noqa: E501
mocked_status_code=status_code_4xx_error,
mocked_json_data=WEB3PY_AS_HEXBYTES,
)
with pytest.raises(Exception, match="called raise_for_status\\(\\)"):
# TODO: change to contract call when async Contract is supported
tx = {
'to': offchain_lookup_contract.address,
'data': '0x6337ed58000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001474657374206f6666636861696e206c6f6f6b7570000000000000000000000000' # noqa: E501
}
await async_w3.eth.call(tx) # type: ignore
await async_offchain_lookup_contract.caller().testOffchainLookup(
OFFCHAIN_LOOKUP_TEST_DATA
)

@pytest.mark.asyncio
async def test_eth_call_offchain_lookup_raises_when_all_supplied_urls_fail(
self, async_w3: "Web3", offchain_lookup_contract: "Contract",
self, async_w3: "Web3", async_offchain_lookup_contract: "Contract",
) -> None:
# GET and POST requests should fail since responses are not mocked
with pytest.raises(
MultipleFailedRequests, match="Offchain lookup failed for supplied urls"
):
# TODO: change to contract call when async Contract is supported
tx = {
'to': offchain_lookup_contract.address,
'data': '0x6337ed58000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001474657374206f6666636861696e206c6f6f6b7570000000000000000000000000' # noqa: E501
}
await async_w3.eth.call(tx) # type: ignore
await async_offchain_lookup_contract.caller().testOffchainLookup(
OFFCHAIN_LOOKUP_TEST_DATA
)

@pytest.mark.asyncio
async def test_eth_call_continuous_offchain_lookup_raises_with_too_many_requests(
self,
async_w3: "Web3",
offchain_lookup_contract: "Contract",
async_offchain_lookup_contract: "Contract",
unlocked_account: ChecksumAddress,
monkeypatch: "MonkeyPatch",
) -> None:
normalized_contract_address = to_hex_if_bytes(offchain_lookup_contract.address).lower()
normalized_contract_address = to_hex_if_bytes(
async_offchain_lookup_contract.address
).lower()

async_mock_offchain_lookup_request_response(
monkeypatch,
mocked_request_url=f'https://web3.py/gateway/{normalized_contract_address}/0x.json',
)
with pytest.raises(TooManyRequests, match="Too many CCIP read redirects"):
# TODO: change to contract call when async Contract is supported
tx = {'to': offchain_lookup_contract.address, 'data': '0x09a3c01b'}
await async_w3.eth.call(tx) # type: ignore
await async_offchain_lookup_contract.caller().continuousOffchainLookup() # noqa: E501 type: ignore

@pytest.mark.asyncio
async def test_async_eth_hashrate(
Expand Down
39 changes: 24 additions & 15 deletions web3/contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -1404,6 +1404,7 @@ async def call(
self.contract_abi,
self.abi,
state_override,
ccip_read_enabled,
*self.args,
**self.kwargs
)
Expand Down Expand Up @@ -1878,11 +1879,16 @@ def call_function(
*args: Any,
transaction: Optional[TxParams] = None,
block_identifier: BlockIdentifier = 'latest',
ccip_read_enabled: bool = True,
**kwargs: Any
) -> Any:
if transaction is None:
transaction = {}
return fn(*args, **kwargs).call(transaction, block_identifier)
return fn(*args, **kwargs).call(
transaction=transaction,
block_identifier=block_identifier,
ccip_read_enabled=ccip_read_enabled,
)


class ContractCaller(BaseContractCaller):
Expand All @@ -1896,19 +1902,20 @@ def __init__(
ccip_read_enabled: bool = True,
) -> None:
super().__init__(
abi,
w3,
address,
transaction,
block_identifier,
ccip_read_enabled,
ContractFunction,
abi=abi,
w3=w3,
address=address,
transaction=transaction,
block_identifier=block_identifier,
ccip_read_enabled=ccip_read_enabled,
contract_function_class=ContractFunction,
)

def __call__(
self,
transaction: Optional[TxParams] = None,
block_identifier: BlockIdentifier = 'latest',
state_override: Optional[CallOverride] = None,
ccip_read_enabled: bool = True,
) -> 'ContractCaller':
if transaction is None:
Expand All @@ -1935,13 +1942,13 @@ def __init__(
ccip_read_enabled: bool = True,
) -> None:
super().__init__(
abi,
w3,
address,
transaction,
block_identifier,
ccip_read_enabled,
AsyncContractFunction
abi=abi,
w3=w3,
address=address,
transaction=transaction,
block_identifier=block_identifier,
ccip_read_enabled=ccip_read_enabled,
contract_function_class=AsyncContractFunction,
)

def __call__(
Expand Down Expand Up @@ -2062,6 +2069,7 @@ async def async_call_contract_function(
contract_abi: Optional[ABI] = None,
fn_abi: Optional[ABIFunction] = None,
state_override: Optional[CallOverride] = None,
ccip_read_enabled: bool = True,
*args: Any,
**kwargs: Any) -> Any:
"""
Expand All @@ -2083,6 +2091,7 @@ async def async_call_contract_function(
call_transaction,
block_identifier=block_id,
state_override=state_override,
ccip_read_enabled=ccip_read_enabled,
)

if fn_abi is None:
Expand Down

0 comments on commit 72701ba

Please sign in to comment.