From 206697d7c55924e5e5b999f642b4bd903442b3d0 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Tue, 26 Sep 2023 17:00:57 +0800 Subject: [PATCH 01/11] add checksum exception --- vyper/exceptions.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/vyper/exceptions.py b/vyper/exceptions.py index aa23614e85..4126e9bef0 100644 --- a/vyper/exceptions.py +++ b/vyper/exceptions.py @@ -199,6 +199,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.""" From 2398fda3165f4f9e1c0044029a1dc20d3e94b2bc Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Tue, 26 Sep 2023 17:01:06 +0800 Subject: [PATCH 02/11] catch invalid checksummed addr --- vyper/semantics/analysis/utils.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/vyper/semantics/analysis/utils.py b/vyper/semantics/analysis/utils.py index 4f911764e0..8b7642d64d 100644 --- a/vyper/semantics/analysis/utils.py +++ b/vyper/semantics/analysis/utils.py @@ -3,6 +3,7 @@ from vyper import ast as vy_ast from vyper.exceptions import ( + BadChecksumAddress, CompilerPanic, InvalidLiteral, InvalidOperation, @@ -307,6 +308,13 @@ def types_from_Constant(self, node): raise OverflowException( "Numeric literal is outside of allowable range for number types", node ) + if isinstance(node, vy_ast.Hex) and len(node.value) == 42: + raise BadChecksumAddress( + "If this is an address, the correct checksummed form is: " + f"{checksum_encode(node.value)}", + node, + ) + raise InvalidLiteral(f"Could not determine type for literal value '{node.value}'", node) def types_from_List(self, node): From df6a042ae929663893655dbf8421bd008ed6035e Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Tue, 26 Sep 2023 17:01:10 +0800 Subject: [PATCH 03/11] add tests --- tests/parser/syntax/test_ann_assign.py | 9 +++++++++ tests/parser/syntax/test_constants.py | 8 ++++++++ 2 files changed, 17 insertions(+) diff --git a/tests/parser/syntax/test_ann_assign.py b/tests/parser/syntax/test_ann_assign.py index b5c1f6a752..4dbc84808b 100644 --- a/tests/parser/syntax/test_ann_assign.py +++ b/tests/parser/syntax/test_ann_assign.py @@ -3,6 +3,7 @@ from vyper import compiler from vyper.exceptions import ( + BadChecksumAddress, InvalidAttribute, InvalidType, UndeclaredDefinition, @@ -125,6 +126,14 @@ def foo() -> bool: """, InvalidType, ), + ( + """ +@external +def foo(): + x: address = 0x6b175474e89094c44da98b954eedeac495271d0F + """, + BadChecksumAddress, + ), ] diff --git a/tests/parser/syntax/test_constants.py b/tests/parser/syntax/test_constants.py index ffd2f1faa0..64e57d6424 100644 --- a/tests/parser/syntax/test_constants.py +++ b/tests/parser/syntax/test_constants.py @@ -4,6 +4,7 @@ from vyper import compiler from vyper.exceptions import ( ArgumentException, + BadChecksumAddress, ImmutableViolation, InvalidType, NamespaceCollision, @@ -171,6 +172,13 @@ def hello() : """, ImmutableViolation, ), + # invalid checksum address + ( + """ +foo: constant(address) = 0x6b175474e89094c44da98b954eedeac495271d0F + """, + BadChecksumAddress, + ), ] From c2866d82e98ec7658a8e9b374e3c1e0d4350390f Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Wed, 27 Sep 2023 12:11:22 +0800 Subject: [PATCH 04/11] change exception in address primitive --- vyper/semantics/types/primitives.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/vyper/semantics/types/primitives.py b/vyper/semantics/types/primitives.py index 07d1a21a94..2f9475b43c 100644 --- a/vyper/semantics/types/primitives.py +++ b/vyper/semantics/types/primitives.py @@ -6,7 +6,13 @@ 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 CompilerPanic, InvalidLiteral, InvalidOperation, OverflowException +from vyper.exceptions import ( + BadChecksumAddress, + CompilerPanic, + InvalidLiteral, + InvalidOperation, + OverflowException, +) from vyper.utils import checksum_encode, int_bounds, is_checksum_encoded from .base import VyperType @@ -335,7 +341,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, From fec3a931b21b1a4dc6d45894e6ad56e0d396fae9 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Wed, 27 Sep 2023 12:11:28 +0800 Subject: [PATCH 05/11] clarify tests --- tests/ast/nodes/test_hex.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/tests/ast/nodes/test_hex.py b/tests/ast/nodes/test_hex.py index 47483c493c..5c0e8eb2f0 100644 --- a/tests/ast/nodes/test_hex.py +++ b/tests/ast/nodes/test_hex.py @@ -2,7 +2,7 @@ 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 = [ """ @@ -27,6 +27,19 @@ def foo(): for i in [0x6b175474e89094c44da98b954eedeac495271d0F]: pass """, +] + + +@pytest.mark.parametrize("code", code_invalid_checksum) +def test_bad_checksum_address(code): + vyper_module = vy_ast.parse_to_ast(code) + + with pytest.raises(BadChecksumAddress): + vy_ast.validation.validate_literal_nodes(vyper_module) + semantics.validate_semantics(vyper_module, {}) + + +code_invalid_literal = [ """ foo: constant(bytes20) = 0x6b175474e89094c44da98b954eedeac495271d0F """, @@ -36,8 +49,8 @@ def foo(): ] -@pytest.mark.parametrize("code", code_invalid_checksum) -def test_invalid_checksum(code): +@pytest.mark.parametrize("code", code_invalid_literal) +def test_invalid_literal(code): vyper_module = vy_ast.parse_to_ast(code) with pytest.raises(InvalidLiteral): From 9ae5b221c6b2458d771ae1eb728a7e840b7c2602 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Fri, 16 Feb 2024 22:16:04 +0800 Subject: [PATCH 06/11] standardize to validate_literal --- vyper/semantics/analysis/utils.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/vyper/semantics/analysis/utils.py b/vyper/semantics/analysis/utils.py index 8eb4b08032..f796a49d2e 100644 --- a/vyper/semantics/analysis/utils.py +++ b/vyper/semantics/analysis/utils.py @@ -3,7 +3,6 @@ from vyper import ast as vy_ast from vyper.exceptions import ( - BadChecksumAddress, CompilerPanic, InvalidLiteral, InvalidOperation, @@ -25,7 +24,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): @@ -314,11 +313,9 @@ def types_from_Constant(self, node): "Numeric literal is outside of allowable range for number types", node ) if isinstance(node, vy_ast.Hex) and len(node.value) == 42: - raise BadChecksumAddress( - "If this is an address, the correct checksummed form is: " - f"{checksum_encode(node.value)}", - node, - ) + # call `validate_literal` for its side effect of throwing an exception for + # address checksum mismatch + AddressT().validate_literal(node) raise InvalidLiteral(f"Could not determine type for literal value '{node.value}'", node) @@ -616,7 +613,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}", From 2df2294da615564e7ff6d69f3af8a4976d9ca266 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Fri, 16 Feb 2024 22:31:13 +0800 Subject: [PATCH 07/11] clean up --- tests/unit/ast/nodes/test_hex.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/unit/ast/nodes/test_hex.py b/tests/unit/ast/nodes/test_hex.py index 1e156a8318..c68566b060 100644 --- a/tests/unit/ast/nodes/test_hex.py +++ b/tests/unit/ast/nodes/test_hex.py @@ -35,7 +35,6 @@ def test_bad_checksum_address(code, dummy_input_bundle): vyper_module = vy_ast.parse_to_ast(code) with pytest.raises(BadChecksumAddress): - vy_ast.validation.validate_literal_nodes(vyper_module) semantics.validate_semantics(vyper_module, dummy_input_bundle) @@ -54,5 +53,4 @@ def test_invalid_literal(code, dummy_input_bundle): vyper_module = vy_ast.parse_to_ast(code) with pytest.raises(InvalidLiteral): - vy_ast.validation.validate_literal_nodes(vyper_module) semantics.validate_semantics(vyper_module, dummy_input_bundle) From 07c3feb2b3a829a852078be14706927a8738b99a Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Tue, 20 Feb 2024 15:28:58 +0800 Subject: [PATCH 08/11] move test --- .../syntax/exceptions/test_type_mismatch_exception.py | 4 ---- tests/functional/syntax/test_constants.py | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/tests/functional/syntax/exceptions/test_type_mismatch_exception.py b/tests/functional/syntax/exceptions/test_type_mismatch_exception.py index 514f2df618..66cfe1aac7 100644 --- a/tests/functional/syntax/exceptions/test_type_mismatch_exception.py +++ b/tests/functional/syntax/exceptions/test_type_mismatch_exception.py @@ -43,10 +43,6 @@ def foo(): def foo(): log Foo("abcd") """, - # Address literal must be checksummed - """ -a: constant(address) = 0x3cd751e6b0078be393132286c442345e5dc49699 - """, ] diff --git a/tests/functional/syntax/test_constants.py b/tests/functional/syntax/test_constants.py index c19ac3ccd5..b35a470e37 100644 --- a/tests/functional/syntax/test_constants.py +++ b/tests/functional/syntax/test_constants.py @@ -175,7 +175,7 @@ def hello() : # invalid checksum address ( """ -foo: constant(address) = 0x6b175474e89094c44da98b954eedeac495271d0F +a: constant(address) = 0x3cd751e6b0078be393132286c442345e5dc49699 """, BadChecksumAddress, ), From ec1c45d7bd882435476a605a932c7ce1e968fd29 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Sat, 2 Mar 2024 17:33:14 +0800 Subject: [PATCH 09/11] move constant msg to hint --- vyper/semantics/analysis/utils.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/vyper/semantics/analysis/utils.py b/vyper/semantics/analysis/utils.py index 6cbeb62758..a593f246f8 100644 --- a/vyper/semantics/analysis/utils.py +++ b/vyper/semantics/analysis/utils.py @@ -3,6 +3,7 @@ from vyper import ast as vy_ast from vyper.exceptions import ( + BadChecksumAddress, CompilerPanic, InvalidLiteral, InvalidOperation, @@ -312,12 +313,17 @@ def types_from_Constant(self, node): raise OverflowException( "Numeric literal is outside of allowable range for number types", node ) + + hint = "" if isinstance(node, vy_ast.Hex) and len(node.value) == 42: # call `validate_literal` for its side effect of throwing an exception for # address checksum mismatch - AddressT().validate_literal(node) + try: + AddressT().validate_literal(node) + except BadChecksumAddress as e: + hint += e.message - raise InvalidLiteral(f"Could not determine type for literal value '{node.value}'", node) + raise InvalidLiteral(f"Could not determine type for literal value '{node.value}'", node, hint=hint) def types_from_IfExp(self, node): validate_expected_type(node.test, BoolT()) From d4a94c592166a1d2073822169c990449dc49e6ff Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Mon, 4 Mar 2024 22:26:50 +0800 Subject: [PATCH 10/11] handle non-address types --- vyper/semantics/analysis/utils.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/vyper/semantics/analysis/utils.py b/vyper/semantics/analysis/utils.py index a593f246f8..a77a1c908b 100644 --- a/vyper/semantics/analysis/utils.py +++ b/vyper/semantics/analysis/utils.py @@ -314,16 +314,15 @@ def types_from_Constant(self, node): "Numeric literal is outside of allowable range for number types", node ) - hint = "" + 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` for its side effect of throwing an exception for - # address checksum mismatch + # call `validate_literal` to add a hint on address checksum mismatch try: AddressT().validate_literal(node) except BadChecksumAddress as e: - hint += e.message + raise InvalidLiteral(msg, node, hint=e.args[0]) - raise InvalidLiteral(f"Could not determine type for literal value '{node.value}'", node, hint=hint) + raise InvalidLiteral(msg, node) def types_from_IfExp(self, node): validate_expected_type(node.test, BoolT()) @@ -581,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 From ef92f79069f5cd6a2ba41da280b1056d7c786df6 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Mon, 4 Mar 2024 22:26:55 +0800 Subject: [PATCH 11/11] fix tests --- tests/unit/ast/nodes/test_hex.py | 37 +++++++++++++++----------------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/tests/unit/ast/nodes/test_hex.py b/tests/unit/ast/nodes/test_hex.py index b30f2b69d9..e161e3c160 100644 --- a/tests/unit/ast/nodes/test_hex.py +++ b/tests/unit/ast/nodes/test_hex.py @@ -9,37 +9,36 @@ foo: constant(address) = 0x6b175474e89094c44da98b954eedeac495271d0F """, """ -foo: constant(address[1]) = [0x6b175474e89094c44da98b954eedeac495271d0F] - """, - """ @external def foo(): bar: address = 0x6b175474e89094c44da98b954eedeac495271d0F """, - """ -@external -def foo(): - bar: address[1] = [0x6b175474e89094c44da98b954eedeac495271d0F] - """, - """ -@external -def foo(): - for i: address in [0x6b175474e89094c44da98b954eedeac495271d0F]: - pass - """, ] @pytest.mark.parametrize("code", code_invalid_checksum) def test_bad_checksum_address(code, dummy_input_bundle): - vyper_module = vy_ast.parse_to_ast(code) - with pytest.raises(BadChecksumAddress): - semantics.validate_semantics(vyper_module, dummy_input_bundle) + vyper_module = vy_ast.parse_to_ast(code) + semantics.analyze_module(vyper_module, dummy_input_bundle) code_invalid_literal = [ """ +foo: constant(address[1]) = [0x6b175474e89094c44da98b954eedeac495271d0F] + """, + """ +@external +def foo(): + for i: address in [0x6b175474e89094c44da98b954eedeac495271d0F]: + pass + """, + """ +@external +def foo(): + bar: address[1] = [0x6b175474e89094c44da98b954eedeac495271d0F] + """, + """ foo: constant(bytes20) = 0x6b175474e89094c44da98b954eedeac495271d0F """, """ @@ -50,8 +49,6 @@ def test_bad_checksum_address(code, dummy_input_bundle): @pytest.mark.parametrize("code", code_invalid_literal) def test_invalid_literal(code, dummy_input_bundle): - vyper_module = vy_ast.parse_to_ast(code) - with pytest.raises(InvalidLiteral): vyper_module = vy_ast.parse_to_ast(code) - semantics.validate_semantics(vyper_module, dummy_input_bundle) + semantics.analyze_module(vyper_module, dummy_input_bundle)