def test_valid_l1_handler():
    program = preprocess_str("""
%lang starknet
%builtins ecdsa

@l1_handler
func f(from_address : felt):
    return ()
end
""")

    assert isinstance(
        program.identifiers.get_by_full_name(WRAPPER_SCOPE + 'f'),
        FunctionDefinition)

    expected_result = '%builtins ecdsa\n\n' + strip_comments_and_linebreaks(
        """\
ret
[ap] = [fp + (-3)] + 1; ap++             # Compute effective calldata end.
[fp + (-4)] = [ap + (-1)] - [fp + (-3)]  # Verify calldata size (1).
[ap] = [[fp + (-3)]]; ap++               # Pass from_address.
call rel -5                              # Call f.
%{ memory[ap] = segments.add() %}        # Allocate memory for return value
ap += 1
[ap] = [[fp + (-5)]]; ap++               # Return syscall_ptr
[ap] = [[fp + (-5)] + 1]; ap++           # Return storage_ptr.
[ap] = [[fp + (-5)] + 2]; ap++           # Return ecdsa.
[ap] = 0; ap++                           # Return retdata_size=0
[ap] = [ap + (-5)]; ap++                 # Return retdata_ptr
ret
""")
    assert program.format() == expected_result
def test_wrapper_without_implicit_args():
    program = preprocess_str("""
%lang starknet
%builtins ecdsa

@external
func f():
    return ()
end
""")

    assert isinstance(
        program.identifiers.get_by_full_name(WRAPPER_SCOPE + 'f'),
        FunctionDefinition)

    expected_result = '%builtins ecdsa\n\n' + strip_comments_and_linebreaks(
        """\
ret
[fp + (-4)] = [fp + (-3)] - [fp + (-3)]  # Verify calldata size (0).
call rel -2                              # Call f.
%{ memory[ap] = segments.add() %}        # Allocate memory for return value
ap += 1
[ap] = [[fp + (-5)]]; ap++               # Return syscall_ptr
[ap] = [[fp + (-5)] + 1]; ap++           # Return storage_ptr.
[ap] = [[fp + (-5)] + 2]; ap++           # Return ecdsa.
[ap] = 0; ap++                           # Return retdata_size=0
[ap] = [ap + (-5)]; ap++                 # Return retdata_ptr
ret
""")
    assert program.format() == expected_result
def test_wrapper_with_return_args():
    program = preprocess_str("""
%lang starknet
%builtins pedersen range_check ecdsa

struct HashBuiltin:
end

@external
func f{ecdsa_ptr}(a : felt, b : felt) -> (c : felt, d : felt):
    return (c=1, d=2)
end
""")

    assert isinstance(
        program.identifiers.get_by_full_name(WRAPPER_SCOPE + 'f'),
        FunctionDefinition)

    expected_result = '%builtins pedersen range_check ecdsa\n' + strip_comments_and_linebreaks(
        """\
# Implementation of f
[ap] = [fp + (-5)]; ap++                 # Return ecdsa_ptr.
[ap] = 1; ap++                           # Return c=1
[ap] = 2; ap++                           # Return d=2
ret

# Implementation of __wrappers__.f
[ap] = [fp + (-3)] + 2; ap++             # Compute effective calldata end.
[fp + (-4)] = [ap + (-1)] - [fp + (-3)]  # Verify calldata size (2).
[ap] = [[fp + (-5)] + 4]; ap++           # Pass ecdsa_ptr.
[ap] = [[fp + (-3)]]; ap++               # Pass a.
[ap] = [[fp + (-3)] + 1]; ap++           # Pass b.
call rel -12                             # Call f.
%{ memory[ap] = segments.add() %}        # Allocate memory for return value
ap += 1
[[ap + (-1)]] = [ap + (-3)]              # [retdata_ptr] = c
[[ap + (-1)] + 1] = [ap + (-2)]          # [retdata_ptr + 1] = d
[ap] = [[fp + (-5)]]; ap++               # Return syscall_ptr
[ap] = [[fp + (-5)] + 1]; ap++           # Return storage_ptr
[ap] = [[fp + (-5)] + 2]; ap++           # Return pedersen_ptr.
[ap] = [[fp + (-5)] + 3]; ap++           # Return range_check.
[ap] = [ap + (-8)]; ap++                 # Return ecdsa.
[ap] = 2; ap++                           # Return retdata_size=2
[ap] = [ap + (-7)]; ap++                 # Return retdata_ptr
ret
""")
    assert program.format() == expected_result
def test_check_felts_only_type():
    program = preprocess_str("""
struct A:
    member x : felt
end

struct B:
end

struct C:
    member x : felt
    member y : (felt, A, B)
    member z : A
end

struct D:
    member x : felt*
end

struct E:
    member x : D
end
    """)

    for (typ, expected_res) in [
            # Positive cases.
        ('test_scope.A', True),
        ('test_scope.B', True),
        ('test_scope.C', True),
        ('(felt, felt)', True),
        ('(felt, (felt, test_scope.C))', True),
            # Negative cases.
        ('test_scope.D', False),
        ('test_scope.E', False),
        ('(felt, test_scope.D)', False),
    ]:
        assert check_felts_only_type(
            cairo_type=mark_type_resolved(parse_type(typ)),
            identifier_manager=program.identifiers) == expected_res
def test_abi():
    program = preprocess_str("""
%lang starknet
%builtins range_check

@external
func f(a : felt, arr_len : felt, arr : felt*) -> (b : felt, c : felt):
    return (0, 1)
end

@view
func g() -> (a: felt):
    return (0)
end

@l1_handler
func handler(from_address):
    return ()
end
""")

    assert program.abi == [
        {
            'inputs': [
                {
                    'name': 'a',
                    'type': 'felt'
                },
                {
                    'name': 'arr_len',
                    'type': 'felt'
                },
                {
                    'name': 'arr',
                    'type': 'felt*'
                },
            ],
            'name':
            'f',
            'outputs': [
                {
                    'name': 'b',
                    'type': 'felt'
                },
                {
                    'name': 'c',
                    'type': 'felt'
                },
            ],
            'type':
            'function',
        },
        {
            'inputs': [],
            'name': 'g',
            'outputs': [
                {
                    'name': 'a',
                    'type': 'felt'
                },
            ],
            'type': 'function',
            'stateMutability': 'view',
        },
        {
            'inputs': [{
                'name': 'from_address',
                'type': 'felt'
            }],
            'name': 'handler',
            'outputs': [],
            'type': 'l1_handler',
        },
    ]
def test_storage_var_success():
    program = preprocess_str("""
%lang starknet
from starkware.starknet.common.storage import Storage
from starkware.cairo.common.cairo_builtins import HashBuiltin

struct A:
    member x : felt
end

struct B:
    member a : A
    member y : felt
end

func g{storage_ptr : Storage*, range_check_ptr, pedersen_ptr : HashBuiltin*}():
    alloc_locals
    let (x) = my_var.read()
    my_var.write(value=x + 1)
    local storage_ptr : Storage* = storage_ptr
    let (my_var2_addr) = my_var2.addr(1, 2)
    my_var2.write(1, 2, value=B(A(3), 4))
    let a = my_var2.read(1, 2)
    return ()
end

@storage_var
func my_var() -> (res : felt):
    # Comment.

end

@storage_var
func my_var2(x, y) -> (res : B):
end
""")
    addr = starknet_keccak(b'my_var')
    addr2 = starknet_keccak(b'my_var2')
    expected_result = f"""\
# Code for the dummy modules.
ret
ret
ret
ret

# Implementation of g.
ap += 1
[ap] = [fp + (-5)]; ap++       # Push storage_ptr.
[ap] = [fp + (-4)]; ap++       # Push range_check_ptr.
[ap] = [fp + (-3)]; ap++       # Push pedersen_ptr.
call rel ???                   # Call my_var.read.
[ap] = [ap + (-4)]; ap++       # Push (updated) storage_ptr.
[ap] = [ap + (-4)]; ap++       # Push (updated) range_check_ptr.
[ap] = [ap + (-4)]; ap++       # Push (updated) pedersen_ptr.
[ap] = [ap + (-4)] + 1; ap++   # Push value.
call rel ???                   # Call my_var.write.
[fp] = [ap + (-3)]             # Copy storage_ptr to a local variable.
[ap] = 1; ap++                 # Push 1.
[ap] = 2; ap++                 # Push 2.
call rel ???                   # Call my_var2.addr.
[ap] = [fp]; ap++              # Push storage_ptr.
[ap] = [ap + (-4)]; ap++       # Push range_check_ptr.
[ap] = [ap + (-4)]; ap++       # Push pedersen_ptr.
[ap] = 1; ap++                 # Push 1.
[ap] = 2; ap++                 # Push 2.
[ap] = 3; ap++                 # Push 3.
[ap] = 4; ap++                 # Push 4.
call rel ???                   # Call my_var2.write.
[ap] = 1; ap++                 # Push 1.
[ap] = 2; ap++                 # Push 2.
call rel ???                   # Call my_var2.read.
[ap] = [ap + (-5)]; ap++       # Return (updated) storage_ptr.
[ap] = [ap + (-5)]; ap++       # Return (updated) range_check_ptr.
[ap] = [ap + (-5)]; ap++       # Return (updated) pedersen_ptr.
ret

# Implementation of my_var.addr.
[ap] = [fp + (-4)]; ap++       # Return range_check_ptr.
[ap] = [fp + (-3)]; ap++       # Return pedersen_ptr.
[ap] = {addr}; ap++            # Return address.
ret

# Implementation of my_var.read.
[ap] = [fp + (-4)]; ap++       # Push range_check_ptr.
[ap] = [fp + (-3)]; ap++       # Push pedersen_ptr.
call rel ???                   # Call my_var.addr().
[ap] = [fp + (-5)]; ap++       # Push storage_ptr.
[ap] = [ap + (-2)]; ap++       # Push address.
call rel ???                   # Call storage_read().
[ap] = [ap + (-2)]; ap++       # Return storage_ptr.
[ap] = [ap + (-8)]; ap++       # Return (updated) range_check_ptr.
[ap] = [ap + (-8)]; ap++       # Return (updated) pedersen_ptr.
[ap] = [ap + (-4)]; ap++       # Return value.
ret

# Implementation of my_var.write.
[ap] = [fp + (-5)]; ap++       # Push range_check_ptr.
[ap] = [fp + (-4)]; ap++       # Push pedersen_ptr.
call rel ???                   # Call my_var.addr().
[ap] = [fp + (-6)]; ap++       # Push storage_ptr.
[ap] = [ap + (-2)]; ap++       # Push address.
[ap] = [fp + (-3)]; ap++       # Push value.
call rel ???                   # Call storage_write().
[ap] = [ap + (-8)]; ap++       # Return (updated) range_check_ptr.
[ap] = [ap + (-8)]; ap++       # Return (updated) pedersen_ptr.
ret

# Implementation of my_var2.addr.
[ap] = [fp + (-5)]; ap++       # Push pedersen_ptr.
[ap] = {addr2}; ap++           # Push address.
[ap] = [fp + (-4)]; ap++       # Push x.
call rel ???                   # Call hash2(res, x).
[ap] = [fp + (-3)]; ap++       # Push y.
call rel ???                   # Call hash2(res, y).
[ap] = [fp + (-6)]; ap++       # Push range_check_ptr.
[ap] = [ap + (-2)]; ap++       # Push res.
call rel ???                   # Call normalize_address(res).
[ap] = [ap + (-2)]; ap++       # Return (updated) range_check_ptr.
[ap] = [ap + (-7)]; ap++       # Return (updated) pedersen_ptr.
[ap] = [ap + (-3)]; ap++       # Return res.
ret

# Implementation of my_var2.read.
[ap] = [fp + (-6)]; ap++       # Push range_check_ptr.
[ap] = [fp + (-5)]; ap++       # Push pedersen_ptr.
[ap] = [fp + (-4)]; ap++       # Push x.
[ap] = [fp + (-3)]; ap++       # Push y.
call rel ???                   # Call my_var.addr().
[ap] = [fp + (-7)]; ap++       # Push storage_ptr.
[ap] = [ap + (-2)]; ap++       # Push address.
call rel ???                   # Call storage_read().
[ap] = [ap + (-2)]; ap++       # Push (updated) storage_ptr.
[ap] = [ap + (-6)] + 1; ap++   # Push address + 1.
call rel ???                   # Call storage_read().
[ap] = [ap + (-2)]; ap++       # Return storage_ptr.
[ap] = [ap + (-12)]; ap++      # Return (updated) range_check_ptr.
[ap] = [ap + (-12)]; ap++      # Return (updated) pedersen_ptr.
[ap] = [ap + (-8)]; ap++       # Return x.
[ap] = [ap + (-5)]; ap++       # Return y.
ret

# Implementation of my_var2.write.
[ap] = [fp + (-8)]; ap++       # Push range_check_ptr.
[ap] = [fp + (-7)]; ap++       # Push pedersen_ptr.
[ap] = [fp + (-6)]; ap++       # Push x.
[ap] = [fp + (-5)]; ap++       # Push y.
call rel ???                   # Call my_var.addr().
[ap] = [fp + (-9)]; ap++       # Push storage_ptr.
[ap] = [ap + (-2)]; ap++       # Push address.
[ap] = [fp + (-4)]; ap++       # Push value.
call rel ???                   # Call storage_write().
[ap] = [ap + (-6)] + 1; ap++   # Push address.
[ap] = [fp + (-3)]; ap++       # Push value.
call rel ???                   # Call storage_write().
[ap] = [ap + (-12)]; ap++      # Return (updated) range_check_ptr.
[ap] = [ap + (-12)]; ap++      # Return (updated) pedersen_ptr.
ret
"""
    assert re.sub('call rel -?[0-9]+', 'call rel ???', program.format()) == \
        strip_comments_and_linebreaks(expected_result).lstrip()