Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat[test]: implement abi_decode spec test #4095

Merged
merged 66 commits into from
Jun 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
66 commits
Select commit Hold shift + click to select a range
674bac6
wip - implement abi-decode spec
charles-cooper Jun 5, 2024
3942f49
spec entry point
charles-cooper Jun 5, 2024
36d1bb7
add ABI_Bool, bytes bound check
charles-cooper Jun 5, 2024
b68ae38
add trailing comma to TupleT repr
charles-cooper Jun 5, 2024
88a57f7
lint
charles-cooper Jun 5, 2024
5ebfcf9
abi_decode fuzz harness
charles-cooper Jun 5, 2024
9949022
wrap types in tuple as necessary
charles-cooper Jun 5, 2024
28da975
fix typos/variable names
charles-cooper Jun 5, 2024
9be35f0
bias nonzero indexes
charles-cooper Jun 5, 2024
f614a4d
fix argnames
charles-cooper Jun 5, 2024
bc59d1a
fix typos, handle special cases
charles-cooper Jun 5, 2024
fc8aea1
fix some more bugs
charles-cooper Jun 5, 2024
6079698
fix more bugs
charles-cooper Jun 5, 2024
0191693
add hypo notes for debugging
charles-cooper Jun 5, 2024
595ef43
fix strict_slice
charles-cooper Jun 5, 2024
f2c2443
fix some decoder routines
charles-cooper Jun 5, 2024
25b12ed
remove bad check
charles-cooper Jun 5, 2024
dff38e0
add padding to bytes, fix lint
charles-cooper Jun 5, 2024
529361d
fix typo
charles-cooper Jun 5, 2024
f507991
Revert "remove bad check"
charles-cooper Jun 5, 2024
65a808c
more fixes
charles-cooper Jun 5, 2024
6cfd815
checksum addresses
charles-cooper Jun 5, 2024
9d8b6a2
fix surrogate characters
charles-cooper Jun 5, 2024
c69b159
decode wrapped type
charles-cooper Jun 6, 2024
4f1c9b0
more verbose exception
charles-cooper Jun 6, 2024
16c3ac4
fix recursion - only need to decode heads when unwrapping
charles-cooper Jun 6, 2024
536e903
more verbosity
charles-cooper Jun 6, 2024
a36a262
use static size, not min size
charles-cooper Jun 6, 2024
306b094
allow oog
charles-cooper Jun 6, 2024
2b71eca
check valid text only
charles-cooper Jun 6, 2024
2e277f2
test more examples
charles-cooper Jun 6, 2024
8eceee5
smaller tuples
charles-cooper Jun 6, 2024
affd6ec
add todo for unwrap_tuple=False
charles-cooper Jun 6, 2024
5f7eb68
add splice and flip mutations
charles-cooper Jun 6, 2024
81bbd0c
clarify a comment
charles-cooper Jun 6, 2024
c6b2725
tune hypothesis settings
charles-cooper Jun 6, 2024
c375747
tune hypothesis phases
charles-cooper Jun 6, 2024
550e429
reduce leaf type bias
charles-cooper Jun 6, 2024
f3cd333
max splice
charles-cooper Jun 6, 2024
bf71c85
codegen fix for static size
charles-cooper Jun 6, 2024
e94385b
filter invalid types (darray or sarray of tuples)
charles-cooper Jun 6, 2024
03b9ef8
add examples target
charles-cooper Jun 6, 2024
5af6bb4
add verbosity, bump num examples
charles-cooper Jun 6, 2024
af7d889
fix missing arg
charles-cooper Jun 6, 2024
e61cdab
avoid more invalid types
charles-cooper Jun 6, 2024
94c9ee8
bias pointers
charles-cooper Jun 6, 2024
7f8da06
tuning
charles-cooper Jun 6, 2024
f74024c
fix stray fixture
charles-cooper Jun 6, 2024
ac706a9
note payload hex
charles-cooper Jun 6, 2024
3bd1b36
wip type stats
charles-cooper Jun 7, 2024
91b5695
more intelligent type drawing
charles-cooper Jun 8, 2024
80ec45a
tune examples settings
charles-cooper Jun 8, 2024
15e4e82
reduce targeting
charles-cooper Jun 8, 2024
ebb302b
filter out contract size limit errors
charles-cooper Jun 8, 2024
b741372
filter out init codesize limit too
charles-cooper Jun 8, 2024
df951cd
tune level of nesting
charles-cooper Jun 8, 2024
b0eb26e
Merge branch 'master' into test-abi-decode-spec
charles-cooper Jun 12, 2024
fa282b3
add extcall fuzzer
charles-cooper Jun 12, 2024
dfcaaa3
fix get_contract_from_ir scope
charles-cooper Jun 12, 2024
1cc5d9a
fix harness setup errors
charles-cooper Jun 12, 2024
18bea6b
kludge, add assertion to match abi_decode behavior
charles-cooper Jun 12, 2024
8af77ec
Merge branch 'master' into test-abi-decode-spec
charles-cooper Jun 14, 2024
a4fafcc
reduce number of examples for CI
charles-cooper Jun 14, 2024
300f300
fix tuple repr
charles-cooper Jun 14, 2024
8277522
fix lint
charles-cooper Jun 14, 2024
0976fb5
update _abi_decode to abi_decode, add a comment
charles-cooper Jun 14, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ def env(gas_limit, evm_version, evm_backend, tracing, account_keys) -> BaseEnv:
)


@pytest.fixture
@pytest.fixture(scope="module")
def get_contract_from_ir(env, optimize):
def ir_compiler(ir, *args, **kwargs):
ir = IRnode.from_list(ir)
Expand Down
12 changes: 11 additions & 1 deletion tests/evm_backends/base_env.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class ExecutionResult:
gas_used: int


class EvmError(RuntimeError):
class EvmError(Exception):
"""Exception raised when a call fails."""


Expand Down Expand Up @@ -205,6 +205,16 @@ def out_of_gas_error(self) -> str:
"""Expected error message when user runs out of gas"""
raise NotImplementedError # must be implemented by subclasses

@property
def contract_size_limit_error(self) -> str:
"""Expected error message when contract is over codesize limit"""
raise NotImplementedError # must be implemented by subclasses

@property
def initcode_size_limit_error(self) -> str:
"""Expected error message when contract is over codesize limit"""
raise NotImplementedError # must be implemented by subclasses


def _compile(
source_code: str,
Expand Down
2 changes: 2 additions & 0 deletions tests/evm_backends/revm_env.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
class RevmEnv(BaseEnv):
invalid_opcode_error = "InvalidFEOpcode"
out_of_gas_error = "OutOfGas"
contract_size_limit_error = "CreateContractSizeLimit"
initcode_size_limit_error = "CreateInitCodeSizeLimit"

def __init__(
self,
Expand Down
148 changes: 148 additions & 0 deletions tests/functional/builtins/codegen/abi_decode.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
from typing import TYPE_CHECKING, Iterable

from eth_utils import to_checksum_address

from vyper.abi_types import (
ABI_Address,
ABI_Bool,
ABI_Bytes,
ABI_BytesM,
ABI_DynamicArray,
ABI_GIntM,
ABI_StaticArray,
ABI_String,
ABI_Tuple,
ABIType,
)
from vyper.utils import int_bounds, unsigned_to_signed

if TYPE_CHECKING:
from vyper.semantics.types import VyperType


class DecodeError(Exception):
pass


def _strict_slice(payload, start, length):
if start < 0:
raise DecodeError(f"OOB {start}")

end = start + length
if end > len(payload):
raise DecodeError(f"OOB {start} + {length} (=={end}) > {len(payload)}")
return payload[start:end]


def _read_int(payload, ofst):
return int.from_bytes(_strict_slice(payload, ofst, 32))


# vyper abi_decode spec implementation
def spec_decode(typ: "VyperType", payload: bytes):
abi_t = typ.abi_type

lo, hi = abi_t.static_size(), abi_t.size_bound()
if not (lo <= len(payload) <= hi):
raise DecodeError(f"bad payload size {lo}, {len(payload)}, {hi}")

return _decode_r(abi_t, 0, payload)


def _decode_r(abi_t: ABIType, current_offset: int, payload: bytes):
if isinstance(abi_t, ABI_Tuple):
return tuple(_decode_multi_r(abi_t.subtyps, current_offset, payload))

if isinstance(abi_t, ABI_StaticArray):
n = abi_t.m_elems
subtypes = [abi_t.subtyp] * n
return _decode_multi_r(subtypes, current_offset, payload)

if isinstance(abi_t, ABI_DynamicArray):
bound = abi_t.elems_bound

n = _read_int(payload, current_offset)
if n > bound:
raise DecodeError("Dynarray too large")

# offsets in dynarray start from after the length word
current_offset += 32
subtypes = [abi_t.subtyp] * n
return _decode_multi_r(subtypes, current_offset, payload)

# sanity check
assert not abi_t.is_complex_type()

if isinstance(abi_t, ABI_Bytes):
bound = abi_t.bytes_bound
length = _read_int(payload, current_offset)
if length > bound:
raise DecodeError("bytes too large")

current_offset += 32 # size of length word
ret = _strict_slice(payload, current_offset, length)

# abi string doesn't actually define string decoder, so we
# just bytecast the output
if isinstance(abi_t, ABI_String):
# match eth-stdlib, since that's what we check against
ret = ret.decode(errors="surrogateescape")

return ret

# sanity check
assert not abi_t.is_dynamic()

if isinstance(abi_t, ABI_GIntM):
ret = _read_int(payload, current_offset)

# handle signedness
if abi_t.signed:
ret = unsigned_to_signed(ret, 256, strict=True)

# bounds check
lo, hi = int_bounds(signed=abi_t.signed, bits=abi_t.m_bits)
if not (lo <= ret <= hi):
u = "" if abi_t.signed else "u"
raise DecodeError(f"invalid {u}int{abi_t.m_bits}")

if isinstance(abi_t, ABI_Address):
return to_checksum_address(ret.to_bytes(20, "big"))

if isinstance(abi_t, ABI_Bool):
if ret not in (0, 1):
raise DecodeError("invalid bool")
return ret

return ret

if isinstance(abi_t, ABI_BytesM):
ret = _strict_slice(payload, current_offset, 32)
m = abi_t.m_bytes
assert 1 <= m <= 32 # internal sanity check
# BytesM is right-padded with zeroes
if ret[m:] != b"\x00" * (32 - m):
raise DecodeError(f"invalid bytes{m}")
return ret[:m]

raise RuntimeError("unreachable")


def _decode_multi_r(types: Iterable[ABIType], outer_offset: int, payload: bytes) -> list:
ret = []
static_ofst = outer_offset

for sub_t in types:
if sub_t.is_dynamic():
# "head" terminology from abi spec
head = _read_int(payload, static_ofst)
ofst = outer_offset + head
else:
ofst = static_ofst

item = _decode_r(sub_t, ofst, payload)

ret.append(item)
static_ofst += sub_t.embedded_static_size()

return ret
Loading
Loading