def test_memory_dict_serialize(): memory = MemoryDict({1: 2, 3: 4, 5: 6}) expected_serialized = bytes([ 1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0]) serialized = memory.serialize(3) assert expected_serialized == serialized assert MemoryDict.deserialize(serialized, 3) == memory
def test_memory_dict_setdefault(): md = MemoryDict({14: 15}) md.setdefault(14, 0) assert md[14] == 15 md.setdefault(123, 456) assert md[123] == 456 with pytest.raises(ValueError, match='must be an int'): md.setdefault(10, 'default') with pytest.raises(ValueError, match='must be positive'): md.setdefault(-10, 123)
def test_memory_dict_setdefault(): memory = MemoryDict({14: 15}) memory.setdefault(14, 0) assert memory[14] == 15 memory.setdefault(123, 456) assert memory[123] == 456 with pytest.raises(ValueError, match='must be an int'): memory.setdefault(10, 'default') with pytest.raises(KeyError, match='must be nonnegative'): memory.setdefault(-10, 123) with pytest.raises(ValueError, match='The offset of a relocatable value must be nonnegative'): memory[RelocatableValue(segment_index=10, offset=-2)] = 13
def test_relocate_segments(): segments = MemorySegmentManager(memory=MemoryDict({}), prime=PRIME) for i in range(5): assert segments.add() == RelocatableValue(segment_index=i, offset=0) segment_sizes = [3, 8, 0, 1, 2] public_memory_offsets = [ [(0, 0), (1, 1)], [(i, 0) for i in range(8)], [], [], [(1, 2)], ] # These segments are not finalized. assert segments.add() == RelocatableValue(segment_index=5, offset=0) assert segments.add() == RelocatableValue(segment_index=6, offset=0) segments.memory[RelocatableValue(5, 4)] = 0 segments.memory.freeze() segments.compute_effective_sizes() for i, (size, public_memory) in enumerate( zip(segment_sizes, public_memory_offsets)): segments.finalize(i, size=size, public_memory=public_memory) segment_offsets = segments.relocate_segments() assert segment_offsets == {0: 1, 1: 4, 2: 12, 3: 12, 4: 13, 5: 15, 6: 20} assert segments.get_public_memory_addresses(segment_offsets) == [(1, 0), (2, 1), (4, 0), (5, 0), (6, 0), (7, 0), (8, 0), (9, 0), (10, 0), (11, 0), (14, 2)] # Negative flows. segments = MemorySegmentManager(memory=MemoryDict({}), prime=PRIME) segments.add(size=1) with pytest.raises(AssertionError, match='compute_effective_sizes must be called before'): segments.relocate_segments() segments.memory[RelocatableValue(0, 2)] = 0 segments.memory.freeze() segments.compute_effective_sizes() with pytest.raises(AssertionError, match='Segment 0 exceeded its allocated size'): segments.relocate_segments()
def test_get_segment_used_size(): memory = MemoryDict({ RelocatableValue(0, 0): 0, RelocatableValue(0, 2): 0, RelocatableValue(1, 5): 0, RelocatableValue(1, 7): 0, RelocatableValue(3, 0): 0, RelocatableValue(4, 1): 0, }) segments = MemorySegmentManager(memory=memory, prime=PRIME) segments.n_segments = 5 memory.freeze() segments.compute_effective_sizes() assert [segments.get_segment_used_size(i) for i in range(5)] == [3, 8, 0, 1, 2]
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
def write_binary_memory(memory_file: BinaryIO, memory: MemoryDict, field_bytes: int): """ Dumps the memory file. """ memory_file.write(memory.serialize(field_bytes)) memory_file.flush()
def from_file(cls, fileobj) -> 'CairoPie': """ Loads an instance of CairoPie from a file. `fileobj` can be a path or a file object. """ if isinstance(fileobj, str): fileobj = open(fileobj, 'rb') verify_zip_file_prefix(fileobj=fileobj) with zipfile.ZipFile(fileobj) as zf: cls.verify_zip_format(zf) with zf.open(cls.METADATA_FILENAME, 'r') as fp: metadata = CairoPieMetadata.Schema().load( json.loads(fp.read(cls.MAX_SIZE).decode('ascii'))) with zf.open(cls.MEMORY_FILENAME, 'r') as fp: memory = MemoryDict.deserialize( data=fp.read(cls.MAX_SIZE), field_bytes=metadata.field_bytes, ) with zf.open(cls.ADDITIONAL_DATA_FILENAME, 'r') as fp: additional_data = json.loads( fp.read(cls.MAX_SIZE).decode('ascii')) with zf.open(cls.EXECUTION_RESOURCES_FILENAME, 'r') as fp: execution_resources = ExecutionResources.Schema().load( json.loads(fp.read(cls.MAX_SIZE).decode('ascii'))) return cls(metadata, memory, additional_data, execution_resources)
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 read_memory(memory_path: str, field_bytes: int) -> MemoryDict: """ Returns the memory (as a MemoryDict). """ # Use MemoryDict to verify that memory cells are consistent. with open(memory_path, 'rb') as memory_file: return MemoryDict.deserialize(memory_file.read(), field_bytes)
def test_memory_dict_get(): md = MemoryDict({14: 15}) assert md.get(14, 'default') == 15 assert md.get(1234, 'default') == 'default' with pytest.raises(ValueError, match='must be nonnegative'): md.get(-10, 'default') # Attempting to read address with a negative offset is ok, it simply returns None. assert md.get(RelocatableValue(segment_index=10, offset=-2)) is None
def test_get_segment_used_size(): memory = MemoryDict({ RelocatableValue(0, 0): 0, RelocatableValue(0, 2): 0, RelocatableValue(1, 5): 0, RelocatableValue(1, 7): 0, RelocatableValue(3, 0): 0, RelocatableValue(4, 1): 0, }) assert [get_segment_used_size(i, memory) for i in range(5)] == [3, 8, 0, 1, 2]
def test_segment_relocation_failures(): memory = MemoryDict() relocation_target = RelocatableValue(segment_index=4, offset=25) with pytest.raises(AssertionError, match='src_ptr.segment_index must be < 0, src_ptr=1:2.'): memory.add_relocation_rule(src_ptr=RelocatableValue( segment_index=1, offset=2), dest_ptr=relocation_target) with pytest.raises(AssertionError, match='src_ptr.offset must be 0, src_ptr=-3:2.'): memory.add_relocation_rule(src_ptr=RelocatableValue( segment_index=-3, offset=2), dest_ptr=relocation_target) memory.add_relocation_rule(src_ptr=RelocatableValue( segment_index=-3, offset=0), dest_ptr=relocation_target) with pytest.raises( AssertionError, match='The segment with index -3 already has a relocation rule.'): memory.add_relocation_rule(src_ptr=RelocatableValue( segment_index=-3, offset=0), dest_ptr=relocation_target)
def test_memory_dict_check_element(): memory = MemoryDict() with pytest.raises(KeyError, match='must be an int'): memory['not a number'] = 12 with pytest.raises(KeyError, match='must be nonnegative'): memory[-12] = 13 with pytest.raises(ValueError, match='The offset of a relocatable value must be nonnegative'): memory[RelocatableValue(segment_index=10, offset=-2)] = 13 # A value may have a negative offset. memory[13] = RelocatableValue(segment_index=10, offset=-2)
def test_memory_dict(): d = {1: 2} mem = MemoryDict(d) d[2] = 3 assert 2 not in mem assert mem[1] == 2 with pytest.raises(UnknownMemoryError): mem[2] mem[1] = 2 with pytest.raises(InconsistentMemoryError): mem[1] = 3
def test_gen_args(): segments = MemorySegmentManager(memory=MemoryDict({}), prime=PRIME) test_array = [2, 3, 7] arg = [1, test_array, [4, -1]] ptr = segments.gen_arg(arg) memory = segments.memory assert memory[ptr] == 1 memory.get_range(memory[ptr + 1], len(test_array)) == test_array memory.get_range(memory[ptr + 2], 2) == [4, PRIME - 1]
def relocate(self): self.segment_offsets = self.segments.relocate_segments() self.relocated_memory = MemoryDict({ self.relocate_value(addr): self.relocate_value(value) for addr, value in self.vm_memory.items() }) self.relocated_trace = relocate_trace(self.vm.trace, self.segment_offsets, self.program.prime) for builtin_runner in self.builtin_runners.values(): builtin_runner.relocate(self.relocate_value)
def test_memory_dict_get(): memory = MemoryDict({14: 15}) assert memory.get(14, 'default') == 15 assert memory.get(1234, 'default') == 'default' assert memory.get(-10, 'default') == 'default' # Attempting to read address with a negative offset is ok, it simply returns None. assert memory.get(RelocatableValue(segment_index=10, offset=-2)) is None
def get_segment_used_size(segment_index: int, memory: MemoryDict) -> int: """ Returns the used size of the given memory segment by finding which is the maximal offset that was accessed. """ max_offset = -1 for addr in memory.keys(): assert isinstance(addr, RelocatableValue), \ f'Expected memory address to be relocatable value. Found: {addr}.' if addr.segment_index != segment_index: continue max_offset = max(max_offset, addr.offset) return max_offset + 1
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_validated_memory_dict(): memory = MemoryDict() memory_validator = ValidatedMemoryDict(memory=memory) def rule_identical_pairs(mem, addr): """ Validates that the values in address pairs (i, i+1), where i is even, are identical. """ offset_diff = (-1) ** (addr.offset % 2) other_addr = RelocatableValue.from_tuple((addr.segment_index, addr.offset + offset_diff)) if other_addr in mem: assert mem[addr] == mem[other_addr] return {addr, other_addr} return set() def rule_constant_value(mem, addr, constant): assert mem[addr] == constant, \ f'Expected value in address {addr} to be {constant}, got {mem[addr]}.' return {addr} memory_validator.add_validation_rule(1, lambda memory, addr: set()) memory_validator.add_validation_rule(2, lambda memory, addr: {addr}) memory_validator.add_validation_rule(3, rule_identical_pairs) memory_validator.add_validation_rule(4, rule_constant_value, 0) addr0 = RelocatableValue.from_tuple((1, 0)) addr1 = RelocatableValue.from_tuple((2, 0)) addr2 = RelocatableValue.from_tuple((3, 0)) addr3 = RelocatableValue.from_tuple((3, 1)) addr4 = RelocatableValue.from_tuple((4, 0)) # Test validated_addresses update. memory_validator[addr0] = 0 assert memory_validator._ValidatedMemoryDict__validated_addresses == set() memory_validator[addr1] = 0 assert memory_validator._ValidatedMemoryDict__validated_addresses == {addr1} # Test validation rule application. memory_validator[addr2] = 1 assert memory_validator._ValidatedMemoryDict__validated_addresses == {addr1} memory_validator[addr3] = 1 assert memory_validator._ValidatedMemoryDict__validated_addresses == {addr1, addr2, addr3} with pytest.raises( AssertionError, match='Expected value in address 4:0 to be 0, got 1.'): memory_validator[addr4] = 1
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_segment_relocations(): memory = MemoryDict() temp_segment = RelocatableValue(segment_index=-1, offset=0) memory[5] = temp_segment + 2 assert memory[5] == RelocatableValue(segment_index=-1, offset=2) relocation_target = RelocatableValue(segment_index=4, offset=25) memory.add_relocation_rule(src_ptr=temp_segment, dest_ptr=relocation_target) assert memory[5] == relocation_target + 2 memory[temp_segment + 3] = 17 memory.relocate_memory() assert memory.data == { 5: relocation_target + 2, relocation_target + 3: 17, }
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_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_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_dict_multiple_values(): memory = MemoryDict({5: 10}) memory[5] = 10 memory[5] = 10 with pytest.raises(InconsistentMemoryError): memory[5] = 11
def test_memory_dict_in(): memory = MemoryDict({1: 2, 3: 4}) assert 1 in memory assert 2 not in memory # Test that `in` doesn't add the value to the dict. assert 2 not in memory
def test_memory_dict_getitem(): memory = MemoryDict({11: 12}) with pytest.raises(UnknownMemoryError): memory[12]