Skip to content

Commit

Permalink
Merge branch 'master' of https://github.com/vyperlang/vyper into tool…
Browse files Browse the repository at this point in the history
…/storage_layout_json
  • Loading branch information
tserg committed Dec 16, 2024
2 parents 196bbc8 + 537313b commit 5018c5f
Show file tree
Hide file tree
Showing 21 changed files with 719 additions and 66 deletions.
2 changes: 2 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ def _global_version(version):
"asttokens>=2.0.5,<3",
"pycryptodome>=3.5.1,<4",
"packaging>=23.1,<24",
"lark>=1.0.0,<2",
"importlib-metadata",
"wheel",
],
Expand All @@ -105,6 +106,7 @@ def _global_version(version):
"vyper=vyper.cli.vyper_compile:_parse_cli_args",
"fang=vyper.cli.vyper_ir:_parse_cli_args",
"vyper-json=vyper.cli.vyper_json:_parse_cli_args",
"venom=vyper.cli.venom_main:_parse_cli_args",
]
},
classifiers=[
Expand Down
32 changes: 31 additions & 1 deletion tests/functional/syntax/exceptions/test_vyper_exception_pos.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from pytest import raises

from vyper.exceptions import VyperException
from vyper import compile_code
from vyper.exceptions import SyntaxException, VyperException


def test_type_exception_pos():
Expand Down Expand Up @@ -29,3 +30,32 @@ def __init__():
"""
assert_compile_failed(lambda: get_contract(code), VyperException)


def test_exception_contains_file(make_input_bundle):
code = """
def bar()>:
"""
input_bundle = make_input_bundle({"code.vy": code})
with raises(SyntaxException, match="contract"):
compile_code(code, input_bundle=input_bundle)


def test_exception_reports_correct_file(make_input_bundle, chdir_tmp_path):
code_a = "def bar()>:"
code_b = "import A"
input_bundle = make_input_bundle({"A.vy": code_a, "B.vy": code_b})

with raises(SyntaxException, match=r'contract "A\.vy:\d+"'):
compile_code(code_b, input_bundle=input_bundle)


def test_syntax_exception_reports_correct_offset(make_input_bundle):
code = """
def foo():
uint256 a = pass
"""
input_bundle = make_input_bundle({"code.vy": code})

with raises(SyntaxException, match=r"line \d+:12"):
compile_code(code, input_bundle=input_bundle)
Empty file.
Empty file.
275 changes: 275 additions & 0 deletions tests/functional/venom/parser/test_parsing.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,275 @@
from vyper.venom.basicblock import IRBasicBlock, IRInstruction, IRLabel, IRLiteral, IRVariable
from vyper.venom.context import IRContext
from vyper.venom.function import IRFunction
from vyper.venom.parser import parse_venom

# TODO: Refactor tests with these helpers


def instructions_eq(i1: IRInstruction, i2: IRInstruction) -> bool:
return i1.output == i2.output and i1.opcode == i2.opcode and i1.operands == i2.operands


def assert_bb_eq(bb1: IRBasicBlock, bb2: IRBasicBlock):
assert bb1.label.value == bb2.label.value
assert len(bb1.instructions) == len(bb2.instructions)
for i1, i2 in zip(bb1.instructions, bb2.instructions):
assert instructions_eq(i1, i2), f"[{i1}] != [{i2}]"


def assert_fn_eq(fn1: IRFunction, fn2: IRFunction):
assert fn1.name.value == fn2.name.value
assert fn1.last_variable == fn2.last_variable
assert len(fn1._basic_block_dict) == len(fn2._basic_block_dict)

for name1, bb1 in fn1._basic_block_dict.items():
assert name1 in fn2._basic_block_dict
assert_bb_eq(bb1, fn2._basic_block_dict[name1])

# check function entry is the same
assert fn1.entry.label == fn2.entry.label


def assert_ctx_eq(ctx1: IRContext, ctx2: IRContext):
assert ctx1.last_label == ctx2.last_label
assert len(ctx1.functions) == len(ctx2.functions)
for label1, fn1 in ctx1.functions.items():
assert label1 in ctx2.functions
assert_fn_eq(fn1, ctx2.functions[label1])

# check entry function is the same
assert next(iter(ctx1.functions.keys())) == next(iter(ctx2.functions.keys()))

assert len(ctx1.data_segment) == len(ctx2.data_segment)
for d1, d2 in zip(ctx1.data_segment, ctx2.data_segment):
assert instructions_eq(d1, d2), f"data: [{d1}] != [{d2}]"


def test_single_bb():
source = """
function main {
main:
stop
}
[data]
"""

parsed_ctx = parse_venom(source)

expected_ctx = IRContext()
expected_ctx.add_function(main_fn := IRFunction(IRLabel("main")))
main_bb = main_fn.get_basic_block("main")
main_bb.append_instruction("stop")

assert_ctx_eq(parsed_ctx, expected_ctx)


def test_multi_bb_single_fn():
source = """
function start {
start:
%1 = callvalue
jnz @fine, @has_callvalue, %1
fine:
%2 = calldataload 4
%4 = add %2, 279387
return %2, %4
has_callvalue:
revert 0, 0
}
[data]
"""

parsed_ctx = parse_venom(source)

expected_ctx = IRContext()
expected_ctx.add_function(start_fn := IRFunction(IRLabel("start")))

start_bb = start_fn.get_basic_block("start")
start_bb.append_instruction("callvalue", ret=IRVariable("1"))
start_bb.append_instruction("jnz", IRVariable("1"), IRLabel("has_callvalue"), IRLabel("fine"))

start_fn.append_basic_block(fine_bb := IRBasicBlock(IRLabel("fine"), start_fn))
fine_bb.append_instruction("calldataload", IRLiteral(4), ret=IRVariable("2"))
fine_bb.append_instruction("add", IRLiteral(279387), IRVariable("2"), ret=IRVariable("4"))
fine_bb.append_instruction("return", IRVariable("4"), IRVariable("2"))

has_callvalue_bb = IRBasicBlock(IRLabel("has_callvalue"), start_fn)
start_fn.append_basic_block(has_callvalue_bb)
has_callvalue_bb.append_instruction("revert", IRLiteral(0), IRLiteral(0))
has_callvalue_bb.append_instruction("stop")

start_fn.last_variable = 4

assert_ctx_eq(parsed_ctx, expected_ctx)


def test_data_section():
parsed_ctx = parse_venom(
"""
function entry {
entry:
stop
}
[data]
dbname @selector_buckets
db @selector_bucket_0
db @fallback
db @selector_bucket_2
db @selector_bucket_3
db @fallback
db @selector_bucket_5
db @selector_bucket_6
"""
)

expected_ctx = IRContext()
expected_ctx.add_function(entry_fn := IRFunction(IRLabel("entry")))
entry_fn.get_basic_block("entry").append_instruction("stop")

expected_ctx.data_segment = [
IRInstruction("dbname", [IRLabel("selector_buckets")]),
IRInstruction("db", [IRLabel("selector_bucket_0")]),
IRInstruction("db", [IRLabel("fallback")]),
IRInstruction("db", [IRLabel("selector_bucket_2")]),
IRInstruction("db", [IRLabel("selector_bucket_3")]),
IRInstruction("db", [IRLabel("fallback")]),
IRInstruction("db", [IRLabel("selector_bucket_5")]),
IRInstruction("db", [IRLabel("selector_bucket_6")]),
]

assert_ctx_eq(parsed_ctx, expected_ctx)


def test_multi_function():
parsed_ctx = parse_venom(
"""
function entry {
entry:
invoke @check_cv
jmp @wow
wow:
mstore 0, 1
return 0, 32
}
function check_cv {
check_cv:
%1 = callvalue
%2 = param
jnz @no_value, @has_value, %1
no_value:
ret %2
has_value:
revert 0, 0
}
[data]
"""
)

expected_ctx = IRContext()
expected_ctx.add_function(entry_fn := IRFunction(IRLabel("entry")))

entry_bb = entry_fn.get_basic_block("entry")
entry_bb.append_instruction("invoke", IRLabel("check_cv"))
entry_bb.append_instruction("jmp", IRLabel("wow"))

entry_fn.append_basic_block(wow_bb := IRBasicBlock(IRLabel("wow"), entry_fn))
wow_bb.append_instruction("mstore", IRLiteral(1), IRLiteral(0))
wow_bb.append_instruction("return", IRLiteral(32), IRLiteral(0))

expected_ctx.add_function(check_fn := IRFunction(IRLabel("check_cv")))

check_entry_bb = check_fn.get_basic_block("check_cv")
check_entry_bb.append_instruction("callvalue", ret=IRVariable("1"))
check_entry_bb.append_instruction("param", ret=IRVariable("2"))
check_entry_bb.append_instruction(
"jnz", IRVariable("1"), IRLabel("has_value"), IRLabel("no_value")
)
check_fn.append_basic_block(no_value_bb := IRBasicBlock(IRLabel("no_value"), check_fn))
no_value_bb.append_instruction("ret", IRVariable("2"))

check_fn.append_basic_block(value_bb := IRBasicBlock(IRLabel("has_value"), check_fn))
value_bb.append_instruction("revert", IRLiteral(0), IRLiteral(0))
value_bb.append_instruction("stop")

check_fn.last_variable = 2

assert_ctx_eq(parsed_ctx, expected_ctx)


def test_multi_function_and_data():
parsed_ctx = parse_venom(
"""
function entry {
entry:
invoke @check_cv
jmp @wow
wow:
mstore 0, 1
return 0, 32
}
function check_cv {
check_cv:
%1 = callvalue
%2 = param
jnz @no_value, @has_value, %1
no_value:
ret %2
has_value:
revert 0, 0
}
[data]
dbname @selector_buckets
db @selector_bucket_0
db @fallback
db @selector_bucket_2
db @selector_bucket_3
db @selector_bucket_6
"""
)

expected_ctx = IRContext()
expected_ctx.add_function(entry_fn := IRFunction(IRLabel("entry")))

entry_bb = entry_fn.get_basic_block("entry")
entry_bb.append_instruction("invoke", IRLabel("check_cv"))
entry_bb.append_instruction("jmp", IRLabel("wow"))

entry_fn.append_basic_block(wow_bb := IRBasicBlock(IRLabel("wow"), entry_fn))
wow_bb.append_instruction("mstore", IRLiteral(1), IRLiteral(0))
wow_bb.append_instruction("return", IRLiteral(32), IRLiteral(0))

expected_ctx.add_function(check_fn := IRFunction(IRLabel("check_cv")))

check_entry_bb = check_fn.get_basic_block("check_cv")
check_entry_bb.append_instruction("callvalue", ret=IRVariable("1"))
check_entry_bb.append_instruction("param", ret=IRVariable("2"))
check_entry_bb.append_instruction(
"jnz", IRVariable("1"), IRLabel("has_value"), IRLabel("no_value")
)
check_fn.append_basic_block(no_value_bb := IRBasicBlock(IRLabel("no_value"), check_fn))
no_value_bb.append_instruction("ret", IRVariable("2"))

check_fn.append_basic_block(value_bb := IRBasicBlock(IRLabel("has_value"), check_fn))
value_bb.append_instruction("revert", IRLiteral(0), IRLiteral(0))
value_bb.append_instruction("stop")

check_fn.last_variable = 2

expected_ctx.data_segment = [
IRInstruction("dbname", [IRLabel("selector_buckets")]),
IRInstruction("db", [IRLabel("selector_bucket_0")]),
IRInstruction("db", [IRLabel("fallback")]),
IRInstruction("db", [IRLabel("selector_bucket_2")]),
IRInstruction("db", [IRLabel("selector_bucket_3")]),
IRInstruction("db", [IRLabel("selector_bucket_6")]),
]

assert_ctx_eq(parsed_ctx, expected_ctx)
16 changes: 16 additions & 0 deletions tests/unit/ast/test_natspec.py
Original file line number Diff line number Diff line change
Expand Up @@ -436,3 +436,19 @@ def test_natspec_parsed_implicitly():
# anything beyond ast is blocked
with pytest.raises(NatSpecSyntaxException):
compile_code(code, output_formats=["annotated_ast_dict"])


def test_natspec_exception_contains_file_path():
code = """
@external
def foo() -> (int128,uint256):
'''
@return int128
@return uint256
@return this should fail
'''
return 1, 2
"""

with pytest.raises(NatSpecSyntaxException, match=r'contract "VyperContract\.vy:\d+"'):
parse_natspec(code)
20 changes: 20 additions & 0 deletions tests/unit/ast/test_pre_parser.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from pathlib import Path

import pytest

from vyper import compile_code
Expand Down Expand Up @@ -56,6 +58,24 @@ def test_invalid_version_pragma(file_version, mock_version):
validate_version_pragma(f"{file_version}", file_version, (SRC_LINE))


def test_invalid_version_contains_file(mock_version):
mock_version(COMPILER_VERSION)
with pytest.raises(VersionException, match=r'contract "mock\.vy:\d+"'):
compile_code("# pragma version ^0.3.10", resolved_path=Path("mock.vy"))


def test_imported_invalid_version_contains_correct_file(
mock_version, make_input_bundle, chdir_tmp_path
):
code_a = "# pragma version ^0.3.10"
code_b = "import A"
input_bundle = make_input_bundle({"A.vy": code_a, "B.vy": code_b})
mock_version(COMPILER_VERSION)

with pytest.raises(VersionException, match=r'contract "A\.vy:\d+"'):
compile_code(code_b, input_bundle=input_bundle)


prerelease_valid_versions = [
"<0.1.1-beta.9",
"<0.1.1b9",
Expand Down
2 changes: 1 addition & 1 deletion tests/unit/cli/vyper_json/test_compile_json.py
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@ def test_wrong_language():

def test_exc_handler_raises_syntax(input_json):
input_json["sources"]["badcode.vy"] = {"content": BAD_SYNTAX_CODE}
with pytest.raises(SyntaxException):
with pytest.raises(SyntaxException, match=r'contract "badcode\.vy:\d+"'):
compile_json(input_json)


Expand Down
Loading

0 comments on commit 5018c5f

Please sign in to comment.