Example #1
0
def test_load_data_after_init():
    code = """\
func main():
  ret
end
"""
    program = compile_cairo(code, PRIME)
    runner = CairoRunner(program, layout='plain')
    runner.initialize_segments()
    runner.initialize_main_entrypoint()
    runner.initialize_vm({})
    addr = runner.segments.add()
    runner.load_data(addr, [42])
    assert runner.vm_memory[addr] == 42
Example #2
0
def verify_secure_runner(runner: CairoRunner, verify_builtins=True):
    """
    Verifies the complete run in runner is safe to relocate and run by another Cairo program.
    Checks that:
    * No access to pure addresses (The entire run must be relocatable).
    * All segment offsets are non negative.
    * All segment accesses are continuous.
    * No access to builtin segments beyond the allowed region. This region is defined
      by the start ptr (0), and the ptr returned in final stack.
    * No access to program segment beyond program data.
    Note: There is no need to check that builtin segment accesses are continuous. They will be
    accessed continuously in the builtin itself.
    """

    builtin_segments = runner.get_builtin_segments_info(
    ) if verify_builtins else {}
    builtin_segment_names = {
        seg.index: name
        for name, seg in builtin_segments.items()
    }
    builtin_segment_sizes = {
        seg.index: seg.size
        for seg in builtin_segments.values()
    }
    segment_usage = defaultdict(set)
    for addr in runner.vm_memory:
        # Check pure addresses.
        if not isinstance(addr, RelocatableValue):
            raise SecurityError(f'Accessed address {addr} is not relocatable.')
        # Check non negative offset.
        if addr.offset < 0:
            raise SecurityError(
                f'Accessed address {addr} has negative offset.')
        # Check builtin segment out of bounds.
        if addr.segment_index in builtin_segment_sizes:
            if not addr.offset < builtin_segment_sizes[addr.segment_index]:
                raise SecurityError(
                    'Out of bounds access to builtin segment '
                    f'{builtin_segment_names[addr.segment_index]} at {addr}.')

        # Check out of bounds for program segment.
        if addr.segment_index == runner.program_base.segment_index:
            if not addr.offset < len(runner.program.data):
                raise SecurityError(
                    f'Out of bounds access to program segment at {addr}.')

        # Check usage.
        segment_usage[addr.segment_index].add(addr.offset)

    # Check continuity.
    for segment_index, usage in segment_usage.items():
        if len(usage) != max(usage) + 1:
            # Find the non continuity for error message.
            i = 0
            while i in usage:
                i += 1
            raise SecurityError(
                f'Non continuous segment {segment_index} at offset {i}.')
def test_select_input_builtins(builtin_selection_indicators):
    """
    Tests the select_input_builtins Cairo function: calls the function with different builtins
    selection and checks that the function returns the expected builtin pointers.
    """
    # Setup runner.
    cairo_file = os.path.join(os.path.dirname(__file__),
                              'select_input_builtins.cairo')
    runner = CairoRunner.from_file(cairo_file, DEFAULT_PRIME)
    runner.initialize_segments()

    output_base = runner.segments.add()
    hash_base = runner.segments.add()
    range_check_base = runner.segments.add()
    signature_base = runner.segments.add()

    # Setup function.
    builtins_encoding = {
        builtin: int.from_bytes(builtin.encode('ascii'), 'big')
        for builtin in ['output', 'pedersen', 'range_check', 'ecdsa']
    }
    all_builtins = [output_base, hash_base, range_check_base, signature_base]

    selected_builtin_encodings = [
        builtin_encoding for builtin_encoding, is_builtin_selected in zip(
            builtins_encoding.values(), builtin_selection_indicators)
        if is_builtin_selected
    ]

    selected_builtins = [
        builtin for builtin, is_builtin_selected in zip(
            all_builtins, builtin_selection_indicators) if is_builtin_selected
    ]

    all_encodings = create_memory_struct(runner, builtins_encoding.values())
    selected_encodings = create_memory_struct(runner,
                                              selected_builtin_encodings)
    all_ptrs = create_memory_struct(runner, all_builtins)
    n_builtins = len(selected_builtin_encodings)

    args = [all_encodings, all_ptrs, selected_encodings, n_builtins]
    end = runner.initialize_function_entrypoint('select_input_builtins', args)

    # Setup context.
    runner.initialize_vm(hint_locals={})
    runner.run_until_pc(end)
    runner.end_run()

    # Check result.
    context = runner.vm.run_context

    # 'select_input_builtins' should return the pointers to the selected builtins.
    return_values_addr = context.ap - n_builtins
    assert [
        context.memory[return_values_addr + i] for i in range(len(selected_builtins))] == \
        selected_builtins
def test_builtin_list():
    # This should work.
    program = compile_cairo(
        code=[('%builtins output pedersen range_check ecdsa\n', '')], prime=PRIME)
    CairoRunner(program, layout='small')

    # These should fail.
    program = compile_cairo(code=[('%builtins pedersen output\n', '')], prime=PRIME)
    with pytest.raises(
            AssertionError,
            match=r"Expected builtin list \['output', 'pedersen'\] does not match "
            r"\['pedersen', 'output'\]."):
        CairoRunner(program, layout='small')

    program = compile_cairo(code=[('%builtins pedersen foo\n', '')], prime=PRIME)
    with pytest.raises(
            AssertionError,
            match=r'Builtins {\'foo\'} are not present in layout "small"'):
        CairoRunner(program, layout='small')
Example #5
0
def runner_and_output_runner():
    PRIME = 2**251 + 17 * 2**192 + 1
    code = """
func main():
  ret
end
"""
    program = compile_cairo(code=[(code, '')], prime=PRIME, add_start=True)
    runner = CairoRunner(program=program, layout='plain', proof_mode=True)
    runner.initialize_segments()
    output_builtin_runner = runner.builtin_runners[
        'output'] = OutputBuiltinRunner(included=True)
    output_builtin_runner.initialize_segments(runner=runner)
    runner.initialize_main_entrypoint()
    runner.initialize_vm(hint_locals={})
    return runner, output_builtin_runner, output_builtin_runner.base
Example #6
0
def test_run_until_label():
    runner = CairoRunner.from_file(CAIRO_FILE, PRIME, proof_mode=True)
    runner.initialize_segments()
    runner.initialize_main_entrypoint()
    runner.initialize_vm({})

    # Test runs.
    assert runner.vm.run_context.pc - runner.program_base == 0
    runner.run_until_label(3)
    assert runner.vm.run_context.pc - runner.program_base == 3
    assert runner.vm.current_step == 3
    runner.run_until_label('label1')
    assert runner.vm.run_context.pc - runner.program_base == 6
    assert runner.vm.current_step == 6
    with pytest.raises(VmException, match='End of program was not reached'):
        runner.run_until_label('label0', max_steps=100)
    assert runner.vm.run_context.pc - runner.program_base == 8
    assert runner.vm.current_step == 106
    runner.run_until_next_power_of_2()
    assert runner.vm.current_step == 128
Example #7
0
def verify_secure_runner(runner: CairoRunner, verify_builtins=True):
    """
    Verifies the complete run in runner is safe to relocate and run by another Cairo program.
    Checks that:
    * No access to pure addresses (The entire run must be relocatable).
    * All segment offsets are non negative.
    * No access to builtin segments beyond the allowed region. This region is defined
      by the start ptr (0), and the ptr returned in final stack.
    * No access to program segment beyond program data.
    Note: The continuity of builtin segments is checked in builtin specific checks.
    """

    builtin_segments = runner.get_builtin_segments_info() if verify_builtins else {}
    builtin_segment_names = {seg.index: name for name, seg in builtin_segments.items()}
    builtin_segment_sizes = {seg.index: seg.size for seg in builtin_segments.values()}
    for addr in runner.vm_memory:
        # Check pure addresses.
        if not isinstance(addr, RelocatableValue):
            raise SecurityError(f'Accessed address {addr} is not relocatable.')
        # Check non negative offset.
        if addr.offset < 0:
            raise SecurityError(f'Accessed address {addr} has negative offset.')
        # Check builtin segment out of bounds.
        if addr.segment_index in builtin_segment_sizes:
            if not addr.offset < builtin_segment_sizes[addr.segment_index]:
                raise SecurityError(
                    'Out of bounds access to builtin segment '
                    f'{builtin_segment_names[addr.segment_index]} at {addr}.')

        # Check out of bounds for program segment.
        if addr.segment_index == runner.program_base.segment_index:
            if not addr.offset < len(runner.program.data):
                raise SecurityError(f'Out of bounds access to program segment at {addr}.')

    # Builtin specific checks.
    try:
        for builtin_runner in runner.builtin_runners.values():
            builtin_runner.run_security_checks(runner)
    except Exception as exc:
        raise SecurityError(str(exc))
def test_validate_builtins(old_builtins, new_builtins, builtin_sizes,
                           expect_throw):
    """
    Tests the inner_validate_builtins_usage Cairo function: calls the function with different
    builtins usage and checks that the used builtins list was filled correctly.
    """
    # Setup runner.
    runner = CairoRunner.from_file(CAIRO_FILE, DEFAULT_PRIME)
    assert len(
        runner.program.hints) == 0, 'Expecting validator to have no hints.'

    range_check_builtin = RangeCheckBuiltinRunner(
        included=True,
        ratio=None,
        inner_rc_bound=2**16,
        n_parts=small_instance.builtins['range_check'].n_parts)
    runner.builtin_runners['range_check_builtin'] = range_check_builtin
    runner.initialize_segments()

    # Setup function.
    old_builtins_ptr = create_memory_struct(runner, old_builtins)
    new_builtins_ptr = create_memory_struct(runner, new_builtins)
    builtins_sizes = create_memory_struct(runner, builtin_sizes)
    args = [
        range_check_builtin.base,
        old_builtins_ptr,
        new_builtins_ptr,
        builtins_sizes,
        len(builtin_sizes),
    ]
    end = runner.initialize_function_entrypoint('validate_builtins', args)
    # Setup context.
    runner.initialize_vm(hint_locals={})

    if expect_throw:
        with pytest.raises(VmException, match='is out of range'):
            runner.run_until_pc(end)
    else:
        runner.run_until_pc(end)
        runner.end_run()
Example #9
0
def test_tracer_data():
    code = """
%builtins output

func main(output_ptr) -> (output_ptr):
  [ap] = 1000; ap++
  let x = 2000
  [ap] = x; ap++
  let x = 5000
  let y = [ap]
  [ap] = [ap - 2] + [ap - 1]; ap++
  assert [output_ptr] = 1234
  assert [output_ptr + 1] = 4321
  ret
end
"""
    program: Program = compile_cairo(code=code, prime=PRIME, debug_info=True)
    runner = CairoRunner(program, layout='small')
    runner.initialize_segments()
    runner.initialize_main_entrypoint()
    runner.initialize_vm(hint_locals={})
    runner.run_until_steps(steps=6)
    runner.end_run()
    runner.finalize_segments_by_effective_size()
    runner.relocate()
    memory = runner.relocated_memory
    trace = runner.relocated_trace

    tracer_data = TracerData(program=program,
                             memory=memory,
                             trace=trace,
                             program_base=runner.relocate_value(
                                 runner.program_base))

    # Test watch evaluator.
    watch_evaluator = WatchEvaluator(tracer_data=tracer_data,
                                     entry=tracer_data.trace[0])
    with pytest.raises(TypeError, match='NoneType'):
        watch_evaluator.eval(None)
    assert watch_evaluator.eval_suppress_errors(
        'x') == "FlowTrackingError: Invalid reference 'x'."
    watch_evaluator = WatchEvaluator(tracer_data=tracer_data,
                                     entry=tracer_data.trace[1])
    assert watch_evaluator.eval('x') == '2000'
    watch_evaluator = WatchEvaluator(tracer_data=tracer_data,
                                     entry=tracer_data.trace[2])
    assert watch_evaluator.eval('[ap]') == '3000'
    assert watch_evaluator.eval('[ap-1]') == '2000'
    assert watch_evaluator.eval('[ap-2]') == '1000'
    assert watch_evaluator.eval('[fp]') == '1000'
    assert watch_evaluator.eval('x') == '5000'

    # Test memory_accesses.
    assert memory[tracer_data.memory_accesses[0]['op1']] == 1000
    assert memory[tracer_data.memory_accesses[1]['op1']] == 2000
    assert tracer_data.memory_accesses[2]['dst'] == trace[2].ap
    assert tracer_data.memory_accesses[2]['op0'] == trace[2].ap - 2
    assert tracer_data.memory_accesses[2]['op1'] == trace[2].ap - 1

    # Test current identifier values.
    assert tracer_data.get_current_identifier_values(trace[0]) == {
        'output_ptr': '21'
    }
    assert tracer_data.get_current_identifier_values(trace[1]) == {
        'output_ptr': '21',
        'x': '2000'
    }
    assert tracer_data.get_current_identifier_values(trace[2]) == {
        'output_ptr': '21',
        'x': '5000',
        'y': '3000'
    }
    # '__temp1' identifier is already available in this step, but should not be returned as its
    # value is still unknown.
    assert tracer_data.get_current_identifier_values(trace[3]) == \
        tracer_data.get_current_identifier_values(trace[4]) == {
            'output_ptr': '21', 'x': '5000', 'y': '3000', '__temp0': '1234'}
    assert tracer_data.get_current_identifier_values(trace[5]) == {
        'output_ptr': '21',
        'x': '5000',
        'y': '3000',
        '__temp0': '1234',
        '__temp1': '4321'
    }
Example #10
0
def cairo_run(args):
    trace_file = args.trace_file
    if trace_file is None and args.tracer:
        # If --tracer is used, use a temporary file as trace_file.
        trace_file = tempfile.NamedTemporaryFile(mode='wb')

    memory_file = args.memory_file
    if memory_file is None and args.tracer:
        # If --tracer is used, use a temporary file as memory_file.
        memory_file = tempfile.NamedTemporaryFile(mode='wb')

    debug_info_file = args.debug_info_file
    if debug_info_file is None and args.tracer:
        # If --tracer is used, use a temporary file as debug_info_file.
        debug_info_file = tempfile.NamedTemporaryFile(mode='w')

    ret_code = 0
    if args.program is not None:
        program: ProgramBase = Program.Schema().load(json.load(args.program))
        initial_memory = MemoryDict()
        steps_input = args.steps
    else:
        raise NotImplementedError('--run_from_cairo_pie is not supported.')

    runner = CairoRunner(program=program,
                         layout=args.layout,
                         memory=initial_memory,
                         proof_mode=args.proof_mode)

    runner.initialize_segments()
    end = runner.initialize_main_entrypoint()

    if args.run_from_cairo_pie is not None:
        # Add extra_segments.
        for segment_info in cairo_pie_input.metadata.extra_segments:
            runner.segments.add(size=segment_info.size)

    program_input = json.load(args.program_input) if args.program_input else {}
    runner.initialize_vm(hint_locals={'program_input': program_input})

    try:
        if args.no_end:
            assert args.steps is not None, '--steps must specified when running with --no-end.'
        else:
            additional_steps = 1 if args.proof_mode else 0
            max_steps = steps_input - additional_steps if steps_input is not None else None
            runner.run_until_pc(end, max_steps=max_steps)
            if args.proof_mode:
                # Run one more step to make sure the last pc that was executed (rather than the pc
                # after it) is __end__.
                runner.run_for_steps(1)
            runner.original_steps = runner.vm.current_step

        if args.min_steps:
            runner.run_until_steps(args.min_steps)

        if steps_input is not None:
            runner.run_until_steps(steps_input)
        elif args.proof_mode:
            runner.run_until_next_power_of_2()
            while not runner.check_used_cells():
                runner.run_for_steps(1)
                runner.run_until_next_power_of_2()
        runner.end_run()
    except (VmException, AssertionError) as exc:
        if args.debug_error:
            print(f'Got an error:\n{exc}')
            ret_code = 1
        else:
            raise exc

    if not args.no_end:
        runner.read_return_values()

    if args.no_end or not args.proof_mode:
        runner.finalize_segments_by_effective_size()
    else:
        # Finalize important segments by correct size.
        runner.finalize_segments()
        # Finalize all user segments by effective size.
        runner.finalize_segments_by_effective_size()

    if args.secure_run:
        verify_secure_runner(runner)

    if args.cairo_pie_output:
        runner.get_cairo_pie().to_file(args.cairo_pie_output)

    runner.relocate()

    if args.print_memory:
        runner.print_memory(relocated=args.relocate_prints)

    if args.print_output:
        runner.print_output()

    if args.print_info:
        runner.print_info(relocated=args.relocate_prints)
        # Skip builtin usage calculation if the execution stopped before reaching the end symbol.
        # Trying to calculate the builtin usage is likely to raise an exception and prevent the user
        # from opening the tracer.
        if args.proof_mode and not args.no_end:
            runner.print_builtin_usage()

    if trace_file is not None:
        field_bytes = math.ceil(program.prime.bit_length() / 8)
        write_binary_trace(trace_file, runner.relocated_trace)

    if memory_file is not None:
        field_bytes = math.ceil(program.prime.bit_length() / 8)
        write_binary_memory(memory_file, runner.relocated_memory, field_bytes)

    if args.air_public_input is not None:
        rc_min, rc_max = runner.get_perm_range_check_limits()
        write_air_public_input(
            layout=args.layout,
            public_input_file=args.air_public_input,
            memory=runner.relocated_memory,
            public_memory_addresses=runner.segments.
            get_public_memory_addresses(runner.segment_offsets),
            memory_segment_addresses=runner.get_memory_segment_addresses(),
            trace=runner.relocated_trace,
            rc_min=rc_min,
            rc_max=rc_max)

    if args.air_private_input is not None:
        assert args.trace_file is not None, \
            '--trace_file must be set when --air_private_input is set.'
        assert args.memory_file is not None, \
            '--memory_file must be set when --air_private_input is set.'
        json.dump(
            {
                'trace_path': f'{os.path.abspath(trace_file.name)}',
                'memory_path': f'{os.path.abspath(memory_file.name)}',
                **runner.get_air_private_input(),
            },
            args.air_private_input,
            indent=4)
        print(file=args.air_private_input)
        args.air_private_input.flush()

    if debug_info_file is not None:
        json.dump(DebugInfo.Schema().dump(runner.get_relocated_debug_info()),
                  debug_info_file)
        debug_info_file.flush()

    if args.tracer:
        CAIRO_TRACER = 'starkware.cairo.lang.tracer.tracer'
        subprocess.call(
            list(
                filter(None, [
                    sys.executable,
                    '-m',
                    CAIRO_TRACER,
                    f'--program={args.program.name}',
                    f'--trace={trace_file.name}',
                    f'--memory={memory_file.name}',
                    f'--air_public_input={args.air_public_input.name}'
                    if args.air_public_input else None,
                    f'--debug_info={debug_info_file.name}',
                ])))

    return ret_code
def cairo_run(args, program_input):
    program: ProgramBase = load_program(args.program)
    initial_memory = MemoryDict()
    steps_input = args.steps

    runner = CairoRunner(program=program,
                         layout=args.layout,
                         memory=initial_memory,
                         proof_mode=args.proof_mode)

    runner.initialize_segments()
    end = runner.initialize_main_entrypoint()
    runner.initialize_vm(hint_locals={'program_input': program_input})

    try:
        additional_steps = 1 if args.proof_mode else 0
        max_steps = steps_input - additional_steps if steps_input is not None else None
        runner.run_until_pc(end, max_steps=max_steps)
        runner.original_steps = runner.vm.current_step
        runner.end_run()
    except (VmException, AssertionError) as exc:
        raise exc

    runner.read_return_values()
    runner.finalize_segments_by_effective_size()
    verify_secure_runner(runner)
    runner.relocate()
    output = retrieveOutput(runner, args.exception)

    return output
Example #12
0
def compile_and_run(code: str):
    """
    Compiles the given code and runs it in the VM.
    """
    program = compile_cairo(code, PRIME)
    runner = CairoRunner(program, layout='small', proof_mode=False)
    runner.initialize_segments()
    end = runner.initialize_main_entrypoint()
    runner.initialize_vm({})
    runner.run_until_pc(end)
    runner.end_run()
Example #13
0
def test_missing_exit_scope():
    code = """\
func main():
  %{ vm_enter_scope() %}
  ret
end
"""
    program = compile_cairo(code, PRIME)
    runner = CairoRunner(program, layout='small')
    runner.initialize_segments()
    end = runner.initialize_main_entrypoint()
    runner.initialize_vm({})
    runner.run_until_pc(end)

    with pytest.raises(
            AssertionError,
            match=re.escape(
                'Every enter_scope() requires a corresponding exit_scope().')):
        runner.end_run()
Example #14
0
def test_bad_stop_ptr():
    code = """\
%builtins output

func main(output_ptr) -> (output_ptr):
  [ap] = 0; ap++
  [ap - 1] = [output_ptr]
  [ap] = output_ptr + 3; ap++  # The correct return value is output_ptr + 1
  ret
end
"""
    program = compile_cairo(code, PRIME)
    runner = CairoRunner(program, layout='small')
    runner.initialize_segments()
    end = runner.initialize_main_entrypoint()
    runner.initialize_vm({})
    runner.run_until_pc(end)

    with pytest.raises(
            AssertionError,
            match='Invalid stop pointer for output. Expected: 2:1, found: 2:3'
    ):
        runner.read_return_values()
Example #15
0
def run_code_in_runner(code, layout='plain'):
    program = compile_cairo(code, PRIME)
    runner = CairoRunner(program, layout=layout)
    runner.initialize_segments()
    end = runner.initialize_main_entrypoint()
    runner.initialize_vm(hint_locals={})
    runner.run_until_pc(end)
    runner.end_run()
    runner.read_return_values()
    runner.finalize_segments_by_effective_size()
    return runner
Example #16
0
def test_run_past_end():
    code = """\
func main():
    ret
end
"""
    program = compile_cairo(code, PRIME)
    runner = CairoRunner(program, layout='plain')
    runner.initialize_segments()
    runner.initialize_main_entrypoint()
    runner.initialize_vm({})

    runner.run_for_steps(1)
    with pytest.raises(
            VmException,
            match='Error: Execution reached the end of the program.'):
        runner.run_for_steps(1)
Example #17
0
def cairo_run(args):
    trace_needed = args.tracer or args.profile_output is not None
    trace_file = args.trace_file
    if trace_file is None and trace_needed:
        # If --tracer or --profile_output is used, use a temporary file as trace_file.
        trace_file = tempfile.NamedTemporaryFile(mode='wb')

    memory_file = args.memory_file
    if memory_file is None and trace_needed:
        # If --tracer or --profile_output is used, use a temporary file as memory_file.
        memory_file = tempfile.NamedTemporaryFile(mode='wb')

    debug_info_file = args.debug_info_file
    if debug_info_file is None and trace_needed:
        # If --tracer or --profile_output is used, use a temporary file as debug_info_file.
        debug_info_file = tempfile.NamedTemporaryFile(mode='w')

    ret_code = 0
    cairo_pie_input = None
    if args.program is not None:
        program: ProgramBase = load_program(args.program)
        initial_memory = MemoryDict()
        steps_input = args.steps
    else:
        assert args.run_from_cairo_pie is not None
        assert args.steps is None and args.min_steps is None, \
            '--steps and --min_steps cannot be specified in --run_from_cairo_pie mode.'
        cairo_pie_input = CairoPie.from_file(args.run_from_cairo_pie)
        try:
            cairo_pie_input.run_validity_checks()
        except Exception as exc:
            # Trim error message in case it's too long.
            msg = str(exc)[:10000]
            raise CairoRunError(
                f'Security check for the CairoPIE input failed: {msg}')
        program = cairo_pie_input.program
        initial_memory = cairo_pie_input.memory
        steps_input = cairo_pie_input.execution_resources.n_steps

    runner = CairoRunner(program=program,
                         layout=args.layout,
                         memory=initial_memory,
                         proof_mode=args.proof_mode)

    runner.initialize_segments()
    end = runner.initialize_main_entrypoint()

    if args.run_from_cairo_pie is not None:
        # Add extra_segments.
        for segment_info in cairo_pie_input.metadata.extra_segments:
            runner.segments.add(size=segment_info.size)
        # Update the builtin runners' additional_data.
        for name, builtin_runner in runner.builtin_runners.items():
            if name in cairo_pie_input.additional_data:
                builtin_runner.extend_additional_data(
                    data=cairo_pie_input.additional_data[name],
                    relocate_callback=lambda x: x,
                    data_is_trusted=not args.secure_run)

    program_input = json.load(args.program_input) if args.program_input else {}
    runner.initialize_vm(hint_locals={'program_input': program_input})

    try:
        if args.no_end:
            assert args.steps is not None, '--steps must be specified when running with --no-end.'
        else:
            additional_steps = 1 if args.proof_mode else 0
            max_steps = steps_input - additional_steps if steps_input is not None else None
            runner.run_until_pc(end, max_steps=max_steps)
            if args.proof_mode:
                # Run one more step to make sure the last pc that was executed (rather than the pc
                # after it) is __end__.
                runner.run_for_steps(1)
            runner.original_steps = runner.vm.current_step

        if args.min_steps:
            runner.run_until_steps(args.min_steps)

        disable_trace_padding = False
        if steps_input is not None:
            runner.run_until_steps(steps_input)
            disable_trace_padding = True

        runner.end_run(disable_trace_padding=disable_trace_padding)
    except (VmException, AssertionError) as exc:
        if args.debug_error:
            print(f'Got an error:\n{exc}')
            ret_code = 1
        else:
            raise exc

    if not args.no_end:
        runner.read_return_values()

    if not args.no_end and args.proof_mode:
        # Finalize important segments by correct size.
        runner.finalize_segments()

    if args.secure_run:
        verify_secure_runner(runner)
        if args.run_from_cairo_pie is not None:
            assert cairo_pie_input is not None
            assert cairo_pie_input == runner.get_cairo_pie(), \
                'The Cairo PIE input is not identical to the resulting Cairo PIE. ' \
                'This may indicate that the Cairo PIE was not generated by cairo_run.'

    if args.cairo_pie_output:
        runner.get_cairo_pie().to_file(args.cairo_pie_output)

    runner.relocate()

    if args.print_memory:
        runner.print_memory(relocated=args.relocate_prints)

    if args.print_output:
        runner.print_output()

    if args.print_info:
        runner.print_info(relocated=args.relocate_prints)
        # Skip builtin usage calculation if the execution stopped before reaching the end symbol.
        # Trying to calculate the builtin usage is likely to raise an exception and prevent the user
        # from opening the tracer.
        if args.proof_mode and not args.no_end:
            runner.print_builtin_usage()

    if trace_file is not None:
        field_bytes = math.ceil(program.prime.bit_length() / 8)
        write_binary_trace(trace_file, runner.relocated_trace)

    if memory_file is not None:
        field_bytes = math.ceil(program.prime.bit_length() / 8)
        write_binary_memory(memory_file, runner.relocated_memory, field_bytes)

    if args.air_public_input is not None:
        rc_min, rc_max = runner.get_perm_range_check_limits()
        write_air_public_input(
            layout=args.layout,
            public_input_file=args.air_public_input,
            memory=runner.relocated_memory,
            public_memory_addresses=runner.segments.
            get_public_memory_addresses(runner.segment_offsets),
            memory_segment_addresses=runner.get_memory_segment_addresses(),
            trace=runner.relocated_trace,
            rc_min=rc_min,
            rc_max=rc_max)

    if args.air_private_input is not None:
        assert args.trace_file is not None, \
            '--trace_file must be set when --air_private_input is set.'
        assert args.memory_file is not None, \
            '--memory_file must be set when --air_private_input is set.'
        json.dump(
            {
                'trace_path': f'{os.path.abspath(trace_file.name)}',
                'memory_path': f'{os.path.abspath(memory_file.name)}',
                **runner.get_air_private_input(),
            },
            args.air_private_input,
            indent=4)
        print(file=args.air_private_input)
        args.air_private_input.flush()

    if debug_info_file is not None:
        json.dump(DebugInfo.Schema().dump(runner.get_relocated_debug_info()),
                  debug_info_file)
        debug_info_file.flush()

    if args.tracer:
        CAIRO_TRACER = 'starkware.cairo.lang.tracer.tracer'
        subprocess.call(
            list(
                filter(None, [
                    sys.executable,
                    '-m',
                    CAIRO_TRACER,
                    f'--program={args.program.name}',
                    f'--trace={trace_file.name}',
                    f'--memory={memory_file.name}',
                    f'--air_public_input={args.air_public_input.name}'
                    if args.air_public_input else None,
                    f'--debug_info={debug_info_file.name}',
                ])))

    if args.profile_output is not None:
        CAIRO_PROFILER = 'starkware.cairo.lang.tracer.profiler'
        subprocess.call(
            list(
                filter(None, [
                    sys.executable,
                    '-m',
                    CAIRO_PROFILER,
                    f'--program={args.program.name}',
                    f'--trace={trace_file.name}',
                    f'--memory={memory_file.name}',
                    f'--air_public_input={args.air_public_input.name}'
                    if args.air_public_input else None,
                    f'--debug_info={debug_info_file.name}',
                    f'--profile_output={args.profile_output}',
                ])))

    return ret_code