Beispiel #1
0
    def unary_operations(self):
        operand = Expr.parse_value_expr(self.expr.operand, self.context)
        if isinstance(self.expr.op, sri_ast.Not):
            if isinstance(operand.typ, BaseType) and operand.typ.typ == 'bool':
                return LLLnode.from_list(["iszero", operand], typ='bool', pos=getpos(self.expr))
            else:
                raise TypeMismatch(
                    f"Only bool is supported for not operation, {operand.typ} supplied.",
                    self.expr,
                )
        elif isinstance(self.expr.op, sri_ast.USub):
            if not is_numeric_type(operand.typ):
                raise TypeMismatch(
                    f"Unsupported type for negation: {operand.typ}",
                    self.expr,
                )

            # Clamp on minimum integer value as we cannot negate that value
            # (all other integer values are fine)
            min_int_val = get_min_val_for_type(operand.typ.typ)
            return LLLnode.from_list(
                    ["sub", 0, ["clampgt", operand, min_int_val]],
                    typ=operand.typ,
                    pos=getpos(self.expr)
                )
        else:
            raise StructureException("Only the 'not' or 'neg' unary operators are supported")
Beispiel #2
0
    def variables(self):

        if self.expr.id == 'self':
            return LLLnode.from_list(['address'], typ='address', pos=getpos(self.expr))
        elif self.expr.id in self.context.vars:
            var = self.context.vars[self.expr.id]
            return LLLnode.from_list(
                var.pos,
                typ=var.typ,
                location=var.location,  # either 'memory' or 'calldata' storage is handled above.
                pos=getpos(self.expr),
                annotation=self.expr.id,
                mutable=var.mutable,
            )

        elif self.expr.id in BUILTIN_CONSTANTS:
            obj, typ = BUILTIN_CONSTANTS[self.expr.id]
            return LLLnode.from_list(
                [obj],
                typ=BaseType(typ, is_literal=True),
                pos=getpos(self.expr))
        elif self.context.constants.ast_is_constant(self.expr):
            return self.context.constants.get_constant(self.expr.id, self.context)
        else:
            raise VariableDeclarationException(f"Undeclared variable: {self.expr.id}", self.expr)
Beispiel #3
0
 def hexstring(self):
     orignum = self.expr.value
     if len(orignum) == 42:
         if checksum_encode(orignum) != orignum:
             raise InvalidLiteral(
                 "Address checksum mismatch. If you are sure this is the "
                 f"right address, the correct checksummed form is: {checksum_encode(orignum)}",
                 self.expr
             )
         return LLLnode.from_list(
             int(self.expr.value, 16),
             typ=BaseType('address', is_literal=True),
             pos=getpos(self.expr),
         )
     elif len(orignum) == 66:
         return LLLnode.from_list(
             int(self.expr.value, 16),
             typ=BaseType('bytes32', is_literal=True),
             pos=getpos(self.expr),
         )
     else:
         raise InvalidLiteral(
             f"Cannot read 0x value with length {len(orignum)}. Expecting 42 (address "
             "incl 0x) or 66 (bytes32 incl 0x)",
             self.expr
         )
Beispiel #4
0
def gen_tuple_return(stmt, context, sub):
    # Is from a call expression.
    if sub.args and len(sub.args[0].args) > 0 and sub.args[0].args[0].value == 'call':
        # self-call to public.
        mem_pos = sub
        mem_size = get_size_of_type(sub.typ) * 32
        return LLLnode.from_list(['return', mem_pos, mem_size], typ=sub.typ)

    elif (sub.annotation and 'Internal Call' in sub.annotation):
        mem_pos = sub.args[-1].value if sub.value == 'seq_unchecked' else sub.args[0].args[-1]
        mem_size = get_size_of_type(sub.typ) * 32
        # Add zero padder if bytes are present in output.
        zero_padder = ['pass']
        byte_arrays = [
            (i, x)
            for i, x
            in enumerate(sub.typ.tuple_members())
            if isinstance(x, ByteArrayLike)
        ]
        if byte_arrays:
            i, x = byte_arrays[-1]
            zero_padder = zero_pad(bytez_placeholder=[
                'add',
                mem_pos,
                ['mload', mem_pos + i * 32]
            ])
        return LLLnode.from_list(['seq'] + [sub] + [zero_padder] + [
            make_return_stmt(stmt, context, mem_pos, mem_size)
        ], typ=sub.typ, pos=getpos(stmt), valency=0)

    abi_typ = abi_type_of(context.return_type)
    # according to the ABI, return types are ALWAYS tuples even if
    # only one element is being returned.
    # https://solidity.readthedocs.io/en/latest/abi-spec.html#function-selector-and-argument-encoding
    # "and the return values v_1, ..., v_k of f are encoded as
    #
    #    enc((v_1, ..., v_k))
    #    i.e. the values are combined into a tuple and encoded.
    # "
    # therefore, wrap it in a tuple if it's not already a tuple.
    # (big difference between returning `(bytes,)` and `bytes`.
    abi_typ = ensure_tuple(abi_typ)
    abi_bytes_needed = abi_typ.static_size() + abi_typ.dynamic_size_bound()
    dst, _ = context.memory_allocator.increase_memory(abi_bytes_needed)
    return_buffer = LLLnode(
            dst,
            location='memory',
            annotation='return_buffer',
            typ=context.return_type)

    check_assign(return_buffer, sub, pos=getpos(stmt))

    encode_out = abi_encode(return_buffer, sub, pos=getpos(stmt), returns=True)
    load_return_len = ['mload', MemoryPositions.FREE_VAR_SPACE]
    os = ['seq',
          ['mstore', MemoryPositions.FREE_VAR_SPACE, encode_out],
          make_return_stmt(stmt, context, return_buffer, load_return_len)]
    return LLLnode.from_list(os, typ=None, pos=getpos(stmt), valency=0)
Beispiel #5
0
def unwrap_location(orig):
    if orig.location == 'memory':
        return LLLnode.from_list(['mload', orig], typ=orig.typ)
    elif orig.location == 'storage':
        return LLLnode.from_list(['sload', orig], typ=orig.typ)
    elif orig.location == 'calldata':
        return LLLnode.from_list(['calldataload', orig], typ=orig.typ)
    else:
        return orig
Beispiel #6
0
 def integer(self):
     # Literal (mostly likely) becomes int128
     if SizeLimits.in_bounds('int128', self.expr.n) or self.expr.n < 0:
         return LLLnode.from_list(
             self.expr.n,
             typ=BaseType('int128', is_literal=True),
             pos=getpos(self.expr),
         )
     # Literal is large enough (mostly likely) becomes uint256.
     else:
         return LLLnode.from_list(
             self.expr.n,
             typ=BaseType('uint256', is_literal=True),
             pos=getpos(self.expr),
         )
Beispiel #7
0
def get_sig_statements(sig, pos):
    method_id_node = LLLnode.from_list(sig.method_id,
                                       pos=pos,
                                       annotation=f'{sig.sig}')

    if sig.private:
        sig_compare = 0
        private_label = LLLnode.from_list(['label', f'priv_{sig.method_id}'],
                                          pos=pos,
                                          annotation=f'{sig.sig}')
    else:
        sig_compare = ['eq', ['mload', 0], method_id_node]
        private_label = ['pass']

    return sig_compare, private_label
Beispiel #8
0
    def boolean_operations(self):
        # Iterate through values
        for value in self.expr.values:
            # Check for calls at assignment
            if self.context.in_assignment and isinstance(value, sri_ast.Call):
                raise StructureException(
                    "Boolean operations with calls may not be performed on assignment",
                    self.expr,
                )

            # Check for boolean operations with non-boolean inputs
            _expr = Expr.parse_value_expr(value, self.context)
            if not is_base_type(_expr.typ, 'bool'):
                raise TypeMismatch(
                    "Boolean operations can only be between booleans!",
                    self.expr,
                )

            # TODO: Handle special case of literals and simplify at compile time

        # Check for valid ops
        if isinstance(self.expr.op, sri_ast.And):
            op = 'and'
        elif isinstance(self.expr.op, sri_ast.Or):
            op = 'or'
        else:
            raise Exception("Unsupported bool op: " + self.expr.op)

        # Handle different numbers of inputs
        count = len(self.expr.values)
        if count < 2:
            raise StructureException("Expected at least two arguments for a bool op", self.expr)
        elif count == 2:
            left = Expr.parse_value_expr(self.expr.values[0], self.context)
            right = Expr.parse_value_expr(self.expr.values[1], self.context)
            return LLLnode.from_list([op, left, right], typ='bool', pos=getpos(self.expr))
        else:
            left = Expr.parse_value_expr(self.expr.values[0], self.context)
            right = Expr.parse_value_expr(self.expr.values[1], self.context)

            p = ['seq', [op, left, right]]
            values = self.expr.values[2:]
            while len(values) > 0:
                value = Expr.parse_value_expr(values[0], self.context)
                p = [op, value, p]
                values = values[1:]

            return LLLnode.from_list(p, typ='bool', pos=getpos(self.expr))
Beispiel #9
0
    def list_literals(self):

        if not len(self.expr.elts):
            raise StructureException("List must have elements", self.expr)

        def get_out_type(lll_node):
            if isinstance(lll_node, ListType):
                return get_out_type(lll_node.subtype)
            return lll_node.typ

        o = []
        previous_type = None
        out_type = None

        for elt in self.expr.elts:
            current_lll_node = Expr(elt, self.context).lll_node
            if not out_type:
                out_type = current_lll_node.typ

            current_type = get_out_type(current_lll_node)
            if len(o) > 0 and previous_type != current_type:
                raise TypeMismatch("Lists may only contain one type", self.expr)
            else:
                o.append(current_lll_node)
                previous_type = current_type

        return LLLnode.from_list(
            ["multi"] + o,
            typ=ListType(out_type, len(o)),
            pos=getpos(self.expr),
        )
Beispiel #10
0
def mzero(dst, nbytes):
    # calldatacopy from past-the-end gives zero bytes.
    # cf. YP H.2 (ops section) with CALLDATACOPY spec.
    return LLLnode.from_list(
        # calldatacopy mempos calldatapos len
        ['calldatacopy', dst, 'calldatasize', nbytes],
        annotation="mzero")
Beispiel #11
0
def lazy_abi_decode(typ, src, pos=None):
    if isinstance(typ, (ListType, TupleLike)):
        if isinstance(typ, TupleLike):
            ts = typ.tuple_members()
        else:
            ts = [typ.subtyp for _ in range(typ.count)]
        ofst = 0
        os = []
        for t in ts:
            child_abi_t = abi_type_of(t)
            loc = _add_ofst(src, ofst)
            if child_abi_t.is_dynamic():
                # load the offset word, which is the
                # (location-independent) offset from the start of the
                # src buffer.
                dyn_ofst = unwrap_location(ofst)
                loc = _add_ofst(src, dyn_ofst)
            os.append(lazy_abi_decode(t, loc, pos))
            ofst += child_abi_t.embedded_static_size()

        return LLLnode.from_list(['multi'] + os, typ=typ, pos=pos)

    elif isinstance(typ, (BaseType, ByteArrayLike)):
        return unwrap_location(src)
    else:
        raise CompilerPanic(f'unknown type for lazy_abi_decode {typ}')
Beispiel #12
0
def abi_decode(lll_node, src, pos=None):
    os = o_list(lll_node, pos=pos)
    lll_ret = []
    src_ptr = 'src'  # pointer to beginning of buffer
    src_loc = 'src_loc'  # pointer to read location in static section
    parent_abi_t = abi_type_of(src.typ)
    for i, o in enumerate(os):
        abi_t = abi_type_of(o.typ)
        src_loc = LLLnode('src_loc', typ=o.typ, location=src.location)
        if parent_abi_t.is_tuple():
            if abi_t.is_dynamic():
                child_loc = ['add', src_ptr, unwrap_location(src_loc)]
            else:
                child_loc = src_loc
            # descend into the child tuple
            lll_ret.append(abi_decode(o, child_loc, pos=pos))
        else:
            lll_ret.append(
                make_setter(o, src_loc, location=o.location, pos=pos))

        if i + 1 == len(os):
            pass  # optimize out the last pointer increment
        else:
            sz = abi_t.embedded_static_size()
            lll_ret.append(['set', src_loc, ['add', src_loc, sz]])

    lll_ret = [
        'with', 'src', src, ['with', 'src_loc', 'src', ['seq', lll_ret]]
    ]

    return lll_ret
Beispiel #13
0
 def struct_literals(expr, name, context):
     member_subs = {}
     member_typs = {}
     for key, value in zip(expr.keys, expr.values):
         if not isinstance(key, sri_ast.Name):
             raise TypeMismatch(
                 f"Invalid member variable for struct: {getattr(key, 'id', '')}",
                 key,
             )
         check_valid_varname(
             key.id,
             context.structs,
             context.constants,
             "Invalid member variable for struct",
         )
         if key.id in member_subs:
             raise TypeMismatch("Member variable duplicated: " + key.id, key)
         sub = Expr(value, context).lll_node
         member_subs[key.id] = sub
         member_typs[key.id] = sub.typ
     return LLLnode.from_list(
         ["multi"] + [member_subs[key] for key in member_subs.keys()],
         typ=StructType(member_typs, name, is_literal=True),
         pos=getpos(expr),
     )
Beispiel #14
0
 def tuple_literals(self):
     if not len(self.expr.elts):
         raise StructureException("Tuple must have elements", self.expr)
     o = []
     for elt in self.expr.elts:
         o.append(Expr(elt, self.context).lll_node)
     typ = TupleType([x.typ for x in o], is_literal=True)
     return LLLnode.from_list(["multi"] + o, typ=typ, pos=getpos(self.expr))
Beispiel #15
0
def base_type_conversion(orig, frm, to, pos, in_function_call=False):
    orig = unwrap_location(orig)

    # do the base type check so we can use BaseType attributes
    if not isinstance(frm, BaseType) or not isinstance(to, BaseType):
        raise TypeMismatch(
            f"Base type conversion from or to non-base type: {frm} {to}", pos)

    if getattr(frm, 'is_literal', False):
        if frm.typ in ('int128', 'uint256'):
            if not SizeLimits.in_bounds(frm.typ, orig.value):
                raise InvalidLiteral(f"Number out of range: {orig.value}", pos)

        if to.typ in ('int128', 'uint256'):
            if not SizeLimits.in_bounds(to.typ, orig.value):
                raise InvalidLiteral(f"Number out of range: {orig.value}", pos)

    is_decimal_int128_conversion = frm.typ == 'int128' and to.typ == 'decimal'
    is_same_type = frm.typ == to.typ
    is_literal_conversion = frm.is_literal and (frm.typ, to.typ) == ('int128',
                                                                     'uint256')
    is_address_conversion = isinstance(frm,
                                       ContractType) and to.typ == 'address'
    if not (is_same_type or is_literal_conversion or is_address_conversion
            or is_decimal_int128_conversion):
        raise TypeMismatch(
            f"Typecasting from base type {frm} to {to} unavailable", pos)

    # handle None value inserted by `empty()`
    if orig.value is None:
        return LLLnode.from_list(0, typ=to)

    if is_decimal_int128_conversion:
        return LLLnode.from_list(
            ['mul', orig, DECIMAL_DIVISOR],
            typ=BaseType('decimal'),
        )

    return LLLnode(orig.value,
                   orig.args,
                   typ=to,
                   add_gas_estimate=orig.add_gas_estimate)
Beispiel #16
0
 def constants(self):
     if self.expr.value is True:
         return LLLnode.from_list(
             1,
             typ=BaseType('bool', is_literal=True),
             pos=getpos(self.expr),
         )
     elif self.expr.value is False:
         return LLLnode.from_list(
             0,
             typ=BaseType('bool', is_literal=True),
             pos=getpos(self.expr),
         )
     elif self.expr.value is None:
         # block None
         raise InvalidLiteral(
                 'None is not allowed in srilang'
                 '(use a default value or built-in `empty()`')
     else:
         raise Exception(f"Unknown name constant: {self.expr.value.value}")
Beispiel #17
0
def byte_array_to_num(
    arg,
    expr,
    out_type,
    offset=32,
):
    if arg.location == "memory":
        lengetter = LLLnode.from_list(['mload', '_sub'],
                                      typ=BaseType('int128'))
        first_el_getter = LLLnode.from_list(['mload', ['add', 32, '_sub']],
                                            typ=BaseType('int128'))
    elif arg.location == "storage":
        lengetter = LLLnode.from_list(['sload', ['sha3_32', '_sub']],
                                      typ=BaseType('int128'))
        first_el_getter = LLLnode.from_list(
            ['sload', ['add', 1, ['sha3_32', '_sub']]], typ=BaseType('int128'))
    if out_type == 'int128':
        result = [
            'clamp', ['mload', MemoryPositions.MINNUM],
            ['div', '_el1', ['exp', 256, ['sub', 32, '_len']]],
            ['mload', MemoryPositions.MAXNUM]
        ]
    elif out_type == 'uint256':
        result = ['div', '_el1', ['exp', 256, ['sub', offset, '_len']]]
    return LLLnode.from_list([
        'with', '_sub', arg,
        [
            'with', '_el1', first_el_getter,
            [
                'with',
                '_len',
                ['clamp', 0, lengetter, 32],
                result,
            ]
        ]
    ],
                             typ=BaseType(out_type),
                             annotation=f'bytearray to number ({out_type})')
Beispiel #18
0
def keccak256_helper(expr, args, kwargs, context):
    sub = args[0]
    # Can hash literals
    if isinstance(sub, bytes):
        return LLLnode.from_list(bytes_to_int(keccak256(sub)),
                                 typ=BaseType('bytes32'),
                                 pos=getpos(expr))
    # Can hash bytes32 objects
    if is_base_type(sub.typ, 'bytes32'):
        return LLLnode.from_list(
            [
                'seq', ['mstore', MemoryPositions.FREE_VAR_SPACE, sub],
                ['sha3', MemoryPositions.FREE_VAR_SPACE, 32]
            ],
            typ=BaseType('bytes32'),
            pos=getpos(expr),
        )
    # Copy the data to an in-memory array
    if sub.location == "memory":
        # If we are hashing a value in memory, no need to copy it, just hash in-place
        return LLLnode.from_list(
            [
                'with', '_sub', sub,
                ['sha3', ['add', '_sub', 32], ['mload', '_sub']]
            ],
            typ=BaseType('bytes32'),
            pos=getpos(expr),
        )
    elif sub.location == "storage":
        lengetter = LLLnode.from_list(['sload', ['sha3_32', '_sub']],
                                      typ=BaseType('int128'))
    else:
        # This should never happen, but just left here for future compiler-writers.
        raise Exception(
            f"Unsupported location: {sub.location}")  # pragma: no test
    placeholder = context.new_placeholder(sub.typ)
    placeholder_node = LLLnode.from_list(placeholder,
                                         typ=sub.typ,
                                         location='memory')
    copier = make_byte_array_copier(
        placeholder_node,
        LLLnode.from_list('_sub', typ=sub.typ, location=sub.location),
    )
    return LLLnode.from_list([
        'with',
        '_sub',
        sub,
        ['seq', copier, ['sha3', ['add', placeholder, 32], lengetter]],
    ],
                             typ=BaseType('bytes32'),
                             pos=getpos(expr))
Beispiel #19
0
def o_list(lll_node, pos=None):
    lll_t = lll_node.typ
    if isinstance(lll_t, (TupleLike, ListType)):
        if lll_node.value == 'multi':  # is literal
            ret = lll_node.args
        else:
            ks = lll_t.tuple_keys() if isinstance(lll_t, TupleLike) else \
                    [LLLnode.from_list(i, 'uint256') for i in range(lll_t.count)]

            ret = [
                add_variable_offset(lll_node, k, pos, array_bounds_check=False)
                for k in ks
            ]
        return ret
    else:
        return [lll_node]
Beispiel #20
0
 def decimal(self):
     numstring, num, den = get_number_as_fraction(self.expr, self.context)
     # if not SizeLimits.in_bounds('decimal', num // den):
     # if not SizeLimits.MINDECIMAL * den <= num <= SizeLimits.MAXDECIMAL * den:
     if not (SizeLimits.MINNUM * den <= num <= SizeLimits.MAXNUM * den):
         raise InvalidLiteral("Number out of range: " + numstring, self.expr)
     if DECIMAL_DIVISOR % den:
         raise InvalidLiteral(
             "Type 'decimal' has maximum 10 decimal places",
             self.expr
         )
     return LLLnode.from_list(
         num * DECIMAL_DIVISOR // den,
         typ=BaseType('decimal', is_literal=True),
         pos=getpos(self.expr),
     )
Beispiel #21
0
 def _make_bytelike(self, btype, bytez, bytez_length):
     placeholder = self.context.new_placeholder(btype)
     seq = []
     seq.append(['mstore', placeholder, bytez_length])
     for i in range(0, len(bytez), 32):
         seq.append([
             'mstore',
             ['add', placeholder, i + 32],
             bytes_to_int((bytez + b'\x00' * 31)[i: i + 32])
         ])
     return LLLnode.from_list(
         ['seq'] + seq + [placeholder],
         typ=btype,
         location='memory',
         pos=getpos(self.expr),
         annotation=f'Create {btype}: {bytez}',
     )
Beispiel #22
0
def zero_pad(bytez_placeholder):
    len_ = ['mload', bytez_placeholder]
    dst = ['add', ['add', bytez_placeholder, 32], 'len']
    # the runtime length of the data rounded up to nearest 32
    # from spec:
    #   the actual value of X as a byte sequence,
    #   followed by the *minimum* number of zero-bytes
    #   such that len(enc(X)) is a multiple of 32.
    num_zero_bytes = ['sub', ['ceil32', 'len'], 'len']
    return LLLnode.from_list(
        [
            'with', 'len', len_,
            ['with', 'dst', dst,
             mzero('dst', num_zero_bytes)]
        ],
        annotation="Zero pad",
    )
Beispiel #23
0
def parse_other_functions(o, otherfuncs, sigs, external_contracts, origcode,
                          global_ctx, default_function):
    sub = ['seq', func_init_lll()]
    add_gas = func_init_lll().gas

    for _def in otherfuncs:
        sub.append(
            parse_function(_def, {
                **{
                    'self': sigs
                },
                **external_contracts
            }, origcode, global_ctx))
        sub[-1].total_gas += add_gas
        add_gas += 30
        for sig in sig_utils.generate_default_arg_sigs(_def,
                                                       external_contracts,
                                                       global_ctx):
            sig.gas = sub[-1].total_gas
            sigs[sig.sig] = sig

    # Add fallback function
    if default_function:
        default_func = parse_function(
            default_function[0],
            {
                **{
                    'self': sigs
                },
                **external_contracts
            },
            origcode,
            global_ctx,
        )
        fallback = default_func
    else:
        fallback = LLLnode.from_list(['revert', 0, 0],
                                     typ=None,
                                     annotation='Default function')
    sub.append(['seq_unchecked', ['label', 'fallback'], fallback])
    o.append(['return', 0, ['lll', sub, 0]])
    return o, sub
Beispiel #24
0
def _add_ofst(loc, ofst):
    if isinstance(loc.value, int):
        return LLLnode(loc.value + ofst)
    return ['add', loc, ofst]
Beispiel #25
0
def pack_arguments(signature,
                   args,
                   context,
                   stmt_expr,
                   return_placeholder=True):
    pos = getpos(stmt_expr)
    placeholder_typ = ByteArrayType(
        maxlen=sum([get_size_of_type(arg.typ)
                    for arg in signature.args]) * 32 + 32)
    placeholder = context.new_placeholder(placeholder_typ)
    setters = [['mstore', placeholder, signature.method_id]]
    needpos = False
    staticarray_offset = 0
    expected_arg_count = len(signature.args)
    actual_arg_count = len(args)
    if actual_arg_count != expected_arg_count:
        raise StructureException(
            f"Wrong number of args for: {signature.name} "
            f"({actual_arg_count} args given, expected {expected_arg_count}",
            stmt_expr)

    for i, (arg,
            typ) in enumerate(zip(args, [arg.typ for arg in signature.args])):
        if isinstance(typ, BaseType):
            setters.append(
                make_setter(LLLnode.from_list(
                    placeholder + staticarray_offset + 32 + i * 32,
                    typ=typ,
                ),
                            arg,
                            'memory',
                            pos=pos,
                            in_function_call=True))

        elif isinstance(typ, ByteArrayLike):
            setters.append([
                'mstore', placeholder + staticarray_offset + 32 + i * 32,
                '_poz'
            ])
            arg_copy = LLLnode.from_list('_s',
                                         typ=arg.typ,
                                         location=arg.location)
            target = LLLnode.from_list(
                ['add', placeholder + 32, '_poz'],
                typ=typ,
                location='memory',
            )
            setters.append([
                'with',
                '_s',
                arg,
                [
                    'seq',
                    make_byte_array_copier(target, arg_copy, pos),
                    [
                        'set', '_poz',
                        [
                            'add', 32,
                            ['ceil32', ['add', '_poz',
                                        get_length(arg_copy)]]
                        ]
                    ],
                ],
            ])
            needpos = True

        elif isinstance(typ, (StructType, ListType)):
            if has_dynamic_data(typ):
                raise TypeMismatch("Cannot pack bytearray in struct",
                                   stmt_expr)
            target = LLLnode.from_list(
                [placeholder + 32 + staticarray_offset + i * 32],
                typ=typ,
                location='memory',
            )
            setters.append(make_setter(target, arg, 'memory', pos=pos))
            if (isinstance(typ, ListType)):
                count = typ.count
            else:
                count = len(typ.tuple_items())
            staticarray_offset += 32 * (count - 1)

        else:
            raise TypeMismatch(f"Cannot pack argument of type {typ}",
                               stmt_expr)

    # For private call usage, doesn't use a returner.
    returner = [[placeholder + 28]] if return_placeholder else []
    if needpos:
        return (LLLnode.from_list([
            'with', '_poz',
            len(args) * 32 + staticarray_offset, ['seq'] + setters + returner
        ],
                                  typ=placeholder_typ,
                                  location='memory'),
                placeholder_typ.maxlen - 28, placeholder + 32)
    else:
        return (LLLnode.from_list(['seq'] + setters + returner,
                                  typ=placeholder_typ,
                                  location='memory'),
                placeholder_typ.maxlen - 28, placeholder + 32)
Beispiel #26
0
def make_setter(left, right, location, pos, in_function_call=False):
    # Basic types
    if isinstance(left.typ, BaseType):
        right = base_type_conversion(
            right,
            right.typ,
            left.typ,
            pos,
            in_function_call=in_function_call,
        )
        if location == 'storage':
            return LLLnode.from_list(['sstore', left, right], typ=None)
        elif location == 'memory':
            return LLLnode.from_list(['mstore', left, right], typ=None)
    # Byte arrays
    elif isinstance(left.typ, ByteArrayLike):
        return make_byte_array_copier(left, right, pos)
    # Can't copy mappings
    elif isinstance(left.typ, MappingType):
        raise TypeMismatch(
            "Cannot copy mappings; can only copy individual elements", pos)
    # Arrays
    elif isinstance(left.typ, ListType):
        # Cannot do something like [a, b, c] = [1, 2, 3]
        if left.value == "multi":
            raise Exception("Target of set statement must be a single item")

        if not isinstance(right.typ, ListType):
            raise TypeMismatch(
                f"Setter type mismatch: left side is {left.typ}, right side is {right.typ}",
                pos)
        if right.typ.count != left.typ.count:
            raise TypeMismatch("Mismatched number of elements", pos)

        left_token = LLLnode.from_list('_L',
                                       typ=left.typ,
                                       location=left.location)
        if left.location == "storage":
            left = LLLnode.from_list(['sha3_32', left],
                                     typ=left.typ,
                                     location="storage_prehashed")
            left_token.location = "storage_prehashed"
        # If the right side is a literal
        if right.value == "multi":
            subs = []
            for i in range(left.typ.count):
                subs.append(
                    make_setter(add_variable_offset(
                        left_token,
                        LLLnode.from_list(i, typ='int128'),
                        pos=pos,
                        array_bounds_check=False,
                    ),
                                right.args[i],
                                location,
                                pos=pos))
            return LLLnode.from_list(['with', '_L', left, ['seq'] + subs],
                                     typ=None)
        elif right.value is None:
            if right.typ != left.typ:
                raise TypeMismatch(
                    f"left side is {left.typ}, right side is {right.typ}", pos)
            if left.location == 'memory':
                return mzero(left, 32 * get_size_of_type(left.typ))

            subs = []
            for i in range(left.typ.count):
                subs.append(
                    make_setter(add_variable_offset(
                        left_token,
                        LLLnode.from_list(i, typ='int128'),
                        pos=pos,
                        array_bounds_check=False,
                    ),
                                LLLnode.from_list(None, typ=right.typ.subtype),
                                location,
                                pos=pos))
            return LLLnode.from_list(['with', '_L', left, ['seq'] + subs],
                                     typ=None)
        # If the right side is a variable
        else:
            right_token = LLLnode.from_list('_R',
                                            typ=right.typ,
                                            location=right.location)
            subs = []
            for i in range(left.typ.count):
                subs.append(
                    make_setter(add_variable_offset(
                        left_token,
                        LLLnode.from_list(i, typ='int128'),
                        pos=pos,
                        array_bounds_check=False,
                    ),
                                add_variable_offset(
                                    right_token,
                                    LLLnode.from_list(i, typ='int128'),
                                    pos=pos,
                                    array_bounds_check=False,
                                ),
                                location,
                                pos=pos))
            return LLLnode.from_list(
                ['with', '_L', left, ['with', '_R', right, ['seq'] + subs]],
                typ=None)
    # Structs
    elif isinstance(left.typ, TupleLike):
        if left.value == "multi" and isinstance(left.typ, StructType):
            raise Exception("Target of set statement must be a single item")
        if right.value is not None:
            if not isinstance(right.typ, left.typ.__class__):
                raise TypeMismatch(
                    f"Setter type mismatch: left side is {left.typ}, right side is {right.typ}",
                    pos,
                )
            if isinstance(left.typ, StructType):
                for k in left.typ.members:
                    if k not in right.typ.members:
                        raise TypeMismatch(
                            f"Keys don't match for structs, missing {k}",
                            pos,
                        )
                for k in right.typ.members:
                    if k not in left.typ.members:
                        raise TypeMismatch(
                            f"Keys don't match for structs, extra {k}",
                            pos,
                        )
                if left.typ.name != right.typ.name:
                    raise TypeMismatch(f"Expected {left.typ}, got {right.typ}",
                                       pos)
            else:
                if len(left.typ.members) != len(right.typ.members):
                    raise TypeMismatch(
                        "Tuple lengths don't match, "
                        f"{len(left.typ.members)} vs {len(right.typ.members)}",
                        pos,
                    )

        left_token = LLLnode.from_list('_L',
                                       typ=left.typ,
                                       location=left.location)
        if left.location == "storage":
            left = LLLnode.from_list(['sha3_32', left],
                                     typ=left.typ,
                                     location="storage_prehashed")
            left_token.location = "storage_prehashed"
        keyz = left.typ.tuple_keys()

        # If the left side is a literal
        if left.value == 'multi':
            locations = [arg.location for arg in left.args]
        else:
            locations = [location for _ in keyz]

        # If the right side is a literal
        if right.value == "multi":
            if len(right.args) != len(keyz):
                raise TypeMismatch("Mismatched number of elements", pos)
            # get the RHS arguments into a dict because
            # they are not guaranteed to be in the same order
            # the LHS keys.
            right_args = dict(zip(right.typ.tuple_keys(), right.args))
            subs = []
            for (key, loc) in zip(keyz, locations):
                subs.append(
                    make_setter(
                        add_variable_offset(left_token, key, pos=pos),
                        right_args[key],
                        loc,
                        pos=pos,
                    ))
            return LLLnode.from_list(['with', '_L', left, ['seq'] + subs],
                                     typ=None)
        # If the right side is a null
        elif right.value is None:
            if left.typ != right.typ:
                raise TypeMismatch(
                    f"left side is {left.typ}, right side is {right.typ}", pos)

            if left.location == 'memory':
                return mzero(left, 32 * get_size_of_type(left.typ))

            subs = []
            for key, loc in zip(keyz, locations):
                subs.append(
                    make_setter(
                        add_variable_offset(left_token, key, pos=pos),
                        LLLnode.from_list(None, typ=right.typ.members[key]),
                        loc,
                        pos=pos,
                    ))
            return LLLnode.from_list(['with', '_L', left, ['seq'] + subs],
                                     typ=None)
        # If tuple assign.
        elif isinstance(left.typ, TupleType) and isinstance(
                right.typ, TupleType):
            subs = []
            static_offset_counter = 0
            zipped_components = zip(left.args, right.typ.members, locations)
            for var_arg in left.args:
                if var_arg.location == 'calldata':
                    raise ConstancyViolation(
                        f"Cannot modify function argument: {var_arg.annotation}",
                        pos)
            for left_arg, right_arg, loc in zipped_components:
                if isinstance(right_arg, ByteArrayLike):
                    RType = ByteArrayType if isinstance(
                        right_arg, ByteArrayType) else StringType
                    offset = LLLnode.from_list([
                        'add', '_R',
                        ['mload', ['add', '_R', static_offset_counter]]
                    ],
                                               typ=RType(right_arg.maxlen),
                                               location='memory',
                                               pos=pos)
                    static_offset_counter += 32
                else:
                    offset = LLLnode.from_list(
                        ['mload', ['add', '_R', static_offset_counter]],
                        typ=right_arg.typ,
                        pos=pos,
                    )
                    static_offset_counter += get_size_of_type(right_arg) * 32
                subs.append(make_setter(left_arg, offset, loc, pos=pos))
            return LLLnode.from_list(
                ['with', '_R', right, ['seq'] + subs],
                typ=None,
                annotation='Tuple assignment',
            )
        # If the right side is a variable
        else:
            subs = []
            right_token = LLLnode.from_list('_R',
                                            typ=right.typ,
                                            location=right.location)
            for typ, loc in zip(keyz, locations):
                subs.append(
                    make_setter(add_variable_offset(left_token, typ, pos=pos),
                                add_variable_offset(right_token, typ, pos=pos),
                                loc,
                                pos=pos))
            return LLLnode.from_list(
                ['with', '_L', left, ['with', '_R', right, ['seq'] + subs]],
                typ=None,
            )
    else:
        raise Exception("Invalid type for setters")
Beispiel #27
0
def abi_encode(dst, lll_node, pos=None, bufsz=None, returns=False):
    parent_abi_t = abi_type_of(lll_node.typ)
    size_bound = parent_abi_t.static_size() + parent_abi_t.dynamic_size_bound()
    if bufsz is not None and bufsz < 32 * size_bound:
        raise CompilerPanic('buffer provided to abi_encode not large enough')

    lll_ret = ['seq']
    dyn_ofst = 'dyn_ofst'  # current offset in the dynamic section
    dst_begin = 'dst'  # pointer to beginning of buffer
    dst_loc = 'dst_loc'  # pointer to write location in static section
    os = o_list(lll_node, pos=pos)

    for i, o in enumerate(os):
        abi_t = abi_type_of(o.typ)

        if parent_abi_t.is_tuple():
            if abi_t.is_dynamic():
                lll_ret.append(['mstore', dst_loc, dyn_ofst])
                # recurse
                child_dst = ['add', dst_begin, dyn_ofst]
                child = abi_encode(child_dst, o, pos=pos, returns=True)
                # increment dyn ofst for the return
                # (optimization note:
                #   if non-returning and this is the last dyn member in
                #   the tuple, this set can be elided.)
                lll_ret.append(['set', dyn_ofst, ['add', dyn_ofst, child]])
            else:
                # recurse
                lll_ret.append(abi_encode(dst_loc, o, pos=pos, returns=False))

        elif isinstance(o.typ, BaseType):
            d = LLLnode(dst_loc, typ=o.typ, location='memory')
            lll_ret.append(make_setter(d, o, location=d.location, pos=pos))
        elif isinstance(o.typ, ByteArrayLike):
            d = LLLnode.from_list(dst_loc, typ=o.typ, location='memory')
            lll_ret.append([
                'seq',
                make_setter(d, o, location=d.location, pos=pos),
                zero_pad(d)
            ])
        else:
            raise CompilerPanic(f'unreachable type: {o.typ}')

        if i + 1 == len(os):
            pass  # optimize out the last increment to dst_loc
        else:  # note: always false for non-tuple types
            sz = abi_t.embedded_static_size()
            lll_ret.append(['set', dst_loc, ['add', dst_loc, sz]])

    # declare LLL variables.
    if returns:
        if not parent_abi_t.is_dynamic():
            lll_ret.append(parent_abi_t.embedded_static_size())
        elif parent_abi_t.is_tuple():
            lll_ret.append('dyn_ofst')
        elif isinstance(lll_node.typ, ByteArrayLike):
            # for abi purposes, return zero-padded length
            calc_len = ['ceil32', ['add', 32, ['mload', dst_loc]]]
            lll_ret.append(calc_len)
        else:
            raise CompilerPanic('unknown type {lll_node.typ}')

    if not (parent_abi_t.is_dynamic() and parent_abi_t.is_tuple()):
        pass  # optimize out dyn_ofst allocation if we don't need it
    else:
        dyn_section_start = parent_abi_t.static_size()
        lll_ret = ['with', 'dyn_ofst', dyn_section_start, lll_ret]

    lll_ret = ['with', dst_begin, dst, ['with', dst_loc, dst_begin, lll_ret]]

    return LLLnode.from_list(lll_ret)
Beispiel #28
0
def make_byte_array_copier(destination, source, pos=None):
    if not isinstance(source.typ, ByteArrayLike):
        btype = 'byte array' if isinstance(destination.typ,
                                           ByteArrayType) else 'string'
        raise TypeMismatch(f"Can only set a {btype} to another {btype}", pos)
    if isinstance(
            source.typ,
            ByteArrayLike) and source.typ.maxlen > destination.typ.maxlen:
        raise TypeMismatch(
            f"Cannot cast from greater max-length {source.typ.maxlen} to shorter "
            f"max-length {destination.typ.maxlen}")

    # stricter check for zeroing a byte array.
    if isinstance(source.typ, ByteArrayLike):
        if source.value is None and source.typ.maxlen != destination.typ.maxlen:
            raise TypeMismatch(
                f"Bad type for clearing bytes: expected {destination.typ}"
                f" but got {source.typ}")

    # Special case: memory to memory
    if source.location == "memory" and destination.location == "memory":
        gas_calculation = GAS_IDENTITY + GAS_IDENTITYWORD * (
            ceil32(source.typ.maxlen) // 32)
        o = LLLnode.from_list(
            [
                'with', '_source', source,
                [
                    'with', '_sz', ['add', 32, ['mload', '_source']],
                    [
                        'assert',
                        [
                            'call', ['gas'], 4, 0, '_source', '_sz',
                            destination, '_sz'
                        ]
                    ]
                ]
            ],  # noqa: E501
            typ=None,
            add_gas_estimate=gas_calculation,
            annotation='Memory copy')
        return o

    if source.value is None:
        pos_node = source
    else:
        pos_node = LLLnode.from_list('_pos',
                                     typ=source.typ,
                                     location=source.location)
    # Get the length
    if source.value is None:
        length = 1
    elif source.location == "memory":
        length = ['add', ['mload', '_pos'], 32]
    elif source.location == "storage":
        length = ['add', ['sload', '_pos'], 32]
        pos_node = LLLnode.from_list(
            ['sha3_32', pos_node],
            typ=source.typ,
            location=source.location,
        )
    else:
        raise CompilerPanic(f"Unsupported location: {source.location}")
    if destination.location == "storage":
        destination = LLLnode.from_list(
            ['sha3_32', destination],
            typ=destination.typ,
            location=destination.location,
        )
    # Maximum theoretical length
    max_length = 32 if source.value is None else source.typ.maxlen + 32
    return LLLnode.from_list([
        'with', '_pos', 0 if source.value is None else source,
        make_byte_slice_copier(
            destination, pos_node, length, max_length, pos=pos)
    ],
                             typ=None)
Beispiel #29
0
def parse_private_function(code: ast.FunctionDef, sig: FunctionSignature,
                           context: Context) -> LLLnode:
    """
    Parse a private function (FuncDef), and produce full function body.

    :param sig: the FuntionSignature
    :param code: ast of function
    :return: full sig compare & function body
    """

    validate_private_function(code, sig)

    # Get nonreentrant lock
    nonreentrant_pre, nonreentrant_post = get_nonreentrant_lock(
        sig, context.global_ctx)

    # Create callback_ptr, this stores a destination in the bytecode for a private
    # function to jump to after a function has executed.
    clampers: List[LLLnode] = []

    # Allocate variable space.
    context.memory_allocator.increase_memory(sig.max_copy_size)

    _post_callback_ptr = f"{sig.name}_{sig.method_id}_post_callback_ptr"
    context.callback_ptr = context.new_placeholder(typ=BaseType('uint256'))
    clampers.append(
        LLLnode.from_list(
            ['mstore', context.callback_ptr, 'pass'],
            annotation='pop callback pointer',
        ))
    if sig.total_default_args > 0:
        clampers.append(LLLnode.from_list(['label', _post_callback_ptr]))

    # private functions without return types need to jump back to
    # the calling function, as there is no return statement to handle the
    # jump.
    if sig.output_type is None:
        stop_func = [['jump', ['mload', context.callback_ptr]]]
    else:
        stop_func = [['stop']]

    # Generate copiers
    if len(sig.base_args) == 0:
        copier = ['pass']
        clampers.append(LLLnode.from_list(copier))
    elif sig.total_default_args == 0:
        copier = get_private_arg_copier(
            total_size=sig.base_copy_size,
            memory_dest=MemoryPositions.RESERVED_MEMORY)
        clampers.append(LLLnode.from_list(copier))

    # Fill variable positions
    for arg in sig.args:
        if isinstance(arg.typ, ByteArrayLike):
            mem_pos, _ = context.memory_allocator.increase_memory(
                32 * get_size_of_type(arg.typ))
            context.vars[arg.name] = VariableRecord(arg.name, mem_pos, arg.typ,
                                                    False)
        else:
            context.vars[arg.name] = VariableRecord(
                arg.name,
                MemoryPositions.RESERVED_MEMORY + arg.pos,
                arg.typ,
                False,
            )

    # Private function copiers. No clamping for private functions.
    dyn_variable_names = [
        a.name for a in sig.base_args if isinstance(a.typ, ByteArrayLike)
    ]
    if dyn_variable_names:
        i_placeholder = context.new_placeholder(typ=BaseType('uint256'))
        unpackers: List[Any] = []
        for idx, var_name in enumerate(dyn_variable_names):
            var = context.vars[var_name]
            ident = f"_load_args_{sig.method_id}_dynarg{idx}"
            o = make_unpacker(ident=ident,
                              i_placeholder=i_placeholder,
                              begin_pos=var.pos)
            unpackers.append(o)

        if not unpackers:
            unpackers = ['pass']

        # 0 added to complete full overarching 'seq' statement, see private_label.
        unpackers.append(0)
        clampers.append(
            LLLnode.from_list(
                ['seq_unchecked'] + unpackers,
                typ=None,
                annotation='dynamic unpacker',
                pos=getpos(code),
            ))

    # Function has default arguments.
    if sig.total_default_args > 0:  # Function with default parameters.

        default_sigs = sig_utils.generate_default_arg_sigs(
            code, context.sigs, context.global_ctx)
        sig_chain: List[Any] = ['seq']

        for default_sig in default_sigs:
            sig_compare, private_label = get_sig_statements(
                default_sig, getpos(code))

            # Populate unset default variables
            set_defaults = []
            for arg_name in get_default_names_to_set(sig, default_sig):
                value = Expr(sig.default_values[arg_name], context).lll_node
                var = context.vars[arg_name]
                left = LLLnode.from_list(var.pos,
                                         typ=var.typ,
                                         location='memory',
                                         pos=getpos(code),
                                         mutable=var.mutable)
                set_defaults.append(
                    make_setter(left, value, 'memory', pos=getpos(code)))
            current_sig_arg_names = [x.name for x in default_sig.args]

            # Load all variables in default section, if private,
            # because the stack is a linear pipe.
            copier_arg_count = len(default_sig.args)
            copier_arg_names = current_sig_arg_names

            # Order copier_arg_names, this is very important.
            copier_arg_names = [
                x.name for x in default_sig.args if x.name in copier_arg_names
            ]

            # Variables to be populated from calldata/stack.
            default_copiers: List[Any] = []
            if copier_arg_count > 0:
                # Get map of variables in calldata, with thier offsets
                offset = 4
                calldata_offset_map = {}
                for arg in default_sig.args:
                    calldata_offset_map[arg.name] = offset
                    offset += (32 if isinstance(arg.typ, ByteArrayLike) else
                               get_size_of_type(arg.typ) * 32)

                # Copy set default parameters from calldata
                dynamics = []
                for arg_name in copier_arg_names:
                    var = context.vars[arg_name]
                    if isinstance(var.typ, ByteArrayLike):
                        _size = 32
                        dynamics.append(var.pos)
                    else:
                        _size = var.size * 32
                    default_copiers.append(
                        get_private_arg_copier(
                            memory_dest=var.pos,
                            total_size=_size,
                        ))

                # Unpack byte array if necessary.
                if dynamics:
                    i_placeholder = context.new_placeholder(
                        typ=BaseType('uint256'))
                    for idx, var_pos in enumerate(dynamics):
                        ident = f'unpack_default_sig_dyn_{default_sig.method_id}_arg{idx}'
                        default_copiers.append(
                            make_unpacker(
                                ident=ident,
                                i_placeholder=i_placeholder,
                                begin_pos=var_pos,
                            ))
                default_copiers.append(0)  # for over arching seq, POP

            sig_chain.append([
                'if', sig_compare,
                [
                    'seq', private_label,
                    LLLnode.from_list([
                        'mstore',
                        context.callback_ptr,
                        'pass',
                    ],
                                      annotation='pop callback pointer',
                                      pos=getpos(code)),
                    ['seq'] + set_defaults if set_defaults else ['pass'],
                    ['seq_unchecked'] +
                    default_copiers if default_copiers else ['pass'],
                    ['goto', _post_callback_ptr]
                ]
            ])

        # With private functions all variable loading occurs in the default
        # function sub routine.
        _clampers = [['label', _post_callback_ptr]]

        # Function with default parameters.
        o = LLLnode.from_list(
            [
                'seq',
                sig_chain,
                [
                    'if',
                    0,  # can only be jumped into
                    [
                        'seq', ['seq'] + nonreentrant_pre + _clampers +
                        [parse_body(c, context)
                         for c in code.body] + nonreentrant_post + stop_func
                    ],
                ],
            ],
            typ=None,
            pos=getpos(code))

    else:
        # Function without default parameters.
        sig_compare, private_label = get_sig_statements(sig, getpos(code))
        o = LLLnode.from_list([
            'if', sig_compare, ['seq'] + [private_label] + nonreentrant_pre +
            clampers + [parse_body(c, context)
                        for c in code.body] + nonreentrant_post + stop_func
        ],
                              typ=None,
                              pos=getpos(code))
        return o

    return o
Beispiel #30
0
def add_variable_offset(parent, key, pos, array_bounds_check=True):
    typ, location = parent.typ, parent.location
    if isinstance(typ, TupleLike):
        if isinstance(typ, StructType):
            if not isinstance(key, str):
                raise TypeMismatch(
                    f"Expecting a member variable access; cannot access element {key}",
                    pos)
            if key not in typ.members:
                raise TypeMismatch(
                    f"Object does not have member variable {key}", pos)
            subtype = typ.members[key]
            attrs = list(typ.tuple_keys())

            if key not in attrs:
                raise TypeMismatch(
                    f"Member {key} not found. Only the following available: " +
                    " ".join(attrs), pos)
            index = attrs.index(key)
            annotation = key
        else:
            if not isinstance(key, int):
                raise TypeMismatch(
                    f"Expecting a static index; cannot access element {key}",
                    pos)
            attrs = list(range(len(typ.members)))
            index = key
            annotation = None

        if location == 'storage':
            return LLLnode.from_list(
                [
                    'add', ['sha3_32', parent],
                    LLLnode.from_list(index, annotation=annotation)
                ],
                typ=subtype,
                location='storage',
            )
        elif location == 'storage_prehashed':
            return LLLnode.from_list(
                [
                    'add', parent,
                    LLLnode.from_list(index, annotation=annotation)
                ],
                typ=subtype,
                location='storage',
            )
        elif location in ('calldata', 'memory'):
            offset = 0
            for i in range(index):
                offset += 32 * get_size_of_type(typ.members[attrs[i]])
            return LLLnode.from_list(['add', offset, parent],
                                     typ=typ.members[key],
                                     location=location,
                                     annotation=annotation)
        else:
            raise TypeMismatch("Not expecting a member variable access", pos)

    elif isinstance(typ, MappingType):

        if isinstance(key.typ, ByteArrayLike):
            if not isinstance(typ.keytype, ByteArrayLike) or (
                    typ.keytype.maxlen < key.typ.maxlen):
                raise TypeMismatch(
                    "Mapping keys of bytes cannot be cast, use exact same bytes type of: "
                    f"{str(typ.keytype)}",
                    pos,
                )
            subtype = typ.valuetype
            if len(key.args[0].args) >= 3:  # handle bytes literal.
                sub = LLLnode.from_list([
                    'seq', key,
                    [
                        'sha3', ['add', key.args[0].args[-1], 32],
                        ['mload', key.args[0].args[-1]]
                    ]
                ])
            else:
                sub = LLLnode.from_list([
                    'sha3', ['add', key.args[0].value, 32],
                    ['mload', key.args[0].value]
                ])
        else:
            subtype = typ.valuetype
            sub = base_type_conversion(key, key.typ, typ.keytype, pos=pos)

        if location == 'storage':
            return LLLnode.from_list(['sha3_64', parent, sub],
                                     typ=subtype,
                                     location='storage')
        elif location in ('memory', 'calldata'):
            raise TypeMismatch(
                "Can only have fixed-side arrays in memory, not mappings", pos)

    elif isinstance(typ, ListType):

        subtype = typ.subtype
        k = unwrap_location(key)
        if not is_base_type(key.typ, ('int128', 'uint256')):
            raise TypeMismatch(f'Invalid type for array index: {key.typ}', pos)

        if not array_bounds_check:
            sub = k
        elif key.typ.is_literal:  # note: BaseType always has is_literal attr
            # perform the check at compile time and elide the runtime check.
            if key.value < 0 or key.value >= typ.count:
                raise ArrayIndexException(
                    'Array index determined to be out of bounds. '
                    f'Index is {key.value} but array size is {typ.count}', pos)
            sub = k
        else:
            # this works, even for int128. for int128, since two's-complement
            # is used, if the index is negative, (unsigned) LT will interpret
            # it as a very large number, larger than any practical value for
            # an array index, and the clamp will throw an error.
            sub = ['uclamplt', k, typ.count]

        if location == 'storage':
            return LLLnode.from_list(['add', ['sha3_32', parent], sub],
                                     typ=subtype,
                                     location='storage',
                                     pos=pos)
        elif location == 'storage_prehashed':
            return LLLnode.from_list(['add', parent, sub],
                                     typ=subtype,
                                     location='storage',
                                     pos=pos)
        elif location in ('calldata', 'memory'):
            offset = 32 * get_size_of_type(subtype)
            return LLLnode.from_list(['add', ['mul', offset, sub], parent],
                                     typ=subtype,
                                     location=location,
                                     pos=pos)
        else:
            raise TypeMismatch("Not expecting an array access ", pos)
    else:
        raise TypeMismatch(
            f"Cannot access the child of a constant variable! {typ}", pos)