From 747dc4a492d0f35e4894edb026f6273c7805d0a7 Mon Sep 17 00:00:00 2001 From: oxlime <93354898+oxlime@users.noreply.github.com> Date: Tue, 7 Nov 2023 06:49:29 -0600 Subject: [PATCH] Range Check builtin (#100) * range check builtin * range check runner * memory validation rule * range check and validation rule * fixes * format * fixed * some fixes * try --------- Co-authored-by: lanaivina <31368580+lana-shanghai@users.noreply.github.com> --- src/vm/builtins/builtin_runner/bitwise.zig | 2 +- .../builtin_runner/builtin_runner.zig | 18 +- src/vm/builtins/builtin_runner/ec_op.zig | 2 +- src/vm/builtins/builtin_runner/hash.zig | 2 +- src/vm/builtins/builtin_runner/keccak.zig | 2 +- src/vm/builtins/builtin_runner/output.zig | 2 +- src/vm/builtins/builtin_runner/poseidon.zig | 2 +- .../builtins/builtin_runner/range_check.zig | 249 +++++++++++++++++- .../builtins/builtin_runner/segment_arena.zig | 2 +- src/vm/builtins/builtin_runner/signature.zig | 2 +- src/vm/error.zig | 11 + src/vm/memory/memory.zig | 67 +++++ src/vm/memory/segments.zig | 10 + src/vm/types/range_check_instance_def.zig | 14 + 14 files changed, 362 insertions(+), 23 deletions(-) diff --git a/src/vm/builtins/builtin_runner/bitwise.zig b/src/vm/builtins/builtin_runner/bitwise.zig index b44199a7..b7246803 100644 --- a/src/vm/builtins/builtin_runner/bitwise.zig +++ b/src/vm/builtins/builtin_runner/bitwise.zig @@ -55,7 +55,7 @@ pub const BitwiseBuiltinRunner = struct { /// # Returns /// /// The base value as a `usize`. - pub fn get_base(self: *const Self) usize { + pub fn getBase(self: *const Self) usize { return self.base; } }; diff --git a/src/vm/builtins/builtin_runner/builtin_runner.zig b/src/vm/builtins/builtin_runner/builtin_runner.zig index 4d801fa2..999b2287 100644 --- a/src/vm/builtins/builtin_runner/builtin_runner.zig +++ b/src/vm/builtins/builtin_runner/builtin_runner.zig @@ -40,15 +40,15 @@ pub const BuiltinRunner = union(enum) { /// The base value as a `usize`. pub fn base(self: *const Self) usize { return switch (self.*) { - .Bitwise => |*bitwise| bitwise.get_base(), - .EcOp => |*ec| ec.get_base(), - .Hash => |*hash| hash.get_base(), - .Output => |*output| output.get_base(), - .RangeCheck => |*range_check| range_check.get_base(), - .Keccak => |*keccak| keccak.get_base(), - .Signature => |*signature| signature.get_base(), - .Poseidon => |*poseidon| poseidon.get_base(), - .SegmentArena => |*segment_arena| segment_arena.get_base(), + .Bitwise => |*bitwise| bitwise.getBase(), + .EcOp => |*ec| ec.getBase(), + .Hash => |*hash| hash.getBase(), + .Output => |*output| output.getBase(), + .RangeCheck => |*range_check| range_check.getBase(), + .Keccak => |*keccak| keccak.getBase(), + .Signature => |*signature| signature.getBase(), + .Poseidon => |*poseidon| poseidon.getBase(), + .SegmentArena => |*segment_arena| segment_arena.getBase(), }; } }; diff --git a/src/vm/builtins/builtin_runner/ec_op.zig b/src/vm/builtins/builtin_runner/ec_op.zig index 19c314e7..61cfca4c 100644 --- a/src/vm/builtins/builtin_runner/ec_op.zig +++ b/src/vm/builtins/builtin_runner/ec_op.zig @@ -66,7 +66,7 @@ pub const EcOpBuiltinRunner = struct { /// # Returns /// /// The base value as a `usize`. - pub fn get_base(self: *const Self) usize { + pub fn getBase(self: *const Self) usize { return self.base; } }; diff --git a/src/vm/builtins/builtin_runner/hash.zig b/src/vm/builtins/builtin_runner/hash.zig index 6095ee57..10bb048f 100644 --- a/src/vm/builtins/builtin_runner/hash.zig +++ b/src/vm/builtins/builtin_runner/hash.zig @@ -61,7 +61,7 @@ pub const HashBuiltinRunner = struct { /// # Returns /// /// The base value as a `usize`. - pub fn get_base(self: *const Self) usize { + pub fn getBase(self: *const Self) usize { return self.base; } }; diff --git a/src/vm/builtins/builtin_runner/keccak.zig b/src/vm/builtins/builtin_runner/keccak.zig index dc77785b..679816c7 100644 --- a/src/vm/builtins/builtin_runner/keccak.zig +++ b/src/vm/builtins/builtin_runner/keccak.zig @@ -71,7 +71,7 @@ pub const KeccakBuiltinRunner = struct { /// # Returns /// /// The base value as a `usize`. - pub fn get_base(self: *const Self) usize { + pub fn getBase(self: *const Self) usize { return self.base; } }; diff --git a/src/vm/builtins/builtin_runner/output.zig b/src/vm/builtins/builtin_runner/output.zig index a8eb4483..b9419a54 100644 --- a/src/vm/builtins/builtin_runner/output.zig +++ b/src/vm/builtins/builtin_runner/output.zig @@ -33,7 +33,7 @@ pub const OutputBuiltinRunner = struct { /// # Returns /// /// The base value as a `usize`. - pub fn get_base(self: *const Self) usize { + pub fn getBase(self: *const Self) usize { return self.base; } }; diff --git a/src/vm/builtins/builtin_runner/poseidon.zig b/src/vm/builtins/builtin_runner/poseidon.zig index d99e2417..30460498 100644 --- a/src/vm/builtins/builtin_runner/poseidon.zig +++ b/src/vm/builtins/builtin_runner/poseidon.zig @@ -65,7 +65,7 @@ pub const PoseidonBuiltinRunner = struct { /// # Returns /// /// The base value as a `usize`. - pub fn get_base(self: *const Self) usize { + pub fn getBase(self: *const Self) usize { return self.base; } }; diff --git a/src/vm/builtins/builtin_runner/range_check.zig b/src/vm/builtins/builtin_runner/range_check.zig index da384138..93e32e74 100644 --- a/src/vm/builtins/builtin_runner/range_check.zig +++ b/src/vm/builtins/builtin_runner/range_check.zig @@ -1,6 +1,26 @@ +const std = @import("std"); +const Allocator = std.mem.Allocator; +const ArrayList = std.ArrayList; + const Felt252 = @import("../../../math/fields/starknet.zig").Felt252; +const MemorySegmentManager = @import("../../memory/segments.zig").MemorySegmentManager; +const relocatable = @import("../../memory/relocatable.zig"); +const Error = @import("../../error.zig"); +const validation_rule = @import("../../memory/memory.zig").validation_rule; +const Memory = @import("../../memory/memory.zig").Memory; +const Field = @import("../../../math/fields/starknet.zig").Field; const range_check_instance_def = @import("../../types/range_check_instance_def.zig"); +const CELLS_PER_RANGE_CHECK = range_check_instance_def.CELLS_PER_RANGE_CHECK; +const Relocatable = relocatable.Relocatable; +const MaybeRelocatable = relocatable.MaybeRelocatable; +const MemoryError = Error.MemoryError; +const RunnerError = Error.RunnerError; + +const N_PARTS: u64 = 8; +const INNER_RC_BOUND_SHIFT: u64 = 16; +const INNER_RC_BOUND_MASK: u64 = @as(u64, @intCast(u16.MAX)); + /// Range check built-in runner pub const RangeCheckBuiltinRunner = struct { const Self = @This(); @@ -43,26 +63,243 @@ pub const RangeCheckBuiltinRunner = struct { n_parts: u32, included: bool, ) Self { + const bound: Felt252 = Felt252.one().saturating_shl(16 * n_parts); + const _bound: ?Felt252 = if (n_parts != 0 and bound.isZero()) null else bound; + return .{ .ratio = ratio, .base = 0, .stop_ptr = null, - .cell_per_instance = range_check_instance_def.CELLS_PER_RANGE_CHECK, - .n_input_cells = range_check_instance_def.CELLS_PER_RANGE_CHECK, - // TODO: implement shl logic: https://github.com/lambdaclass/cairo-vm/blob/e6171d66a64146acc16d5512766ae91ae044f297/vm/src/vm/runners/builtin_runner/range_check.rs#L48-L53 - ._bound = null, + .cells_per_instance = CELLS_PER_RANGE_CHECK, + .n_input_cells = CELLS_PER_RANGE_CHECK, + ._bound = _bound, .included = included, .n_parts = n_parts, .instances_per_component = 1, }; } - /// Get the base value of this range check runner. + /// Get the base value of this Range Check runner. /// /// # Returns /// /// The base value as a `usize`. - pub fn get_base(self: *const Self) usize { + pub fn getBase(self: *const Self) usize { return self.base; } + + /// Get the ratio value of this Range Check runner. + /// + /// # Returns + /// + /// The ratio value as an `u32`. + pub fn getRatio(self: *const Self) ?u32 { + return self.ratio; + } + + /// Initializes memory segments and sets the base value for the Range Check runner. + /// + /// This function adds a memory segment using the provided `segments` manager and + /// sets the `base` value to the index of the new segment. + /// + /// # Parameters + /// - `segments`: A pointer to the `MemorySegmentManager` for segment management. + /// + /// # Modifies + /// - `self`: Updates the `base` value to the new segment's index. + pub fn initializeSegments(self: *Self, segments: *MemorySegmentManager) void { + self.base = @intCast(segments.addSegment().segment_index); + } + + /// Initializes and returns an `ArrayList` of `MaybeRelocatable` values. + /// + /// If the range check runner is included, it appends a `Relocatable` element to the `ArrayList` + /// with the base value. Otherwise, it returns an empty `ArrayList`. + /// + /// # Parameters + /// - `allocator`: An allocator for initializing the `ArrayList`. + /// + /// # Returns + /// An `ArrayList` of `MaybeRelocatable` values. + pub fn initialStack(self: *Self, allocator: Allocator) !ArrayList(MaybeRelocatable) { + var result = ArrayList(MaybeRelocatable).init(allocator); + if (self.included) { + try result.append(.{ + .relocatable = Relocatable.new( + @intCast(self.base), + 0, + ), + }); + return result; + } + return result; + } + + /// Get the number of used cells associated with this Range Check runner. + /// + /// # Parameters + /// + /// - `segments`: A pointer to a `MemorySegmentManager` for segment size information. + /// + /// # Returns + /// + /// The number of used cells as a `u32`, or `MemoryError.MissingSegmentUsedSizes` if + /// the size is not available. + pub fn getUsedCells(self: *const Self, segments: *MemorySegmentManager) !u32 { + return segments.get_segment_used_size( + @intCast(self.base), + ) orelse MemoryError.MissingSegmentUsedSizes; + } + + /// Calculates the number of used instances for the Range Check runner. + /// + /// This function computes the number of used instances based on the available + /// used cells and the number of cells per instance. It performs a ceiling division + /// to ensure that any remaining cells are counted as an additional instance. + /// + /// # Parameters + /// - `segments`: A pointer to the `MemorySegmentManager` for segment information. + /// + /// # Returns + /// The number of used instances as a `usize`. + pub fn getUsedInstances(self: *Self, segments: *MemorySegmentManager) !usize { + return std.math.divCeil( + usize, + try self.getUsedCells(segments), + @intCast(self.cells_per_instance), + ); + } + + /// Retrieves memory segment addresses as a tuple. + /// + /// Returns a tuple containing the `base` and `stop_ptr` addresses associated + /// with the Range Check runner's memory segments. The `stop_ptr` may be `null`. + /// + /// # Returns + /// A tuple of `usize` and `?usize` addresses. + pub fn getMemorySegmentAddresses(self: *Self) std.meta.Tuple(&.{ + usize, + ?usize, + }) { + return .{ + self.base, + self.stop_ptr, + }; + } + + /// Calculate the final stack. + /// + /// This function calculates the final stack pointer for the Range Check runner, based on the provided `segments`, `pointer`, and `self` settings. If the runner is included, + /// it verifies the stop pointer for consistency and sets it. Otherwise, it sets the stop pointer to zero. + /// + /// # Parameters + /// + /// - `segments`: A pointer to the `MemorySegmentManager` for segment management. + /// - `pointer`: A `Relocatable` pointer to the current stack pointer. + /// + /// # Returns + /// + /// A `Relocatable` pointer to the final stack pointer, or an error code if the + /// verification fails. + pub fn finalStack( + self: *Self, + segments: *MemorySegmentManager, + pointer: Relocatable, + ) !Relocatable { + if (self.included) { + const stop_pointer_addr = pointer.subUint( + @intCast(1), + ) catch return RunnerError.NoStopPointer; + const stop_pointer = try (segments.memory.get( + stop_pointer_addr, + ) catch return RunnerError.NoStopPointer).tryIntoRelocatable(); + if (@as( + isize, + @intCast(self.base), + ) != stop_pointer.segment_index) { + return RunnerError.InvalidStopPointerIndex; + } + const stop_ptr = stop_pointer.offset; + + if (stop_ptr != try self.getUsedInstances(segments) * @as( + usize, + @intCast(self.cells_per_instance), + )) { + return RunnerError.InvalidStopPointer; + } + self.stop_ptr = stop_ptr; + return stop_pointer_addr; + } + + self.stop_ptr = 0; + return pointer; + } + + /// Creates Validation Rules ArrayList + /// + /// # Parameters + /// + /// - `memory`: A `Memory` pointer of validation rules segment index. + /// - `address`: A `Relocatable` pointer to the validation rule. + /// + /// # Returns + /// + /// An `ArrayList(Relocatable)` containing the rules address + /// verification fails. + pub fn rangeCheckValidationRule(memory: *Memory, address: Relocatable, allocator: Allocator) !std.ArrayList(Relocatable) { + var result = ArrayList(Relocatable).init(allocator); + const num = (memory.get(address) catch { + return null; + }).tryIntoFelt() catch { + return RunnerError.BuiltinExpectedInteger; + }; + + if (num.Mask <= N_PARTS * INNER_RC_BOUND_SHIFT) { + return try result.append(address); + } else { + return try result.append(Error.MemoryOutOfBounds); + } + } + + /// Creates Validation Rule in Memory + /// + /// # Parameters + /// + /// - `memory`: A `Memory` pointer of validation rules segment index. + /// + /// # Modifies + /// + /// - `memory`: Adds validation rule to `memory`. + pub fn addValidationRule(self: *const Self, memory: *Memory) void { + memory.addValidationRule(self.base.segment_index, rangeCheckValidationRule); + } }; + +test "initialize segments for range check" { + + // given + var builtin = RangeCheckBuiltinRunner.new(8, 8, true); + var allocator = std.testing.allocator; + var mem = try MemorySegmentManager.init(allocator); + defer mem.deinit(); + + // assert + try std.testing.expectEqual( + builtin.base, + 0, + ); +} + +test "used instances" { + + // given + var builtin = RangeCheckBuiltinRunner.new(10, 12, true); + + var memory_segment_manager = try MemorySegmentManager.init(std.testing.allocator); + defer memory_segment_manager.deinit(); + try memory_segment_manager.segment_used_sizes.put(0, 1); + try std.testing.expectEqual( + @as(usize, @intCast(1)), + try builtin.getUsedInstances(memory_segment_manager), + ); +} diff --git a/src/vm/builtins/builtin_runner/segment_arena.zig b/src/vm/builtins/builtin_runner/segment_arena.zig index 1f6b1bb8..c7c48db8 100644 --- a/src/vm/builtins/builtin_runner/segment_arena.zig +++ b/src/vm/builtins/builtin_runner/segment_arena.zig @@ -49,7 +49,7 @@ pub const SegmentArenaBuiltinRunner = struct { /// # Returns /// /// The base segment index as a `usize`. - pub fn get_base(self: *const Self) usize { + pub fn getBase(self: *const Self) usize { return @as( usize, @intCast(self.base.segment_index), diff --git a/src/vm/builtins/builtin_runner/signature.zig b/src/vm/builtins/builtin_runner/signature.zig index 9141041b..29b30456 100644 --- a/src/vm/builtins/builtin_runner/signature.zig +++ b/src/vm/builtins/builtin_runner/signature.zig @@ -63,7 +63,7 @@ pub const SignatureBuiltinRunner = struct { /// # Returns /// /// The base value as a `usize`. - pub fn get_base(self: *const Self) usize { + pub fn getBase(self: *const Self) usize { return self.base; } }; diff --git a/src/vm/error.zig b/src/vm/error.zig index 53d8157b..55fa01c5 100644 --- a/src/vm/error.zig +++ b/src/vm/error.zig @@ -11,3 +11,14 @@ pub const CairoVMError = error{ TypeMismatchNotRelocatable, ValueTooLarge, }; + +pub const MemoryError = error{ + MissingSegmentUsedSizes, +}; + +pub const RunnerError = error{ + NoStopPointer, + InvalidStopPointerIndex, + InvalidStopPointer, + BuiltinExpectedInteger, +}; diff --git a/src/vm/memory/memory.zig b/src/vm/memory/memory.zig index 6161a426..166979d3 100644 --- a/src/vm/memory/memory.zig +++ b/src/vm/memory/memory.zig @@ -11,6 +11,13 @@ const Relocatable = relocatable.Relocatable; const CairoVMError = @import("../error.zig").CairoVMError; const starknet_felt = @import("../../math/fields/starknet.zig"); +// Test imports. +const MemorySegmentManager = @import("./segments.zig").MemorySegmentManager; +const RangeCheckBuiltinRunner = @import("../builtins/builtin_runner/range_check.zig").RangeCheckBuiltinRunner; + +// Function that validates a memory address and returns a list of validated adresses +pub const validation_rule = *const fn (*Memory, Relocatable) std.ArrayList(Relocatable); + // Representation of the VM memory. pub const Memory = struct { const Self = @This(); @@ -37,6 +44,12 @@ pub const Memory = struct { std.hash_map.AutoContext(Relocatable), std.hash_map.default_max_load_percentage, ), + validation_rules: std.HashMap( + u32, + validation_rule, + std.hash_map.AutoContext(u32), + std.hash_map.default_max_load_percentage, + ), // ************************************************************ // * MEMORY ALLOCATION AND DEALLOCATION * @@ -61,6 +74,10 @@ pub const Memory = struct { Relocatable, bool, ).init(allocator), + .validation_rules = std.AutoHashMap( + u32, + validation_rule, + ).init(allocator), }; return memory; } @@ -118,6 +135,14 @@ pub const Memory = struct { ) error{MemoryOutOfBounds}!MaybeRelocatable { return self.data.get(address) orelse CairoVMError.MemoryOutOfBounds; } + + // Adds a validation rule for a given segment. + // # Arguments + // - `segment_index` - The index of the segment. + // - `rule` - The validation rule. + pub fn addValidationRule(self: *Self, segment_index: usize, rule: validation_rule) !void { + self.validation_rules.put(segment_index, rule); + } }; test "memory get without value raises error" { @@ -180,3 +205,45 @@ test "memory set and get" { // Assert that the value is the expected value. try expect(maybe_value_1.eq(value_1)); } + +test "validate existing memory for range check within bound" { + // ************************************************************ + // * SETUP TEST CONTEXT * + // ************************************************************ + // Initialize an allocator. + var allocator = std.testing.allocator; + + // Initialize a memory instance. + var memory = try Memory.init(allocator); + defer memory.deinit(); + + var segments = try MemorySegmentManager.init(allocator); + defer segments.deinit(); + + var builtin = RangeCheckBuiltinRunner.new(8, 8, true); + builtin.initializeSegments(segments); + + // ************************************************************ + // * TEST BODY * + // ************************************************************ + const address_1 = Relocatable.new( + 0, + 0, + ); + const value_1 = relocatable.fromFelt(starknet_felt.Felt252.one()); + + // Set a value into the memory. + _ = try memory.set( + address_1, + value_1, + ); + + // Get the value from the memory. + const maybe_value_1 = try memory.get(address_1); + + // ************************************************************ + // * TEST CHECKS * + // ************************************************************ + // Assert that the value is the expected value. + try expect(maybe_value_1.eq(value_1)); +} diff --git a/src/vm/memory/segments.zig b/src/vm/memory/segments.zig index 0b2ab780..3425de0e 100644 --- a/src/vm/memory/segments.zig +++ b/src/vm/memory/segments.zig @@ -110,6 +110,16 @@ pub const MemorySegmentManager = struct { return relocatable_address; } + /// Retrieves the size of a memory segment by its index if available, else returns null. + /// + /// # Parameters + /// - `index` (u32): The index of the memory segment. + /// + /// # Returns + /// A `u32` representing the size of the segment or null if not computed. + pub fn get_segment_used_size(self: *Self, index: u32) ?u32 { + return self.segment_used_sizes.get(index) orelse null; + } }; // ************************************************************ diff --git a/src/vm/types/range_check_instance_def.zig b/src/vm/types/range_check_instance_def.zig index 1de7fe68..8593ac4c 100644 --- a/src/vm/types/range_check_instance_def.zig +++ b/src/vm/types/range_check_instance_def.zig @@ -3,10 +3,24 @@ pub const CELLS_PER_RANGE_CHECK: u32 = 1; /// Represents a Range Check Instance Definition. pub const RangeCheckInstanceDef = struct { + const Self = @This(); + /// Ratio ratio: ?u32, /// Number of 16-bit range checks that will be used for each instance of the builtin. /// /// For example, n_parts=8 defines the range [0, 2^128). n_parts: u32, + + /// Number of units per builtin + pub fn rangeCheckUnitsPerBuiltin(self: *Self) u32 { + return self.n_parts; + } + + pub fn default() Self { + return .{ + .ratio = 8, + .n_parts = 8, + }; + } };