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

chore: improve error message for invalid address literal #3621

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,6 @@ def foo():
def foo():
log Foo("abcd")
""",
# Address literal must be checksummed
"""
a: constant(address) = 0x3cd751e6b0078be393132286c442345e5dc49699
""",
]


Expand Down
9 changes: 9 additions & 0 deletions tests/functional/syntax/test_ann_assign.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

from vyper import compiler
from vyper.exceptions import (
BadChecksumAddress,
InvalidAttribute,
TypeMismatch,
UndeclaredDefinition,
Expand Down Expand Up @@ -125,6 +126,14 @@ def foo() -> bool:
""",
TypeMismatch,
),
(
"""
@external
def foo():
x: address = 0x6b175474e89094c44da98b954eedeac495271d0F
""",
BadChecksumAddress,
),
]


Expand Down
8 changes: 8 additions & 0 deletions tests/functional/syntax/test_constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from vyper import compiler
from vyper.exceptions import (
ArgumentException,
BadChecksumAddress,
ImmutableViolation,
NamespaceCollision,
StateAccessViolation,
Expand Down Expand Up @@ -171,6 +172,13 @@ def hello() :
""",
ImmutableViolation,
),
# invalid checksum address
(
"""
a: constant(address) = 0x3cd751e6b0078be393132286c442345e5dc49699
""",
BadChecksumAddress,
),
]


Expand Down
29 changes: 20 additions & 9 deletions tests/unit/ast/nodes/test_hex.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,30 @@

from vyper import ast as vy_ast
from vyper import semantics
from vyper.exceptions import InvalidLiteral
from vyper.exceptions import BadChecksumAddress, InvalidLiteral

code_invalid_checksum = [
"""
foo: constant(address) = 0x6b175474e89094c44da98b954eedeac495271d0F
""",
"""
foo: constant(address[1]) = [0x6b175474e89094c44da98b954eedeac495271d0F]
""",
"""
@external
def foo():
bar: address = 0x6b175474e89094c44da98b954eedeac495271d0F
""",
]


@pytest.mark.parametrize("code", code_invalid_checksum)
def test_bad_checksum_address(code, dummy_input_bundle):
with pytest.raises(BadChecksumAddress):
vyper_module = vy_ast.parse_to_ast(code)
semantics.analyze_module(vyper_module, dummy_input_bundle)


code_invalid_literal = [
"""
@external
def foo():
bar: address[1] = [0x6b175474e89094c44da98b954eedeac495271d0F]
foo: constant(address[1]) = [0x6b175474e89094c44da98b954eedeac495271d0F]
""",
"""
@external
Expand All @@ -28,6 +34,11 @@ def foo():
pass
""",
"""
@external
def foo():
bar: address[1] = [0x6b175474e89094c44da98b954eedeac495271d0F]
""",
"""
foo: constant(bytes20) = 0x6b175474e89094c44da98b954eedeac495271d0F
""",
"""
Expand All @@ -36,8 +47,8 @@ def foo():
]


@pytest.mark.parametrize("code", code_invalid_checksum)
def test_invalid_checksum(code, dummy_input_bundle):
@pytest.mark.parametrize("code", code_invalid_literal)
def test_invalid_literal(code, dummy_input_bundle):
with pytest.raises(InvalidLiteral):
vyper_module = vy_ast.parse_to_ast(code)
semantics.analyze_module(vyper_module, dummy_input_bundle)
4 changes: 4 additions & 0 deletions vyper/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,10 @@ class InvalidLiteral(VyperException):
"""Invalid literal value."""


class BadChecksumAddress(InvalidLiteral):
"""Invalid literal address value."""


class InvalidAttribute(VyperException):
"""Reference to an attribute that does not exist."""

Expand Down
27 changes: 23 additions & 4 deletions vyper/semantics/analysis/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

from vyper import ast as vy_ast
from vyper.exceptions import (
BadChecksumAddress,
CompilerPanic,
InvalidLiteral,
InvalidOperation,
Expand All @@ -24,7 +25,7 @@
from vyper.semantics.types.bytestrings import BytesT, StringT
from vyper.semantics.types.primitives import AddressT, BoolT, BytesM_T, IntegerT
from vyper.semantics.types.subscriptable import DArrayT, SArrayT, TupleT
from vyper.utils import checksum_encode, int_to_fourbytes
from vyper.utils import int_to_fourbytes


def _validate_op(node, types_list, validation_fn_name):
Expand Down Expand Up @@ -312,7 +313,16 @@ def types_from_Constant(self, node):
raise OverflowException(
"Numeric literal is outside of allowable range for number types", node
)
raise InvalidLiteral(f"Could not determine type for literal value '{node.value}'", node)

msg = f"Could not determine type for literal value '{node.value}'"
if isinstance(node, vy_ast.Hex) and len(node.value) == 42:
# call `validate_literal` to add a hint on address checksum mismatch
try:
AddressT().validate_literal(node)
except BadChecksumAddress as e:
raise InvalidLiteral(msg, node, hint=e.args[0])

raise InvalidLiteral(msg, node)

def types_from_IfExp(self, node):
validate_expected_type(node.test, BoolT())
Expand Down Expand Up @@ -570,7 +580,14 @@ def validate_expected_type(node, expected_type):
# fail block
pass

given_types = _ExprAnalyser().get_possible_types_from_node(node)
try:
given_types = _ExprAnalyser().get_possible_types_from_node(node)
except InvalidLiteral as i:
# call `validate_literal` for its side effect of throwing an exception for
# address checksum mismatch only if the expected type is an address
if AddressT() in expected_type and isinstance(node, vy_ast.Hex) and len(node.value) == 42:
AddressT().validate_literal(node)
raise i

if isinstance(node, vy_ast.List):
# special case - for literal arrays we individually validate each item
Expand Down Expand Up @@ -609,7 +626,9 @@ def validate_expected_type(node, expected_type):

suggestion_str = ""
if expected_type[0] == AddressT() and given_types[0] == BytesM_T(20):
suggestion_str = f" Did you mean {checksum_encode(node.value)}?"
# call `validate_literal` for its side effect of throwing an exception for
# address checksum mismatch
AddressT().validate_literal(node)

raise TypeMismatch(
f"Expected {expected_str} but literal can only be cast as {given_str}.{suggestion_str}",
Expand Down
3 changes: 2 additions & 1 deletion vyper/semantics/types/primitives.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from vyper import ast as vy_ast
from vyper.abi_types import ABI_Address, ABI_Bool, ABI_BytesM, ABI_FixedMxN, ABI_GIntM, ABIType
from vyper.exceptions import (
BadChecksumAddress,
CompilerPanic,
InvalidLiteral,
InvalidOperation,
Expand Down Expand Up @@ -384,7 +385,7 @@ def validate_literal(self, node: vy_ast.Constant) -> None:

addr = node.value
if not is_checksum_encoded(addr):
raise InvalidLiteral(
raise BadChecksumAddress(
"Address checksum mismatch. If you are sure this is the right "
f"address, the correct checksummed form is: {checksum_encode(addr)}",
node,
Expand Down
Loading