diff --git a/src/beacon_tests.zig b/src/beacon_tests.zig index 0f4803d..3550564 100644 --- a/src/beacon_tests.zig +++ b/src/beacon_tests.zig @@ -129,7 +129,7 @@ test "List[Validator] serialization and hash tree root" { const MAX_VALIDATORS = 100; const ValidatorList = utils.List(Validator, MAX_VALIDATORS); - var validator_list = try ValidatorList.init(std.testing.allocator); + var validator_list = ValidatorList.init(); defer validator_list.deinit(); // Add test validators @@ -168,7 +168,7 @@ test "List[Validator] serialization and hash tree root" { try expect(std.mem.eql(u8, list.items, &expected_validator_list_bytes)); // Test deserialization - var deserialized_list = try ValidatorList.init(std.testing.allocator); + var deserialized_list = ValidatorList.init(); defer deserialized_list.deinit(); try deserialize(ValidatorList, list.items, &deserialized_list, std.testing.allocator); @@ -237,7 +237,7 @@ test "BeamBlockBody with validator array - full cycle" { }; // Create validator array - var validators = try ValidatorArray.init(std.testing.allocator); + var validators = ValidatorArray.init(); defer validators.deinit(); try validators.append(validator1); try validators.append(validator2); @@ -258,7 +258,7 @@ test "BeamBlockBody with validator array - full cycle" { // Test deserialization var deserialized_body: BeamBlockBody = undefined; - deserialized_body.validators = try ValidatorArray.init(std.testing.allocator); + deserialized_body.validators = ValidatorArray.init(); defer deserialized_body.validators.deinit(); try deserialize(BeamBlockBody, serialized_data.items, &deserialized_body, std.testing.allocator); @@ -333,7 +333,7 @@ test "Zeam-style List/Bitlist usage with tree root stability" { justified_slots: JustifiedSlots, }; - var votes = try Mini3SFVotes.init(std.testing.allocator); + var votes = Mini3SFVotes.init(); defer votes.deinit(); try votes.append(Mini3SFVote{ .validator_id = 1, @@ -343,7 +343,7 @@ test "Zeam-style List/Bitlist usage with tree root stability" { .source = Mini3SFCheckpoint{ .root = [_]u8{3} ** 32, .slot = 8 }, }); - var hashes = try HistoricalBlockHashes.init(std.testing.allocator); + var hashes = HistoricalBlockHashes.init(); defer hashes.deinit(); try hashes.append([_]u8{0xaa} ** 32); try hashes.append([_]u8{0xbb} ** 32); @@ -416,7 +416,7 @@ test "BeamState with historical roots - comprehensive test" { finalized_checkpoint_root: Root, }; - var historical_roots = try utils.List(Root, MAX_HISTORICAL_ROOTS).init(std.testing.allocator); + var historical_roots = utils.List(Root, MAX_HISTORICAL_ROOTS).init(); defer historical_roots.deinit(); try historical_roots.append([_]u8{0x01} ** 32); @@ -461,7 +461,7 @@ test "BeamState with historical roots - comprehensive test" { // Test deserialization var deserialized_state: BeamState = undefined; - deserialized_state.historical_roots = try utils.List(Root, MAX_HISTORICAL_ROOTS).init(std.testing.allocator); + deserialized_state.historical_roots = utils.List(Root, MAX_HISTORICAL_ROOTS).init(); defer deserialized_state.historical_roots.deinit(); try deserialize(BeamState, serialized_data.items, &deserialized_state, std.testing.allocator); @@ -507,7 +507,7 @@ test "BeamState with empty historical roots" { }; // Create BeamState with empty historical roots - var empty_historical_roots = try utils.List(Root, MAX_HISTORICAL_ROOTS).init(std.testing.allocator); + var empty_historical_roots = utils.List(Root, MAX_HISTORICAL_ROOTS).init(); defer empty_historical_roots.deinit(); const beam_state = SimpleBeamState{ @@ -536,7 +536,7 @@ test "BeamState with empty historical roots" { // Test deserialization var deserialized_state: SimpleBeamState = undefined; - deserialized_state.historical_roots = try utils.List(Root, MAX_HISTORICAL_ROOTS).init(std.testing.allocator); + deserialized_state.historical_roots = utils.List(Root, MAX_HISTORICAL_ROOTS).init(); defer deserialized_state.historical_roots.deinit(); try deserialize(SimpleBeamState, serialized_data.items, &deserialized_state, std.testing.allocator); @@ -565,7 +565,7 @@ test "BeamState with maximum historical roots" { }; // Create BeamState with maximum historical roots - var max_historical_roots = try utils.List(Root, MAX_HISTORICAL_ROOTS).init(std.testing.allocator); + var max_historical_roots = utils.List(Root, MAX_HISTORICAL_ROOTS).init(); defer max_historical_roots.deinit(); // Fill to maximum capacity @@ -603,7 +603,7 @@ test "BeamState with maximum historical roots" { // Test deserialization var deserialized_state: MaxBeamState = undefined; - deserialized_state.historical_roots = try utils.List(Root, MAX_HISTORICAL_ROOTS).init(std.testing.allocator); + deserialized_state.historical_roots = utils.List(Root, MAX_HISTORICAL_ROOTS).init(); defer deserialized_state.historical_roots.deinit(); try deserialize(MaxBeamState, serialized_data.items, &deserialized_state, std.testing.allocator); @@ -636,7 +636,7 @@ test "BeamState historical roots access and comparison" { metadata: u64, }; - var historical_roots = try utils.List(Root, MAX_HISTORICAL_ROOTS).init(std.testing.allocator); + var historical_roots = utils.List(Root, MAX_HISTORICAL_ROOTS).init(); defer historical_roots.deinit(); // Add roots with specific patterns @@ -682,7 +682,7 @@ test "BeamState historical roots access and comparison" { // Test deserialization var deserialized_state: AccessBeamState = undefined; - deserialized_state.historical_roots = try utils.List(Root, MAX_HISTORICAL_ROOTS).init(std.testing.allocator); + deserialized_state.historical_roots = utils.List(Root, MAX_HISTORICAL_ROOTS).init(); defer deserialized_state.historical_roots.deinit(); try deserialize(AccessBeamState, serialized_data.items, &deserialized_state, std.testing.allocator); @@ -724,7 +724,7 @@ test "SimpleBeamState with empty historical roots" { }; // Create BeamState with empty historical roots - var empty_historical_roots = try utils.List(Root, MAX_HISTORICAL_ROOTS).init(std.testing.allocator); + var empty_historical_roots = utils.List(Root, MAX_HISTORICAL_ROOTS).init(); defer empty_historical_roots.deinit(); const beam_state = SimpleBeamState{ @@ -752,7 +752,7 @@ test "SimpleBeamState with empty historical roots" { // Test deserialization var deserialized_state: SimpleBeamState = undefined; - deserialized_state.historical_roots = try utils.List(Root, MAX_HISTORICAL_ROOTS).init(std.testing.allocator); + deserialized_state.historical_roots = utils.List(Root, MAX_HISTORICAL_ROOTS).init(); defer deserialized_state.historical_roots.deinit(); try deserialize(SimpleBeamState, serialized_data.items, &deserialized_state, std.testing.allocator); diff --git a/src/tests.zig b/src/tests.zig index 6055aa3..ca75e2d 100644 --- a/src/tests.zig +++ b/src/tests.zig @@ -670,7 +670,7 @@ test "calculate the root hash of an union" { test "(de)serialize List[N] of fixed-length objects" { const MAX_VALIDATORS_PER_COMMITTEE: usize = 2048; const ListValidatorIndex = utils.List(u64, MAX_VALIDATORS_PER_COMMITTEE); - var attesting_indices = try ListValidatorIndex.init(std.testing.allocator); + var attesting_indices = ListValidatorIndex.init(); defer attesting_indices.deinit(); for (0..10) |i| { try attesting_indices.append(i * 100); @@ -678,7 +678,7 @@ test "(de)serialize List[N] of fixed-length objects" { var list = ArrayList(u8).init(std.testing.allocator); defer list.deinit(); try serialize(ListValidatorIndex, attesting_indices, &list); - var attesting_indices_deser = try ListValidatorIndex.init(std.testing.allocator); + var attesting_indices_deser = ListValidatorIndex.init(); defer attesting_indices_deser.deinit(); try deserialize(ListValidatorIndex, list.items, &attesting_indices_deser, std.testing.allocator); try expect(attesting_indices.eql(&attesting_indices_deser)); @@ -686,7 +686,7 @@ test "(de)serialize List[N] of fixed-length objects" { test "(de)serialize List[N] of variable-length objects" { const ListOfStrings = utils.List([]const u8, 16); - var string_list = try ListOfStrings.init(std.testing.allocator); + var string_list = ListOfStrings.init(); defer string_list.deinit(); for (0..10) |i| { try string_list.append(try std.fmt.allocPrint(std.testing.allocator, "count={}", .{i})); @@ -697,7 +697,7 @@ test "(de)serialize List[N] of variable-length objects" { var list = ArrayList(u8).init(std.testing.allocator); defer list.deinit(); try serialize(ListOfStrings, string_list, &list); - var string_list_deser = try ListOfStrings.init(std.testing.allocator); + var string_list_deser = ListOfStrings.init(); defer string_list_deser.deinit(); try deserialize(ListOfStrings, list.items, &string_list_deser, std.testing.allocator); try expect(string_list.len() == string_list_deser.len()); @@ -711,7 +711,7 @@ test "List[N].fromSlice of structs" { var start: usize = 0; var end: usize = pastries.len; _ = .{ &start, &end }; - var pastry_list = try PastryList.fromSlice(std.testing.allocator, pastries[start..end]); + var pastry_list = try PastryList.fromSlice(pastries[start..end]); defer pastry_list.deinit(); for (pastries, 0..) |pastry, i| { try expect(std.mem.eql(u8, (try pastry_list.get(i)).name, pastry.name)); @@ -960,9 +960,9 @@ test "slice hashtree root simple type" { test "List tree root calculation" { const ListU64 = utils.List(u64, 1024); - var empty_list = try ListU64.init(std.testing.allocator); + var empty_list = ListU64.init(); defer empty_list.deinit(); - var list_with_items = try ListU64.init(std.testing.allocator); + var list_with_items = ListU64.init(); defer list_with_items.deinit(); try list_with_items.append(42); try list_with_items.append(123); @@ -978,7 +978,7 @@ test "List tree root calculation" { try expect(!std.mem.eql(u8, &empty_hash, &filled_hash)); - var same_content_list = try ListU64.init(std.testing.allocator); + var same_content_list = ListU64.init(); defer same_content_list.deinit(); try same_content_list.append(42); try same_content_list.append(123); @@ -1026,7 +1026,7 @@ test "Bitlist tree root calculation" { test "List of composite types tree root" { const ListOfPastry = utils.List(Pastry, 100); - var pastry_list = try ListOfPastry.init(std.testing.allocator); + var pastry_list = ListOfPastry.init(); defer pastry_list.deinit(); try pastry_list.append(Pastry{ .name = "croissant", .weight = 20 }); try pastry_list.append(Pastry{ .name = "muffin", .weight = 30 }); @@ -1034,7 +1034,7 @@ test "List of composite types tree root" { var hash1: [32]u8 = undefined; try hashTreeRoot(Sha256, ListOfPastry, pastry_list, &hash1, std.testing.allocator); - var pastry_list2 = try ListOfPastry.init(std.testing.allocator); + var pastry_list2 = ListOfPastry.init(); defer pastry_list2.deinit(); try pastry_list2.append(Pastry{ .name = "croissant", .weight = 20 }); try pastry_list2.append(Pastry{ .name = "muffin", .weight = 30 }); @@ -1054,7 +1054,7 @@ test "List of composite types tree root" { test "serializedSize correctly calculates List/Bitlist sizes" { // Test List size calculation const ListType = utils.List(u64, 100); - var list = try ListType.init(std.testing.allocator); + var list = ListType.init(); defer list.deinit(); try list.append(123); try list.append(456); @@ -1279,7 +1279,7 @@ test "serialize max/min integer values" { test "Empty List hash tree root" { const ListU32 = utils.List(u32, 100); - var empty_list = try ListU32.init(std.testing.allocator); + var empty_list = ListU32.init(); defer empty_list.deinit(); var hash: [32]u8 = undefined; @@ -1331,7 +1331,7 @@ test "Empty BitList (>256) hash tree root" { test "List at maximum capacity" { const ListU8 = utils.List(u8, 4); - var full_list = try ListU8.init(std.testing.allocator); + var full_list = ListU8.init(); defer full_list.deinit(); // Fill to capacity @@ -1515,7 +1515,7 @@ test "uint256 hash tree root" { test "Single element List" { const ListU64 = utils.List(u64, 10); - var single = try ListU64.init(std.testing.allocator); + var single = ListU64.init(); defer single.deinit(); try single.append(42); @@ -1667,7 +1667,7 @@ test "decodeDynamicLength - comprehensive validation" { } test "List validation - size limits enforced" { - var list = try utils.List(u32, 3).init(std.testing.allocator); + var list = utils.List(u32, 3).init(); defer list.deinit(); // Test oversized fixed-size list @@ -1734,7 +1734,7 @@ test "Bitlist init consistency with List" { const TestList = utils.List(u32, 10); const TestBitlist = utils.Bitlist(10); - var list = try TestList.init(std.testing.allocator); + var list = TestList.init(); defer list.deinit(); var bitlist = try TestBitlist.init(std.testing.allocator); defer bitlist.deinit(); @@ -1744,7 +1744,7 @@ test "Bitlist init consistency with List" { try expect(bitlist.len() == 0); // Both should have reserved capacity but no actual elements - try expect(list.inner.items.len == 0); + try expect(list.inner.len == 0); try expect(bitlist.inner.items.len == 0); } @@ -1897,21 +1897,21 @@ test "SSZ external reference vectors" { test "List fromSlice overflow rejection" { const TestList = utils.List(u32, 2); const oversized_slice = [_]u32{ 1, 2, 3, 4 }; - const result = TestList.fromSlice(std.testing.allocator, &oversized_slice); + const result = TestList.fromSlice(&oversized_slice); try expectError(error.Overflow, result); } test "List fromSlice at exact capacity" { const TestList = utils.List(u32, 3); const exact_slice = [_]u32{ 10, 20, 30 }; - var list = try TestList.fromSlice(std.testing.allocator, &exact_slice); + var list = try TestList.fromSlice(&exact_slice); defer list.deinit(); try expect(list.len() == 3); } test "Zero capacity List" { const TestList = utils.List(u32, 0); - var list = try TestList.init(std.testing.allocator); + var list = TestList.init(); defer list.deinit(); try expect(list.len() == 0); try expectError(error.Overflow, list.append(1)); @@ -1927,7 +1927,7 @@ test "Zero capacity Bitlist" { test "Large capacity List overflow" { const TestList = utils.List(u8, 1000); - var list = try TestList.init(std.testing.allocator); + var list = TestList.init(); defer list.deinit(); var i: usize = 0; @@ -2046,7 +2046,7 @@ test "nested dynamic list uses relative offsets" { dynamic_list: InnerList, // variable }; - var inner_list = try InnerList.init(std.testing.allocator); + var inner_list = InnerList.init(); defer inner_list.deinit(); const item1: []const u8 = "hello"; @@ -2119,16 +2119,16 @@ test "deeply nested dynamic structures use relative offsets" { nested_lists: OuterList, }; - var inner1 = try InnerList.init(std.testing.allocator); + var inner1 = InnerList.init(); defer inner1.deinit(); try inner1.append("a"); try inner1.append("bb"); - var inner2 = try InnerList.init(std.testing.allocator); + var inner2 = InnerList.init(); defer inner2.deinit(); try inner2.append("ccc"); - var outer_list = try OuterList.init(std.testing.allocator); + var outer_list = OuterList.init(); defer outer_list.deinit(); try outer_list.append(inner1); try outer_list.append(inner2); diff --git a/src/utils.zig b/src/utils.zig index 2b6df82..6f17159 100644 --- a/src/utils.zig +++ b/src/utils.zig @@ -6,6 +6,7 @@ const serialize = lib.serialize; const deserialize = lib.deserialize; const isFixedSizeObject = lib.isFixedSizeObject; const ArrayList = std.ArrayList; +const BoundedArray = std.BoundedArray; const Allocator = std.mem.Allocator; const hashes_of_zero = @import("./zeros.zig").hashes_of_zero; @@ -24,7 +25,7 @@ pub fn List(T: type, comptime N: usize) type { return struct { const Self = @This(); const Item = T; - const Inner = std.ArrayList(T); + const Inner = BoundedArray(T, N); const OFFSET_SIZE = 4; @@ -40,18 +41,7 @@ pub fn List(T: type, comptime N: usize) type { pub fn sszDecode(serialized: []const u8, out: *Self, allocator: ?std.mem.Allocator) !void { // BitList[N] or regular List[N]? - const alloc = allocator orelse return error.AllocatorRequired; - out.* = try init(alloc); - - // FastSSZ-style capacity optimization: pre-allocate based on input size - // TODO: replace this with the definite value, taken from the list - if (serialized.len > 0) { - const estimated_capacity = if (try lib.isFixedSizeObject(Self.Item)) - serialized.len / (try lib.serializedFixedSize(Self.Item)) - else - serialized.len / 8; // Conservative estimate for dynamic types - try out.inner.ensureTotalCapacity(estimated_capacity); - } + out.* = init(); if (Self.Item == bool) { @panic("Use the optimized utils.Bitlist(N) instead of utils.List(bool, N)"); @@ -84,14 +74,14 @@ pub fn List(T: type, comptime N: usize) type { if (i > 0 and start < indices[i - 1]) { return error.OffsetOrdering; } - const item = try out.inner.addOne(); + const item = out.inner.addOne() catch return error.Overflow; try deserialize(Self.Item, serialized[start..end], item, allocator); } } } - pub fn init(allocator: Allocator) !Self { - return .{ .inner = Inner.init(allocator) }; + pub fn init() Self { + return .{ .inner = Inner{} }; } pub fn eql(self: *const Self, other: *const Self) bool { @@ -111,41 +101,38 @@ pub fn List(T: type, comptime N: usize) type { } pub fn deinit(self: *Self) void { - self.inner.deinit(); + _ = self; + // BoundedArray is stack-allocated, no cleanup needed } - pub fn append(self: *Self, item: Self.Item) error{ Overflow, OutOfMemory }!void { - if (self.inner.items.len >= N) return error.Overflow; - return self.inner.append(item); + pub fn append(self: *Self, item: Self.Item) error{Overflow}!void { + self.inner.append(item) catch return error.Overflow; } pub fn slice(self: *Self) []T { - return self.inner.items; + return self.inner.slice(); } pub fn constSlice(self: *const Self) []const T { - return self.inner.items; + return self.inner.constSlice(); } - pub fn fromSlice(allocator: Allocator, m: []const T) !Self { - if (m.len > N) return error.Overflow; - var inner = Inner.init(allocator); - try inner.appendSlice(m); - return .{ .inner = inner }; + pub fn fromSlice(m: []const T) error{Overflow}!Self { + return .{ .inner = Inner.fromSlice(m) catch return error.Overflow }; } pub fn get(self: Self, i: usize) error{IndexOutOfBounds}!T { - if (i >= self.inner.items.len) return error.IndexOutOfBounds; - return self.inner.items[i]; + if (i >= self.inner.len) return error.IndexOutOfBounds; + return self.inner.buffer[i]; } pub fn set(self: *Self, i: usize, item: T) error{IndexOutOfBounds}!void { - if (i >= self.inner.items.len) return error.IndexOutOfBounds; - self.inner.items[i] = item; + if (i >= self.inner.len) return error.IndexOutOfBounds; + self.inner.buffer[i] = item; } pub fn len(self: *const Self) usize { - return self.inner.items.len; + return self.inner.len; } pub fn serializedSize(self: *const Self) !usize {