Medium Risk
https://github.com/vyperlang/vyper/blob/3ba14124602b673d45b86bae7ff90a01d782acb5/vyper/builtins/functions.py#L460-L463
https://github.com/vyperlang/vyper/blob/3ba14124602b673d45b86bae7ff90a01d782acb5/vyper/builtins/_signatures.py#L82-L103
https://github.com/vyperlang/vyper/blob/3ba14124602b673d45b86bae7ff90a01d782acb5/vyper/semantics/analysis/utils.py#L527
When types are validated for literal lists passed to builtin functions, we perform the following check:
if not isinstance(expected, (DArrayT, SArrayT)):
However, in this scenario, expected
is the type class, not an instance, so it always fails. As a result, the compilation fails.
We will use the builtin len()
function to demonstrate this issue.
The len()
function accepts a single argument, which can be either a string, byte array or dynamic array:
_inputs = [("b", (StringT.any(), BytesT.any(), DArrayT.any()))]
All builtin functions implement the BuiltinFunction
class, which calls the _validate_arg_types()
function, which calls self._validate_single()
for all arguments.
In the case of the len()
function being called with a literal list, the argument passed to _validate_single()
is the list node, and the expected type is a tuple of the allowed type classes:
(<class 'vyper.semantics.types.bytestrings.StringT'>, <class 'vyper.semantics.types.bytestrings.BytesT'>, <class 'vyper.semantics.types.subscriptable.DArrayT'>)
This calls the validate_expected_type()
, where the given_types
returns all the possible types for the literal list.
In the event that the node is a literal list, we go down this code path:
# if it's a literal list, validate: expected contains array, lengths match, each item matches
if isinstance(node, vy_ast.List):
# special case - for literal arrays we individually validate each item
for expected in expected_type:
if not isinstance(expected, (DArrayT, SArrayT)):
continue
if _validate_literal_array(node, expected):
return
As we can see, this checks that isinstance(expected, (DArrayT, SArrayT))
. Only when this is the case does it proceed to the _validate_literal_array()
function, which allows us to return safely without an error.
Unfortunately, isinstance()
tells us if an instance fits a given type. But expected
is not an instance — it is the type class itself. As a result, this check will always fail, and the compilation will fail.
The following Vyper contracts will fail to compile due to this error:
# @version ^0.3.9
x: uint256
@external
def __init__():
self.x = len([1, 2, 3])
# @version ^0.3.9
number: public(uint256)
exampleList: constant(DynArray[uint256, 3]) = [1, 2, 3]
@external
def __init__():
self.number = len(exampleList)
Contracts that include literal lists as arguments to builtin functions will fail to compile.
Manual Review
In validate_expected_type()
, adjust the check to ensure that the expected type matches with DArrayT
or SArrayT
, rather than requiring it to be an instance of it.