From 53bf2974b3a742ecfe821b071f960d86ba41f926 Mon Sep 17 00:00:00 2001 From: "Abdel @ StarkWare" Date: Fri, 20 Oct 2023 11:03:48 +0200 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20`CairoVM.updatePc`=20function=20and?= =?UTF-8?q?=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 24 +-- src/vm/core.zig | 274 ++++++++++++++++++++++++++++++++-- src/vm/memory/relocatable.zig | 56 +++++-- 3 files changed, 319 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index eb256b9b..b97f667e 100644 --- a/README.md +++ b/README.md @@ -14,18 +14,6 @@ > _Note that `cairo-zig` is still experimental. Breaking changes will be made before the first stable release. The library is also NOT audited or reviewed for security at the moment. Use at your own risk._ -## TODOs - -- [ ] Add test coverage (investigate using [kcov](https://github.com/SimonKagstrom/kcov), [code coverage for zig article](https://zig.news/squeek502/code-coverage-for-zig-1dk1)). -- [ ] Benchmark performances. -- [ ] Enable usage as a library. -- [ ] Fuzzing. -- [ ] Differential testing against Cairo VM in Rust. -- [ ] Memory leaks detection (i.e use tools like [valgrind](https://valgrind.org/)). -- [ ] Check [Zig style guide](https://ziglang.org/documentation/master/#Style-Guide) and apply it. -- [ ] Go through the code and check carefully for memory safety issues, i.e make sure we have safe deallocation of memory everywhere. -- [ ] Create documentation. - ## 📦 Installation ### 📋 Prerequisites @@ -76,6 +64,18 @@ OPTIONS: zig build test --summary all ``` +## 🔤 TODOs + +- [ ] Add test coverage (investigate using [kcov](https://github.com/SimonKagstrom/kcov), [code coverage for zig article](https://zig.news/squeek502/code-coverage-for-zig-1dk1)). +- [ ] Benchmark performances. +- [ ] Enable usage as a library. +- [ ] Fuzzing. +- [ ] Differential testing against Cairo VM in Rust. +- [ ] Memory leaks detection (i.e use tools like [valgrind](https://valgrind.org/)). +- [ ] Check [Zig style guide](https://ziglang.org/documentation/master/#Style-Guide) and apply it. +- [ ] Go through the code and check carefully for memory safety issues, i.e make sure we have safe deallocation of memory everywhere. +- [ ] Create documentation. + ## 📄 License This project is licensed under the MIT license. diff --git a/src/vm/core.zig b/src/vm/core.zig index 88ad19fb..30dec70d 100644 --- a/src/vm/core.zig +++ b/src/vm/core.zig @@ -173,23 +173,55 @@ pub const CairoVM = struct { /// - `instruction`: The instruction that was executed. /// - `operands`: The operands of the instruction. pub fn updatePc(self: *CairoVM, instruction: *const instructions.Instruction, operands: OperandsResult) !void { - _ = operands; - switch (instruction.pc_update) { + // ************************************************************ + // * PC UPDATE REGULAR * + // ************************************************************ instructions.PcUpdate.Regular => { + // Update the PC. self.run_context.pc.*.addUintInPlace(instruction.size()); }, + // ************************************************************ + // * PC UPDATE JUMP * + // ************************************************************ instructions.PcUpdate.Jump => { - // TODO: Implement this. - return; + // Check that the res is not null. + if (operands.res == null) { + return error.ResUnconstrainedUsedWithPcUpdateJump; + } + // Check that the res is a relocatable. + const res = operands.res.?.tryIntoRelocatable() catch { + return error.PcUpdateJumpResNotRelocatable; + }; + // Update the PC. + self.run_context.pc.* = res; }, + // ************************************************************ + // * PC UPDATE JUMP REL * + // ************************************************************ instructions.PcUpdate.JumpRel => { - // TODO: Implement this. - return; + // Check that the res is not null. + if (operands.res == null) { + return error.ResUnconstrainedUsedWithPcUpdateJumpRel; + } + // Check that the res is a felt. + const res = operands.res.?.tryIntoFelt() catch { + return error.PcUpdateJumpRelResNotFelt; + }; + // Update the PC. + try self.run_context.pc.*.addFeltInPlace(res); }, + // ************************************************************ + // * PC UPDATE JNZ * + // ************************************************************ instructions.PcUpdate.Jnz => { - // TODO: Implement this. - return; + if (operands.dst.isZero()) { + // Update the PC. + self.run_context.pc.*.addUintInPlace(instruction.size()); + } else { + // Update the PC. + try self.run_context.pc.*.addMaybeRelocatableInplace(operands.op_1); + } }, } } @@ -234,7 +266,7 @@ pub const CairoVM = struct { /// Represents the operands for an instruction. const OperandsResult = struct { dst: relocatable.MaybeRelocatable, - res: relocatable.MaybeRelocatable, + res: ?relocatable.MaybeRelocatable, op_0: relocatable.MaybeRelocatable, op_1: relocatable.MaybeRelocatable, dst_addr: relocatable.MaybeRelocatable, @@ -316,3 +348,227 @@ test "update pc regular with imm" { const pc = vm.getPc(); try expectEqual(pc.offset, 2); } + +test "update pc jump with operands res null" { + + // ************************************************************ + // * SETUP TEST CONTEXT * + // ************************************************************ + // Initialize an allocator. + // TODO: Consider using a testing allocator. + var allocator = std.heap.page_allocator; + var instruction = instructions.Instruction.default(); + instruction.pc_update = instructions.PcUpdate.Jump; + var operands = OperandsResult.default(); + operands.res = null; + // Create a new VM instance. + var vm = try CairoVM.init(&allocator); + defer vm.deinit(); + + // ************************************************************ + // * TEST BODY * + // ************************************************************ + try expectError(error.ResUnconstrainedUsedWithPcUpdateJump, vm.updatePc(&instruction, operands)); +} + +test "update pc jump with operands res not relocatable" { + + // ************************************************************ + // * SETUP TEST CONTEXT * + // ************************************************************ + // Initialize an allocator. + // TODO: Consider using a testing allocator. + var allocator = std.heap.page_allocator; + var instruction = instructions.Instruction.default(); + instruction.pc_update = instructions.PcUpdate.Jump; + var operands = OperandsResult.default(); + operands.res = relocatable.fromU64(0); + // Create a new VM instance. + var vm = try CairoVM.init(&allocator); + defer vm.deinit(); + + // ************************************************************ + // * TEST BODY * + // ************************************************************ + try expectError(error.PcUpdateJumpResNotRelocatable, vm.updatePc(&instruction, operands)); +} + +test "update pc jump with operands res relocatable" { + + // ************************************************************ + // * SETUP TEST CONTEXT * + // ************************************************************ + // Initialize an allocator. + // TODO: Consider using a testing allocator. + var allocator = std.heap.page_allocator; + var instruction = instructions.Instruction.default(); + instruction.pc_update = instructions.PcUpdate.Jump; + var operands = OperandsResult.default(); + operands.res = relocatable.newFromRelocatable(relocatable.Relocatable.new(0, 42)); + // Create a new VM instance. + var vm = try CairoVM.init(&allocator); + defer vm.deinit(); + + // ************************************************************ + // * TEST BODY * + // ************************************************************ + try vm.updatePc(&instruction, operands); + + // ************************************************************ + // * TEST CHECKS * + // ************************************************************ + const pc = vm.getPc(); + try expectEqual(pc.offset, 42); +} + +test "update pc jump rel with operands res null" { + + // ************************************************************ + // * SETUP TEST CONTEXT * + // ************************************************************ + // Initialize an allocator. + // TODO: Consider using a testing allocator. + var allocator = std.heap.page_allocator; + var instruction = instructions.Instruction.default(); + instruction.pc_update = instructions.PcUpdate.JumpRel; + var operands = OperandsResult.default(); + operands.res = null; + // Create a new VM instance. + var vm = try CairoVM.init(&allocator); + defer vm.deinit(); + + // ************************************************************ + // * TEST BODY * + // ************************************************************ + try expectError(error.ResUnconstrainedUsedWithPcUpdateJumpRel, vm.updatePc(&instruction, operands)); +} + +test "update pc jump rel with operands res not felt" { + + // ************************************************************ + // * SETUP TEST CONTEXT * + // ************************************************************ + // Initialize an allocator. + // TODO: Consider using a testing allocator. + var allocator = std.heap.page_allocator; + var instruction = instructions.Instruction.default(); + instruction.pc_update = instructions.PcUpdate.JumpRel; + var operands = OperandsResult.default(); + operands.res = relocatable.newFromRelocatable(relocatable.Relocatable.new(0, 42)); + // Create a new VM instance. + var vm = try CairoVM.init(&allocator); + defer vm.deinit(); + + // ************************************************************ + // * TEST BODY * + // ************************************************************ + try expectError(error.PcUpdateJumpRelResNotFelt, vm.updatePc(&instruction, operands)); +} + +test "update pc jump rel with operands res felt" { + + // ************************************************************ + // * SETUP TEST CONTEXT * + // ************************************************************ + // Initialize an allocator. + // TODO: Consider using a testing allocator. + var allocator = std.heap.page_allocator; + var instruction = instructions.Instruction.default(); + instruction.pc_update = instructions.PcUpdate.JumpRel; + var operands = OperandsResult.default(); + operands.res = relocatable.fromU64(42); + // Create a new VM instance. + var vm = try CairoVM.init(&allocator); + defer vm.deinit(); + + // ************************************************************ + // * TEST BODY * + // ************************************************************ + try vm.updatePc(&instruction, operands); + + // ************************************************************ + // * TEST CHECKS * + // ************************************************************ + const pc = vm.getPc(); + try expectEqual(pc.offset, 42); +} + +test "update pc update jnz with operands dst zero" { + + // ************************************************************ + // * SETUP TEST CONTEXT * + // ************************************************************ + // Initialize an allocator. + // TODO: Consider using a testing allocator. + var allocator = std.heap.page_allocator; + var instruction = instructions.Instruction.default(); + instruction.pc_update = instructions.PcUpdate.Jnz; + var operands = OperandsResult.default(); + operands.dst = relocatable.fromU64(0); + // Create a new VM instance. + var vm = try CairoVM.init(&allocator); + defer vm.deinit(); + + // ************************************************************ + // * TEST BODY * + // ************************************************************ + try vm.updatePc(&instruction, operands); + + // ************************************************************ + // * TEST CHECKS * + // ************************************************************ + const pc = vm.getPc(); + try expectEqual(pc.offset, 2); +} + +test "update pc update jnz with operands dst not zero op1 not felt" { + + // ************************************************************ + // * SETUP TEST CONTEXT * + // ************************************************************ + // Initialize an allocator. + // TODO: Consider using a testing allocator. + var allocator = std.heap.page_allocator; + var instruction = instructions.Instruction.default(); + instruction.pc_update = instructions.PcUpdate.Jnz; + var operands = OperandsResult.default(); + operands.dst = relocatable.fromU64(1); + operands.op_1 = relocatable.newFromRelocatable(relocatable.Relocatable.new(0, 42)); + // Create a new VM instance. + var vm = try CairoVM.init(&allocator); + defer vm.deinit(); + + // ************************************************************ + // * TEST BODY * + // ************************************************************ + try expectError(error.TypeMismatchNotFelt, vm.updatePc(&instruction, operands)); +} + +test "update pc update jnz with operands dst not zero op1 felt" { + + // ************************************************************ + // * SETUP TEST CONTEXT * + // ************************************************************ + // Initialize an allocator. + // TODO: Consider using a testing allocator. + var allocator = std.heap.page_allocator; + var instruction = instructions.Instruction.default(); + instruction.pc_update = instructions.PcUpdate.Jnz; + var operands = OperandsResult.default(); + operands.dst = relocatable.fromU64(1); + operands.op_1 = relocatable.fromU64(42); + // Create a new VM instance. + var vm = try CairoVM.init(&allocator); + defer vm.deinit(); + + // ************************************************************ + // * TEST BODY * + // ************************************************************ + try vm.updatePc(&instruction, operands); + + // ************************************************************ + // * TEST CHECKS * + // ************************************************************ + const pc = vm.getPc(); + try expectEqual(pc.offset, 42); +} diff --git a/src/vm/memory/relocatable.zig b/src/vm/memory/relocatable.zig index 856f76f3..0b238a2d 100644 --- a/src/vm/memory/relocatable.zig +++ b/src/vm/memory/relocatable.zig @@ -1,5 +1,5 @@ const std = @import("std"); -const starknet_felt = @import("../../math/fields/starknet.zig"); +const Felt252 = @import("../../math/fields/starknet.zig").Felt252; const CairoVMError = @import("../error.zig").CairoVMError; // Relocatable in the Cairo VM represents an address @@ -91,13 +91,31 @@ pub const Relocatable = struct { } return self.addUint(@as(u64, @intCast(other))); } + + /// Add a felt to this Relocatable, modifying it in place. + /// # Arguments + /// - self: Pointer to the Relocatable object to modify. + /// - other: The felt to add to `self.offset`. + pub fn addFeltInPlace(self: *Relocatable, other: Felt252) !void { + const new_offset_felt = Felt252.fromInteger(@as(u256, self.offset)).add(other); + const new_offset = try new_offset_felt.tryIntoU64(); + self.offset = new_offset; + } + + /// Performs additions if other contains a Felt value, fails otherwise. + /// # Arguments + /// - other - The other MaybeRelocatable to add. + pub fn addMaybeRelocatableInplace(self: *Relocatable, other: MaybeRelocatable) !void { + const other_as_felt = try other.tryIntoFelt(); + try self.addFeltInPlace(other_as_felt); + } }; // MaybeRelocatable is the type of the memory cells in the Cairo // VM. It can either be a Relocatable or a field element. pub const MaybeRelocatable = union(enum) { relocatable: Relocatable, - felt: starknet_felt.Felt252, + felt: Felt252, /// Determines if two `MaybeRelocatable` instances are equal. /// @@ -130,19 +148,19 @@ pub const MaybeRelocatable = union(enum) { }; } - // Return the value of the MaybeRelocatable as a felt or error. - // # Returns - // The value of the MaybeRelocatable as a Relocatable felt or error. - pub fn tryIntoFelt(self: MaybeRelocatable) error{TypeMismatchNotFelt}!starknet_felt.Felt252 { + /// Return the value of the MaybeRelocatable as a felt or error. + /// # Returns + /// The value of the MaybeRelocatable as a Relocatable felt or error. + pub fn tryIntoFelt(self: MaybeRelocatable) error{TypeMismatchNotFelt}!Felt252 { return switch (self) { .relocatable => CairoVMError.TypeMismatchNotFelt, .felt => |felt| felt, }; } - // Return the value of the MaybeRelocatable as a felt or error. - // # Returns - // The value of the MaybeRelocatable as a Relocatable felt or error. + /// Return the value of the MaybeRelocatable as a felt or error. + /// # Returns + /// The value of the MaybeRelocatable as a Relocatable felt or error. pub fn tryIntoU64(self: MaybeRelocatable) error{ TypeMismatchNotFelt, ValueTooLarge }!u64 { return switch (self) { .relocatable => CairoVMError.TypeMismatchNotFelt, @@ -150,15 +168,25 @@ pub const MaybeRelocatable = union(enum) { }; } - // Return the value of the MaybeRelocatable as a Relocatable. - // # Returns - // The value of the MaybeRelocatable as a Relocatable. + /// Return the value of the MaybeRelocatable as a Relocatable. + /// # Returns + /// The value of the MaybeRelocatable as a Relocatable. pub fn tryIntoRelocatable(self: MaybeRelocatable) !Relocatable { return switch (self) { .relocatable => |relocatable| relocatable, .felt => error.TypeMismatchNotRelocatable, }; } + + /// Whether the MaybeRelocatable is zero or not. + /// # Returns + /// true if the MaybeRelocatable is zero, false otherwise. + pub fn isZero(self: MaybeRelocatable) bool { + return switch (self) { + .relocatable => false, + .felt => |felt| felt.isZero(), + }; + } }; // Creates a new MaybeRelocatable from a Relocatable. @@ -175,7 +203,7 @@ pub fn newFromRelocatable(relocatable: Relocatable) MaybeRelocatable { // - felt - The field element to create the MaybeRelocatable from. // # Returns // A new MaybeRelocatable. -pub fn fromFelt(felt: starknet_felt.Felt252) MaybeRelocatable { +pub fn fromFelt(felt: Felt252) MaybeRelocatable { return MaybeRelocatable{ .felt = felt }; } @@ -185,7 +213,7 @@ pub fn fromFelt(felt: starknet_felt.Felt252) MaybeRelocatable { // # Returns // A new MaybeRelocatable. pub fn fromU256(value: u256) MaybeRelocatable { - return MaybeRelocatable{ .felt = starknet_felt.Felt252.fromInteger(value) }; + return MaybeRelocatable{ .felt = Felt252.fromInteger(value) }; } // Creates a new MaybeRelocatable from a u64.