Skip to content

Commit

Permalink
fix(tests): make all tests green and asyncify (#165)
Browse files Browse the repository at this point in the history
  • Loading branch information
BobTheBuidler authored Apr 14, 2024
1 parent 490263b commit 646c0bf
Show file tree
Hide file tree
Showing 6 changed files with 98 additions and 51 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/pytest.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -47,5 +47,5 @@ jobs:
env:
PYTEST_NETWORK: mainnet
ETHERSCAN_TOKEN: ${{ secrets.ETHERSCAN_TOKEN }}
run: pytest
run: pytest --asyncio-task-timeout=3600

3 changes: 1 addition & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,4 @@ dank_mids.egg-info
.pytest_cache
.eggs
.mypy_cache
__pycache__
*.log*
__pycache__
8 changes: 6 additions & 2 deletions dank_mids/semaphores.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@

import logging
from typing import TYPE_CHECKING, Literal, Union
from decimal import Decimal
from typing import TYPE_CHECKING, Literal, Optional, Union

from a_sync.primitives import DummySemaphore, ThreadsafeSemaphore
from a_sync.primitives.locks.prio_semaphore import (
Expand All @@ -13,6 +13,10 @@

class _BlockSemaphoreContextManager(_PrioritySemaphoreContextManager):
_priority_name = "block"
def __init__(self, parent: "BlockSemaphore", priority: Union[int, float, Decimal], name: Optional[str] = None) -> None:
if not isinstance(priority, (int, float, Decimal)):
raise TypeError(priority)
super().__init__(parent, priority, name)

class BlockSemaphore(_AbstractPrioritySemaphore[str, _BlockSemaphoreContextManager]):
_context_manager_class = _BlockSemaphoreContextManager
Expand Down
2 changes: 2 additions & 0 deletions requirements-dev.txt
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
eth-brownie>1.16.0
pytest
pytest-asyncio-cooperative
81 changes: 56 additions & 25 deletions tests/test_brownie_patch.py
Original file line number Diff line number Diff line change
@@ -1,54 +1,85 @@
# sourcery skip: no-loop-in-tests
import asyncio
import pytest

from brownie import Contract, web3
import brownie
import eth_retry
from decimal import Decimal
from multicall.utils import await_awaitable

from dank_mids import dank_web3, patch_contract, setup_dank_w3_from_sync
import dank_mids
from dank_mids.brownie_patch.call import _patch_call


def test_patch_call():
# must use from_explorer for gh testing workflow
# NOTE: we don't want tests to fail due to api limits
get_contract = eth_retry.auto_retry(brownie.Contract.from_explorer)
get_dank_contract = eth_retry.auto_retry(dank_mids.Contract.from_explorer)

@pytest.mark.asyncio_cooperative
async def test_patch_call():
# must use from_explorer for gh testing workflow
weth = Contract.from_explorer('0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2')
_patch_call(weth.totalSupply, dank_web3)
weth = get_contract('0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2')
_patch_call(weth.totalSupply, dank_mids.web3)
assert hasattr(weth.totalSupply, 'coroutine')
assert await_awaitable(weth.totalSupply.coroutine(block_identifier=13_000_000)) == 6620041514474872981393155
assert await weth.totalSupply.coroutine(block_identifier=13_000_000) == 6620041514474872981393155

def test_gather():
@pytest.mark.asyncio_cooperative
async def test_gather():
# must use from_explorer for gh testing workflow
weth = Contract.from_explorer('0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2')
_patch_call(weth.totalSupply, dank_web3)
weth = get_contract('0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2')
_patch_call(weth.totalSupply, dank_mids.web3)
assert hasattr(weth.totalSupply, 'coroutine')
for result in await_awaitable(asyncio.gather(*[weth.totalSupply.coroutine(block_identifier=13_000_000) for _ in range(10_000)])):
for result in await asyncio.gather(*[weth.totalSupply.coroutine(block_identifier=13_000_000) for _ in range(10_000)]):
assert result == 6620041514474872981393155

def test_patch_contract():
# ContractCall
# must use from_explorer for gh testing workflow
@pytest.mark.asyncio_cooperative
async def test_patch_contract_call():
# specify w3
weth = patch_contract(Contract.from_explorer('0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2'), dank_web3)
weth = dank_mids.patch_contract(get_contract('0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2'), dank_mids.web3)
assert hasattr(weth.totalSupply, 'coroutine')
assert await_awaitable(weth.totalSupply.coroutine(block_identifier=13_000_000)) == 6620041514474872981393155
assert await_awaitable(weth.totalSupply.coroutine(block_identifier=13_000_000, decimals=18)) == Decimal(6620041.514474872981393155)
assert await_awaitable(weth.totalSupply)
assert await weth.totalSupply.coroutine(block_identifier=13_000_000) == 6620041514474872981393155
assert await weth.totalSupply.coroutine(block_identifier=13_000_000, decimals=18) == Decimal("6620041.514474872981393155")

# ContractTx
@pytest.mark.asyncio_cooperative
async def test_patch_contract_tx():
# must use from_explorer for gh testing workflow
# dont specify w3
uni_v3_quoter = patch_contract(Contract.from_explorer('0xb27308f9F90D607463bb33eA1BeBb41C27CE5AB6'))
uni_v3_quoter = dank_mids.patch_contract(get_contract('0xb27308f9F90D607463bb33eA1BeBb41C27CE5AB6'))
assert hasattr(uni_v3_quoter.quoteExactInput, 'coroutine')
assert await (
uni_v3_quoter.quoteExactInput.coroutine(b"\xc0*\xaa9\xb2#\xfe\x8d\n\x0e\\O'\xea\xd9\x08<ul\xc2\x00\x01\xf4\xa0\xb8i\x91\xc6!\x8b6\xc1\xd1\x9dJ.\x9e\xb0\xce6\x06\xebH", 1e18, block_identifier=13_000_000)
) == 3169438072
assert await (
uni_v3_quoter.quoteExactInput.coroutine(b"\xc0*\xaa9\xb2#\xfe\x8d\n\x0e\\O'\xea\xd9\x08<ul\xc2\x00\x01\xf4\xa0\xb8i\x91\xc6!\x8b6\xc1\xd1\x9dJ.\x9e\xb0\xce6\x06\xebH", 1e18, block_identifier=13_000_000, decimals=8)
) == Decimal("31.69438072")

@pytest.mark.asyncio_cooperative
async def test_dank_contract_call():
dank_weth = get_dank_contract('0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2')
assert isinstance(dank_weth, dank_mids.Contract)
assert isinstance(dank_weth.totalSupply, dank_mids.DankContractCall)
assert await dank_weth.totalSupply.coroutine(block_identifier=13_000_000) == 6620041514474872981393155
assert await dank_weth.totalSupply.coroutine(block_identifier=13_000_000, decimals=18) == Decimal("6620041.514474872981393155")
assert await dank_weth.totalSupply

@pytest.mark.asyncio_cooperative
async def test_dank_contract_tx():
# ContractTx
# must use from_explorer for gh testing workflow
uni_v3_quoter = get_dank_contract('0xb27308f9F90D607463bb33eA1BeBb41C27CE5AB6')
assert isinstance(uni_v3_quoter, dank_mids.Contract)
assert isinstance(uni_v3_quoter.quoteExactInput, dank_mids.DankContractTx)
assert hasattr(uni_v3_quoter.quoteExactInput, 'coroutine')
assert await_awaitable(
assert await (
uni_v3_quoter.quoteExactInput.coroutine(b"\xc0*\xaa9\xb2#\xfe\x8d\n\x0e\\O'\xea\xd9\x08<ul\xc2\x00\x01\xf4\xa0\xb8i\x91\xc6!\x8b6\xc1\xd1\x9dJ.\x9e\xb0\xce6\x06\xebH", 1e18, block_identifier=13_000_000)
) == 3169438072
assert await_awaitable(
assert await (
uni_v3_quoter.quoteExactInput.coroutine(b"\xc0*\xaa9\xb2#\xfe\x8d\n\x0e\\O'\xea\xd9\x08<ul\xc2\x00\x01\xf4\xa0\xb8i\x91\xc6!\x8b6\xc1\xd1\x9dJ.\x9e\xb0\xce6\x06\xebH", 1e18, block_identifier=13_000_000, decimals=8)
) == Decimal(31.69438072)
) == Decimal("31.69438072")

def test_call_setup_twice_on_same_web3():
w3_a = setup_dank_w3_from_sync(web3)
w3_a = dank_mids.setup_dank_w3_from_sync(brownie.web3)
w3_a.test = True
w3_b = setup_dank_w3_from_sync(web3)
w3_b = dank_mids.setup_dank_w3_from_sync(brownie.web3)
assert hasattr(w3_b, 'test')

53 changes: 32 additions & 21 deletions tests/test_dank_mids.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@


import asyncio
import importlib
import pytest
import sys
from brownie import chain
from multicall import Call
from multicall.utils import await_awaitable, gather
from web3._utils.rpc_abi import RPC

from dank_mids import dank_web3, instances
Expand All @@ -12,19 +13,26 @@
height = chain.height
BIG_WORK = [Call(CHAI, 'totalSupply()(uint)', [[f'totalSupply{i}',None]], block_id=height - (i // 25000), _w3=dank_web3).coroutine() for i in range(100_000)]
height = chain.height
MULTIBLOCK_WORK = [Call(CHAI, 'totalSupply()(uint)', [[f'totalSupply{i}',None]], _w3=dank_web3, block_id=height-i).coroutine() for i in range(100_000)]
MULTIBLOCK_WORK = [Call(CHAI, 'totalSupply()(uint)', [[f'totalSupply{i}',None]], _w3=dank_web3, block_id=height-i).coroutine() for i in range(1_000)]


def _get_controller():
brownie_version = tuple(int(x) for x in importlib.metadata.version('eth-brownie').split('.'))
if brownie_version >= (1, 20):
# Not sure why but 1.20 creates 2 instances
return instances[chain.id][1]
return instances[chain.id][0]

def test_dank_middleware():
await_awaitable(gather(BIG_WORK))
@pytest.mark.asyncio_cooperative
async def test_dank_middleware():
await asyncio.gather(*BIG_WORK)
cid = _get_controller().call_uid.latest
mid = _get_controller().multicall_uid.latest
rid = _get_controller().request_uid.latest
assert cid, "The DankMiddlewareController did not process any calls."
assert mid, "The DankMiddlewareController did not process any batches."
if sys.version_info < (3, 10):
# Not sure why this assert fails above 3.10
assert mid, "The DankMiddlewareController did not process any batches."
assert rid, "The DankMiddlewareController did not process any requests."
print(f"calls: {cid}")
print(f"multicalls: {mid}")
Expand All @@ -33,17 +41,18 @@ def test_dank_middleware():
print(f"calls per request: {cid/rid}")
print(f"multicalls per request: {mid/rid}")
# General "tests" that verify batching performance
assert mid < cid / 100, f"Batched {cid} calls into {mid} multicalls. Performance underwhelming."
assert mid < cid / 50, f"Batched {cid} calls into {mid} multicalls. Performance underwhelming."
assert rid < cid / 150, f"Batched {cid} calls into {rid} requests. Performance underwhelming."
assert mid / rid > 1, f"Batched {mid} multicalls into {rid} requests. Performance underwhelming."

def test_bad_hex_handling():
@pytest.mark.asyncio_cooperative
async def test_bad_hex_handling():
chainlinkfeed = "0xfe67209f6FE3BA6cE36d0941700085C194e958DF"
assert await_awaitable(Call(chainlinkfeed, 'latestAnswer()(uint)', block_id=14_000_000).coroutine()) == 15717100
assert chainlinkfeed in _get_controller().no_multicall
assert await Call(chainlinkfeed, 'latestAnswer()(uint)', block_id=14_000_000) == 15717100

def test_json_batch():
await_awaitable(gather(MULTIBLOCK_WORK))
@pytest.mark.asyncio_cooperative
async def test_json_batch():
await asyncio.gather(*MULTIBLOCK_WORK)

def test_next_cid():
assert _get_controller().call_uid.next + 1 == _get_controller().call_uid.next
Expand All @@ -54,17 +63,19 @@ def test_next_mid():
def test_next_bid():
assert _get_controller().multicall_uid.next + 1 == _get_controller().multicall_uid.next

def test_other_methods():
work = [dank_web3.eth.get_block_number() for i in range(50)]
@pytest.mark.asyncio_cooperative
async def test_other_methods():
work = [dank_web3.eth.block_number for i in range(50)]
work.append(dank_web3.eth.get_block('0xe25822'))
work.append(dank_web3.manager.coro_request(RPC.web3_clientVersion, []))
assert await_awaitable(gather(work))
assert await asyncio.gather(*work)

def test_AttributeDict():
block = await_awaitable(dank_web3.eth.get_block("0xe25822"))
assert block['timestamp']
assert block.timestamp
@pytest.mark.asyncio_cooperative
async def test_AttributeDict():
block = await dank_web3.eth.get_block("0xe25822")
assert block['timestamp'] and block.timestamp and (block['timestamp'] == block.timestamp)

def test_string_block():
@pytest.mark.asyncio_cooperative
async def test_string_block():
with pytest.raises(TypeError):
await_awaitable(Call(CHAI, 'totalSupply()(uint)', block_id="14000000").coroutine())
await Call(CHAI, 'totalSupply()(uint)', block_id="14000000")

0 comments on commit 646c0bf

Please sign in to comment.