Skip to content

Commit

Permalink
Merge pull request #183 from DanielSchiavini/coverage
Browse files Browse the repository at this point in the history
chore: test coverage
  • Loading branch information
charles-cooper authored Jun 12, 2024
2 parents 2a0065d + 995b009 commit f0106eb
Show file tree
Hide file tree
Showing 24 changed files with 408 additions and 43 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/main.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ jobs:
--cov=boa
--cov-append
--cov-report term-missing:skip-covered
--cov-fail-under=70
--cov-fail-under=78
-nauto
tests/unitary/
Expand Down
2 changes: 1 addition & 1 deletion boa/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@

# turn off tracebacks if we are in repl
# https://stackoverflow.com/a/64523765
if hasattr(sys, "ps1"):
if hasattr(sys, "ps1"): # pragma: no cover
pass
# sys.tracebacklimit = 0

Expand Down
3 changes: 1 addition & 2 deletions boa/contracts/abi/abi_contract.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
from collections import defaultdict
from copy import deepcopy
from functools import cached_property
from typing import Any, Optional, Union
from warnings import warn
Expand Down Expand Up @@ -340,7 +339,7 @@ def __init__(self, name: str, abi: list[dict], filename: Optional[str] = None):

@cached_property
def abi(self):
return deepcopy(self._abi)
return self._abi

@cached_property
def functions(self):
Expand Down
2 changes: 1 addition & 1 deletion boa/contracts/base_evm_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ def __init__(
self._address = address # this can be overridden by subclasses
self.filename = filename

def stack_trace(self, computation: ComputationAPI):
def stack_trace(self, computation: ComputationAPI): # pragma: no cover
raise NotImplementedError

def handle_error(self, computation):
Expand Down
6 changes: 3 additions & 3 deletions boa/contracts/vyper/decoder_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ def __getitem__(self, subscript):
start -= start_ofst
stop -= start_ofst
return memoryview(ret[start:stop])
else:
else: # pragma: no cover
raise Exception("Must slice {self}")


Expand All @@ -65,7 +65,7 @@ def _get_length(mem, bound):
def decode_vyper_object(mem, typ):
if isinstance(typ, BytesM_T):
# TODO tag return value like `vyper_object` does
return mem[: typ.m_bits].tobytes()
return mem[: typ.m].tobytes()
if isinstance(typ, (AddressT, InterfaceT)):
return to_checksum_address(mem[12:32].tobytes())
if isinstance(typ, BoolT):
Expand Down Expand Up @@ -114,4 +114,4 @@ def decode_vyper_object(mem, typ):
ofst += n
return tuple(ret)

return f"unimplemented decoder for `{typ}`"
return f"unimplemented decoder for `{typ}`" # pragma: no cover
9 changes: 5 additions & 4 deletions boa/contracts/vyper/ir_executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
_keccak_cache = lrudict(256)


# note: This is used in the generated code for `Sha3_64` below.
def keccak256(x):
return _keccak_cache.setdefault_lambda(x, keccak)

Expand Down Expand Up @@ -102,14 +103,14 @@ def translate_label(self, label):
return f"{self.contract_name}_{self.uuid}_{label}"

def add_unique_symbol(self, symbol):
if symbol in self.unique_symbols:
if symbol in self.unique_symbols: # pragma: no cover
raise ValueError(
"duplicated symbol {symbol}, this is likely a bug in vyper!"
)
self.unique_symbols.add(symbol)

def add_label(self, labelname, executor):
if labelname in self.labels:
if labelname in self.labels: # pragma: no cover
raise ValueError("duplicated label: {labelname}")
self.labels[labelname] = executor

Expand Down Expand Up @@ -232,7 +233,7 @@ def compile(self, out=None, out_typ=None):
else:
self.builder.append(res)

def _compile(self, context):
def _compile(self, context): # pragma: no cover
raise RuntimeError("must be overridden in subclass!")

def compile_main(self, contract_path=""):
Expand Down Expand Up @@ -768,7 +769,7 @@ def compile(self, out=None, out_typ=None):
arg.compile(out=None)
else:
return arg.compile(out=out, out_typ=out_typ)
else:
else: # pragma: no cover
raise RuntimeError("loop should have broken")


Expand Down
3 changes: 0 additions & 3 deletions boa/ipython.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import boa

_ = None
_contracts = {}


Expand All @@ -23,7 +22,6 @@ def deployer(self, line, cell):
if line:
self.shell.user_ns[line] = c # ret available in user ipython locals
_contracts[line] = c # ret available in boa.ipython._contracts
_ = c # ret available at `boa.ipython._`
return c

@ipython.cell_magic
Expand All @@ -33,7 +31,6 @@ def contract(self, line, cell):
if line:
self.shell.user_ns[line] = c # ret available in user ipython locals
_contracts[line] = c # ret available in boa.ipython._contracts
_ = c # ret available at `boa.ipython._`
return c

# unsure about "vyper" vs "eval" line magic; keep both until decided
Expand Down
12 changes: 7 additions & 5 deletions boa/rpc.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

try:
import ujson as json
except ImportError:
except ImportError: # pragma: no cover
import json # type: ignore

TIMEOUT = 60 # default timeout for http requests in seconds
Expand Down Expand Up @@ -63,20 +63,22 @@ class RPC:
"""

@property
def identifier(self) -> str:
def identifier(self) -> str: # pragma: no cover
raise NotImplementedError

@property
def name(self) -> str:
def name(self) -> str: # pragma: no cover
raise NotImplementedError

def fetch_uncached(self, method, params):
return self.fetch(method, params)

def fetch(self, method: str, params: Any) -> Any:
def fetch(self, method: str, params: Any) -> Any: # pragma: no cover
raise NotImplementedError

def fetch_multi(self, payloads: list[tuple[str, Any]]) -> list[Any]:
def fetch_multi(
self, payloads: list[tuple[str, Any]]
) -> list[Any]: # pragma: no cover
raise NotImplementedError

def wait_for_tx_receipt(self, tx_hash, timeout: float, poll_latency=0.25):
Expand Down
4 changes: 2 additions & 2 deletions boa/test/strategies.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,8 +127,8 @@ def _bytes_strategy(


@_exclude_filter
def _string_strategy(min_size: int = 0, max_size: int = 64) -> SearchStrategy:
return st.text(min_size=min_size, max_size=max_size)
def _string_strategy(min_size: int = 0, max_size: int = 64, **kwargs) -> SearchStrategy:
return st.text(min_size=min_size, max_size=max_size, **kwargs)


def _get_array_length(var_str: str, length: ArrayLengthType, dynamic_len: int) -> int:
Expand Down
2 changes: 1 addition & 1 deletion boa/util/lrudict.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ def __getitem__(self, k):
return val

def __setitem__(self, k, val):
if len(self) == self.n:
if len(self) == self.n and k not in self:
del self[next(iter(self))]
super().__setitem__(k, val)

Expand Down
10 changes: 2 additions & 8 deletions boa/vm/fork.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,6 @@
import sys
from typing import Any, Type

from requests import HTTPError

try:
import ujson as json
except ImportError:
import json # type: ignore

import rlp
from eth.db.account import AccountDB, keccak
from eth.db.backends.memory import MemoryDB
Expand All @@ -18,8 +11,9 @@
from eth.vm.interrupt import MissingBytecode
from eth.vm.message import Message
from eth_utils import int_to_big_endian, to_canonical_address, to_checksum_address
from requests import HTTPError

from boa.rpc import RPC, RPCError, fixup_dict, to_bytes, to_hex, to_int
from boa.rpc import RPC, RPCError, fixup_dict, json, to_bytes, to_hex, to_int
from boa.util.lrudict import lrudict

TIMEOUT = 60 # default timeout for http requests in seconds
Expand Down
2 changes: 1 addition & 1 deletion tests/integration/fork/test_abi_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ def test_tricrypto(tricrypto):
assert tricrypto.get_virtual_price() == 1003146380129683788
assert tricrypto.gamma() == 11809167828997
assert tricrypto.fee() == 7069800
# TODO: test the overloaded functions
assert tricrypto.initial_A_gamma() == 581076037942835227425498917514114728328226821


def test_no_bytecode(get_filepath):
Expand Down
3 changes: 1 addition & 2 deletions tests/integration/network/sepolia/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
from eth_account import Account

import boa
from boa.network import NetworkEnv

PKEY = os.environ["SEPOLIA_PKEY"]
SEPOLIA_URI = os.environ["SEPOLIA_ENDPOINT"]
Expand All @@ -15,6 +14,6 @@
# run all tests with testnet
@pytest.fixture(scope="module", autouse=True)
def sepolia_env():
with boa.swap_env(NetworkEnv(SEPOLIA_URI)):
with boa.swap_env(boa.NetworkEnv(SEPOLIA_URI)):
boa.env.add_account(Account.from_key(PKEY))
yield
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import pytest
import yaml
from eth.constants import ZERO_ADDRESS

import boa
from boa import BoaError
Expand All @@ -24,7 +25,7 @@ def load_solidity_from_yaml(get_filepath):
"""

def _load(yaml_filename):
with open(get_filepath(f"fixtures/solidity_{yaml_filename}.yaml")) as f:
with open(get_filepath(f"../../fixtures/solidity_{yaml_filename}.yaml")) as f:
data = yaml.safe_load(f)
bytecode = bytes.fromhex(data["bytecode"]["object"])
address, _ = boa.env.deploy_code(bytecode=bytecode)
Expand Down Expand Up @@ -85,6 +86,7 @@ def test(_a: address) -> address:
result = abi_contract.test(sender)
assert result == sender
assert isinstance(result, Address)
assert repr(result) == f"Address('{sender}')"


def test_address_nested():
Expand Down Expand Up @@ -145,8 +147,17 @@ def test(a: uint128 = 0, b: uint128 = 0) -> uint128:


def test_bad_address():
code = """
@external
def test() -> uint256:
return 0
"""
abi_contract, _ = load_via_abi(code)
with pytest.warns(UserWarning, match=r"there is no bytecode at that address!$"):
ABIContractFactory.from_abi_dict([]).at(boa.env.eoa)
abi_contract = abi_contract.deployer.at(ZERO_ADDRESS)
with pytest.raises(BoaError) as e:
abi_contract.test()
assert "no bytecode at this address!" in str(e.value)


def test_abi_reverts():
Expand Down Expand Up @@ -190,3 +201,39 @@ def test(n: uint256) -> uint256:
abi_contract.test(0)
((error,),) = exc_info.value.args
assert re.match(r"^ +\(unknown method id .*\.0x29e99f07\)$", error)


def test_prepare_calldata():
code = """
@external
def overloaded(n: uint256 = 0) -> uint256:
return n
@external
def argumented(n: uint256) -> uint256:
return n
"""
abi_contract, _ = load_via_abi(code)
assert abi_contract.overloaded.prepare_calldata() == b"\x07\x8e\xec\xb4"
assert (
abi_contract.argumented.prepare_calldata(0) == b"\xedu\x96\x8d" + b"\x00" * 32
)
assert len(abi_contract.abi) == 3
assert abi_contract.deployer.abi == abi_contract.abi


def test_abi_invalid_components():
contract = ABIContractFactory.from_abi_dict(
[
{
"type": "function",
"name": "test",
"inputs": [{"name": "n", "type": "uint256", "components": []}],
"outputs": [],
}
]
).at(ZERO_ADDRESS)
with pytest.raises(Exception) as exc_info:
_ = contract.test.argument_types

assert "Components found in non-tuple type uint256" == str(exc_info.value)
63 changes: 63 additions & 0 deletions tests/unitary/contracts/vyper/test_vyper_contract.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import boa


def test_decode_struct():
code = """
struct Point:
x: int8
y: int8
point: Point
@external
def __init__():
self.point = Point({x: 1, y: 2})
"""
result = boa.loads(code)._storage.point.get()
assert str(result) == "Point({'x': 1, 'y': 2})"


def test_decode_tuple():
code = """
point: (int8, int8)
@external
def __init__():
self.point[0] = 1
self.point[1] = 2
"""
assert boa.loads(code)._storage.point.get() == (1, 2)


def test_decode_string_array():
code = """
point: int8[2]
@external
def __init__():
self.point[0] = 1
self.point[1] = 2
"""
assert boa.loads(code)._storage.point.get() == [1, 2]


def test_decode_bytes_m():
code = """
b: bytes2
@external
def __init__():
self.b = 0xd9b6
"""
assert boa.loads(code)._storage.b.get() == bytes.fromhex("d9b6")


def test_decode_dynarray():
code = """
point: DynArray[int8, 10]
@external
def __init__():
self.point = [1, 2]
"""
assert boa.loads(code)._storage.point.get() == [1, 2]
Loading

0 comments on commit f0106eb

Please sign in to comment.