def test_main_args_match_builtins(): """ Checks that an appropriate exception is thrown if the arguments in a given Cairo program's main don't match the list of builtins specified by the directive (and their order). """ expected_error_msg = 'Expected main to contain the following arguments (in this order): ' \ "['output_ptr', 'range_check_ptr']" with pytest.raises(AssertionError, match=re.escape(expected_error_msg)): compile_cairo(code=""" %builtins output range_check func main(output_ptr) -> (output_ptr): return (output_ptr=output_ptr + 1) end """, prime=PRIME) # Check that even if all builtin ptrs were passed as arguments but in the wrong order then # the same exception is thrown. with pytest.raises(AssertionError, match=re.escape(expected_error_msg)): compile_cairo(code=""" %builtins output range_check func main(range_check_ptr, output_ptr) -> (range_check_ptr, output_ptr): return (range_check_ptr + 1, output_ptr=output_ptr + 1) end """, prime=PRIME)
def test_secure_hints_serialization(): template_program = compile_cairo(ALLOWED_CODE, DEFAULT_PRIME) whitelist = HintsWhitelist.from_program(template_program) data = HintsWhitelist.Schema().dumps(whitelist) whitelist = HintsWhitelist.Schema().loads(data) for good_code in GOOD_CODES: program = compile_cairo(good_code, DEFAULT_PRIME) whitelist.verify_program_hint_secure(program)
def test_secure_hints_cases(): template_program = compile_cairo(ALLOWED_CODE, DEFAULT_PRIME) whitelist = HintsWhitelist.from_program(template_program) for good_code in GOOD_CODES: program = compile_cairo(good_code, DEFAULT_PRIME) whitelist.verify_program_hint_secure(program) for bad_code, message in BAD_CODES: program = compile_cairo(bad_code, DEFAULT_PRIME) with pytest.raises(InsecureHintError, match=re.escape(message)): whitelist.verify_program_hint_secure(program)
def test_main_return_match_builtins(): """ Checks that an appropriate exception is thrown if the arguments in a given Cairo program's main don't match the list of builtins specified by the directive (and their order). """ expected_error_msg = 'Expected main to return the following values (in this order): ' \ "['output_ptr', 'range_check_ptr']" with pytest.raises(AssertionError, match=re.escape(expected_error_msg)): compile_cairo(code=""" %builtins output range_check func main(output_ptr, range_check_ptr) -> (output_ptr): return (output_ptr=output_ptr + 1) end """, prime=PRIME)
def run_single(code: str, steps: int, *, pc=RelocatableValue(0, 10), ap=100, fp=100, extra_mem={}): program = compile_cairo(code, PRIME, debug_info=True) # Set memory[fp - 1] to an arbitrary value, since [fp - 1] is assumed to be set. memory: Dict[MaybeRelocatable, MaybeRelocatable] = { **{pc + i: v for i, v in enumerate(program.data)}, fp - 1: 1234, **extra_mem } context = RunContext( pc=pc, ap=ap, fp=fp, memory=MemoryDict(memory), prime=PRIME, ) vm = VirtualMachine(program, context, {}) for _ in range(steps): vm.step() return vm
def test_jmp_segment(): code = """ jmp abs [ap]; ap++ """ program = compile_cairo(code=code, prime=PRIME, debug_info=True) program_base_a = RelocatableValue(0, 10) program_base_b = RelocatableValue(1, 20) memory = { **{program_base_a + i: v for i, v in enumerate(program.data)}, **{program_base_b + i: v for i, v in enumerate(program.data)}, 99: 0, 100: program_base_b, 101: program_base_a } context = RunContext( pc=program_base_a, ap=100, fp=100, memory=MemoryDict(memory), prime=PRIME, ) vm = VirtualMachine(program, context, {}) vm.step() assert vm.run_context.pc == program_base_b assert vm.get_location(vm.run_context.pc) is None vm.step() assert vm.run_context.pc == program_base_a assert vm.get_location(vm.run_context.pc) is not None
def get_runner_from_code(code: Union[str, Sequence[Tuple[str, str]]], layout: str, prime: int) -> CairoRunner: """ Given a code with some compile and run parameters (prime, layout, etc.), runs the code using Cairo runner and returns the runner. """ program = compile_cairo(code=code, prime=prime, debug_info=True) return get_main_runner(program=program, hint_locals={}, layout=layout)
def test_reconstruct_traceback(): code = """ func bar(): assert 0 = 1 return () end func foo(): bar() return () end func main(): foo() return () end """ codes = [(code, 'filename')] program_with_debug_info = compile_cairo(code=codes, prime=DEFAULT_PRIME, debug_info=True) program_without_debug_info = compile_cairo(code=codes, prime=DEFAULT_PRIME, debug_info=False) with pytest.raises(VmException) as exc: get_main_runner(program=program_without_debug_info, hint_locals={}, layout='plain') exception_str = str(exc.value) # The exception before calling reconstruct_traceback(). assert exception_str == """\ Error at pc=0:2: An ASSERT_EQ instruction failed: 1 != 0 Cairo traceback (most recent call last): Unknown location (pc=0:8) Unknown location (pc=0:5)\ """ res = reconstruct_traceback(program=program_with_debug_info, traceback_txt=exception_str) # The exception after calling reconstruct_traceback(). assert res == """\
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')
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
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()
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
def test_hint_exception(): code = """ # Some comment. %{ x = 0 %} %{ def f(): 0 / 0 # Raises exception. %} [ap] = 0; ap++ %{ y = 0 %} %{ f() %} [ap] = 1; ap++ """ # In this test we actually do write the code to a file, to allow the linecache module to fetch # the line raising the exception. cairo_file = tempfile.NamedTemporaryFile('w') print(code, file=cairo_file) cairo_file.flush() program = compile_cairo(code=[(code, cairo_file.name)], prime=PRIME, debug_info=True) program_base = 10 memory = {program_base + i: v for i, v in enumerate(program.data)} # Set memory[fp - 1] to an arbitrary value, since [fp - 1] is assumed to be set. memory[99] = 1234 context = RunContext( pc=program_base, ap=200, fp=100, memory=MemoryDict(memory), prime=PRIME, ) vm = VirtualMachine(program, context, {}) vm.step() with pytest.raises(VmException) as excinfo: vm.step() assert str(excinfo.value) == f"""\
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)
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
def get_runner_from_code(code: Union[str, Sequence[Tuple[str, str]]], layout: str, prime: int) -> CairoRunner: """ Given a code with some compile and run parameters (prime, layout, etc.), runs the code using Cairo runner and returns the runner. """ program = compile_cairo(code=code, prime=prime, debug_info=True) runner = CairoRunner(program, layout=layout) runner.initialize_segments() end = runner.initialize_main_entrypoint() runner.initialize_vm(hint_locals={}) runner.run_until_pc(end) runner.read_return_values() runner.finalize_segments_by_effective_size() runner.end_run() return runner
def test_skip_instruction_execution(): code = """ %{ x = 0 vm.run_context.pc += 2 vm.skip_instruction_execution = True %} [ap] = [ap] + 1; ap++ # This intruction will not be executed. %{ x = 1 %} [ap] = 10; ap++ """ program = compile_cairo(code, PRIME, debug_info=True) initial_ap = 100 memory: Dict[MaybeRelocatable, MaybeRelocatable] = { **{i: v for i, v in enumerate(program.data)}, initial_ap - 1: 1234, } context = RunContext( pc=0, ap=initial_ap, fp=initial_ap, memory=MemoryDict(memory), prime=PRIME, ) vm = VirtualMachine(program, context, {}) vm.enter_scope({'vm': vm}) exec_locals = vm.exec_scopes[-1] assert 'x' not in exec_locals assert vm.run_context.pc == 0 vm.step() assert exec_locals['x'] == 0 assert vm.run_context.pc == 2 vm.step() assert exec_locals['x'] == 1 assert vm.run_context.pc == 4 assert vm.run_context.ap == initial_ap + 1 assert vm.run_context.memory[vm.run_context.ap - 1] == 10 vm.exit_scope()
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()
def test_cairo_pie_serialize_deserialize(): program = compile_cairo( code=[('%builtins output pedersen range_check ecdsa\nmain:\n[ap] = [ap]\n', '')], prime=DEFAULT_PRIME) metadata = CairoPieMetadata( program=program.stripped(), program_segment=SegmentInfo(0, 10), execution_segment=SegmentInfo(1, 20), ret_fp_segment=SegmentInfo(6, 12), ret_pc_segment=SegmentInfo(7, 21), builtin_segments={ 'a': SegmentInfo(4, 15), }, extra_segments=[], ) memory = MemoryDict({ 1: 2, RelocatableValue(3, 4): RelocatableValue(6, 7), }) additional_data = {'c': ['d', 3]} execution_resources = ExecutionResources( n_steps=10, n_memory_holes=7, builtin_instance_counter={ 'output': 6, 'pedersen': 3, } ) cairo_pie = CairoPie( metadata=metadata, memory=memory, additional_data=additional_data, execution_resources=execution_resources ) fileobj = io.BytesIO() cairo_pie.to_file(fileobj) actual_cairo_pie = CairoPie.from_file(fileobj) assert cairo_pie == actual_cairo_pie
def test_auto_deduction_rules(): code = """ [fp + 1] = [fp] + [ap] """ program = compile_cairo(code=code, prime=PRIME, debug_info=True) memory = {i: v for i, v in enumerate(program.data)} initial_ap = RelocatableValue(segment_index=1, offset=200) initial_fp = RelocatableValue(segment_index=2, offset=100) context = RunContext( pc=0, ap=initial_ap, fp=initial_fp, memory=MemoryDict(memory), prime=PRIME, ) vm = VirtualMachine(program, context, {}) def rule_ap_segment(vm, addr, val): return val vm.add_auto_deduction_rule(1, rule_ap_segment, 100) vm.add_auto_deduction_rule(2, lambda vm, addr: None) vm.add_auto_deduction_rule( 2, lambda vm, addr: 200 if addr == initial_fp else None) vm.add_auto_deduction_rule(2, lambda vm, addr: 456) vm.step() assert vm.run_context.memory[initial_ap] == 100 assert vm.run_context.memory[initial_fp] == 200 assert vm.run_context.memory[initial_fp + 1] == 300 with pytest.raises(InconsistentAutoDeductionError, match='at address 2:100. 200 != 456'): vm.verify_auto_deductions()
def test_memory_validation_in_hints(): code = """ %{ memory[ap] = 0 %} [ap] = [ap]; ap++ %{ memory[ap] = 0 %} [ap] = [ap]; ap++ """ program = compile_cairo(code=code, prime=PRIME, debug_info=True) initial_ap_and_fp = RelocatableValue(segment_index=1, offset=200) memory = {i: v for i, v in enumerate(program.data)} # Set memory[fp - 1] to an arbitrary value, since [fp - 1] is assumed to be set. memory[initial_ap_and_fp - 1] = 1234 context = RunContext( pc=0, ap=initial_ap_and_fp, fp=initial_ap_and_fp, memory=MemoryDict(memory), prime=PRIME, ) vm = VirtualMachine(program, context, {}) vm.add_validation_rule(1, lambda memory, addr: {addr}) assert vm.validated_memory._ValidatedMemoryDict__validated_addresses == set( ) vm.step() assert vm.validated_memory._ValidatedMemoryDict__validated_addresses == { initial_ap_and_fp } def fail_validation(memory, addr): raise Exception('Validation failed.') vm.add_validation_rule(1, fail_validation) with pytest.raises(VmException, match='Exception: Validation failed.'): vm.step()
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()
def test_collision(): """ Tests multiple hints with the same code but different reference expressions. """ code = """ func f(): let b = [ap] %{ ids.b = 1 %} ret end func g(): let b = [ap - 10] %{ ids.b = 1 %} ret end """ program = compile_cairo(code, DEFAULT_PRIME) whitelist = HintsWhitelist.from_program(program) assert len(whitelist.allowed_reference_expressions_for_hint) == 1 whitelist.verify_program_hint_secure(program)
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' }