diff --git a/README.md b/README.md index fccfdce..0c70be0 100644 --- a/README.md +++ b/README.md @@ -230,7 +230,7 @@ defer wallet.deinit(); // Create transaction var tx = zigeth.types.Transaction.newEip1559(allocator); tx.to = try zigeth.primitives.Address.fromHex("0x..."); -tx.value = zigeth.primitives.U256.fromInt(100_000_000_000_000_000); // 0.1 ETH +tx.value = 100_000_000_000_000_000; // 0.1 ETH tx.nonce = try provider.getTransactionCount(wallet.address); tx.gas_limit = 21000; tx.max_fee_per_gas = 30_000_000_000; // 30 gwei diff --git a/build.zig b/build.zig index 04fc27b..4cb3417 100644 --- a/build.zig +++ b/build.zig @@ -29,11 +29,9 @@ pub fn build(b: *std.Build) void { const secp256k1_artifact = secp256k1_dep.artifact("secp256k1"); // Build static library - const lib = b.addStaticLibrary(.{ + const lib = b.addLibrary(.{ .name = "zigeth", - .root_source_file = b.path("src/root.zig"), - .target = target, - .optimize = optimize, + .root_module = zigeth_mod, }); // Link libc (required for some std library functions) @@ -45,9 +43,11 @@ pub fn build(b: *std.Build) void { // Build executable (CLI tool) const exe = b.addExecutable(.{ .name = "zigeth", - .root_source_file = b.path("src/main.zig"), - .target = target, - .optimize = optimize, + .root_module = b.createModule(.{ + .root_source_file = b.path("src/main.zig"), + .target = target, + .optimize = optimize, + }), }); exe.root_module.addImport("zigeth", zigeth_mod); @@ -68,11 +68,14 @@ pub fn build(b: *std.Build) void { // Unit tests for library const lib_unit_tests = b.addTest(.{ - .root_source_file = b.path("src/root.zig"), - .target = target, - .optimize = optimize, + .root_module = b.createModule(.{ + .root_source_file = b.path("src/root.zig"), + .target = target, + .optimize = optimize, + }), }); + lib_unit_tests.root_module.addImport("secp256k1", secp256k1_mod); lib_unit_tests.linkLibC(); lib_unit_tests.linkLibrary(secp256k1_artifact); @@ -80,9 +83,11 @@ pub fn build(b: *std.Build) void { // Unit tests for executable const exe_unit_tests = b.addTest(.{ - .root_source_file = b.path("src/main.zig"), - .target = target, - .optimize = optimize, + .root_module = b.createModule(.{ + .root_source_file = b.path("src/main.zig"), + .target = target, + .optimize = optimize, + }), }); exe_unit_tests.root_module.addImport("zigeth", zigeth_mod); @@ -146,9 +151,11 @@ pub fn build(b: *std.Build) void { const example_exe = b.addExecutable(.{ .name = example_name, - .root_source_file = b.path(example_path), - .target = target, - .optimize = optimize, + .root_module = b.createModule(.{ + .root_source_file = b.path(example_path), + .target = target, + .optimize = optimize, + }), }); example_exe.root_module.addImport("zigeth", zigeth_mod); @@ -198,12 +205,15 @@ pub fn build(b: *std.Build) void { lint_step.dependOn(&fmt_check.step); // 2. Build library with warnings - const lint_lib = b.addStaticLibrary(.{ + const lint_lib = b.addLibrary(.{ .name = "zigeth-lint", - .root_source_file = b.path("src/root.zig"), - .target = target, - .optimize = .Debug, + .root_module = b.createModule(.{ + .root_source_file = b.path("src/root.zig"), + .target = target, + .optimize = .Debug, + }), }); + lint_lib.root_module.addImport("secp256k1", secp256k1_mod); lint_lib.linkLibC(); const lint_lib_check = b.addInstallArtifact(lint_lib, .{ @@ -214,9 +224,11 @@ pub fn build(b: *std.Build) void { // 3. Build executable with warnings const lint_exe = b.addExecutable(.{ .name = "zigeth-lint", - .root_source_file = b.path("src/main.zig"), - .target = target, - .optimize = .Debug, + .root_module = b.createModule(.{ + .root_source_file = b.path("src/main.zig"), + .target = target, + .optimize = .Debug, + }), }); lint_exe.root_module.addImport("zigeth", zigeth_mod); lint_exe.linkLibC(); diff --git a/build.zig.zon b/build.zig.zon index be14dff..12024d6 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -15,8 +15,8 @@ // To add a new dependency, use: zig fetch --save .dependencies = .{ .zig_eth_secp256k1 = .{ - .url = "https://github.com/jsign/zig-eth-secp256k1/archive/refs/heads/main.tar.gz", - .hash = "zig_eth_secp256k1-0.1.0-_U97sGzoDACkTnLX6ggeDfs_MhZHyxapYfhPPAtoQJJG", + .url = "https://github.com/DaviRain-Su/zig-eth-secp256k1/archive/refs/heads/main.tar.gz", + .hash = "zig_eth_secp256k1-0.1.0-_U97sLbpDAATA_3x8cFERYszQ8DPzDck3ZiffzwQOcnt", }, }, .paths = .{ diff --git a/examples/04_smart_contracts.zig b/examples/04_smart_contracts.zig index 72dc04e..d72d6f0 100644 --- a/examples/04_smart_contracts.zig +++ b/examples/04_smart_contracts.zig @@ -125,11 +125,11 @@ pub fn main() !void { const selectors = zigeth.sol.Selectors; std.debug.print("✅ Common ERC-20 selectors:\n", .{}); - std.debug.print(" balanceOf: 0x{x:0>8}\n", .{selectors.ERC20_BALANCE_OF}); - std.debug.print(" transfer: 0x{x:0>8}\n", .{selectors.ERC20_TRANSFER}); - std.debug.print(" approve: 0x{x:0>8}\n", .{selectors.ERC20_APPROVE}); - std.debug.print(" transferFrom: 0x{x:0>8}\n", .{selectors.ERC20_TRANSFER_FROM}); - std.debug.print(" allowance: 0x{x:0>8}\n\n", .{selectors.ERC20_ALLOWANCE}); + std.debug.print(" balanceOf: {s}\n", .{selectors.ERC20_BALANCE_OF}); + std.debug.print(" transfer: {s}\n", .{selectors.ERC20_TRANSFER}); + std.debug.print(" approve: {s}\n", .{selectors.ERC20_APPROVE}); + std.debug.print(" transferFrom: {s}\n", .{selectors.ERC20_TRANSFER_FROM}); + std.debug.print(" allowance: {s}\n\n", .{selectors.ERC20_ALLOWANCE}); } // Example 6: ABI encoding patterns diff --git a/examples/README.md b/examples/README.md index 8761b9e..fa9c8e2 100644 --- a/examples/README.md +++ b/examples/README.md @@ -322,7 +322,7 @@ var signer = try zigeth.middleware.SignerMiddleware.init(allocator, private_key, var tx = zigeth.types.Transaction.newEip1559(allocator); tx.from = from_address; tx.to = try zigeth.primitives.Address.fromHex(allocator, "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb"); -tx.value = zigeth.primitives.U256.fromInt(100_000_000_000_000_000); // 0.1 ETH +tx.value = 100_000_000_000_000_000; // 0.1 ETH tx.nonce = try provider.getTransactionCount(from_address); tx.gas_limit = 21000; diff --git a/src/abi/decode.zig b/src/abi/decode.zig index ce4c20d..a4ab6cd 100644 --- a/src/abi/decode.zig +++ b/src/abi/decode.zig @@ -114,15 +114,15 @@ pub fn decodeFunctionReturn( output_types: []const types.Parameter, ) ![]types.AbiValue { var decoder = Decoder.init(allocator, data); - var results = std.ArrayList(types.AbiValue).init(allocator); - defer results.deinit(); + var results = try std.ArrayList(types.AbiValue).initCapacity(allocator, 0); + defer results.deinit(allocator); for (output_types) |param| { const value = try decodeValue(&decoder, param.type); - try results.append(value); + try results.append(allocator, value); } - return try results.toOwnedSlice(); + return try results.toOwnedSlice(allocator); } /// Decode a single value diff --git a/src/abi/encode.zig b/src/abi/encode.zig index 4f7f078..b6f69d8 100644 --- a/src/abi/encode.zig +++ b/src/abi/encode.zig @@ -9,21 +9,21 @@ pub const Encoder = struct { allocator: std.mem.Allocator, buffer: std.ArrayList(u8), - pub fn init(allocator: std.mem.Allocator) Encoder { + pub fn init(allocator: std.mem.Allocator) !Encoder { return .{ .allocator = allocator, - .buffer = std.ArrayList(u8).init(allocator), + .buffer = try std.ArrayList(u8).initCapacity(allocator, 0), }; } pub fn deinit(self: *Encoder) void { - self.buffer.deinit(); + self.buffer.deinit(self.allocator); } /// Encode a uint256 value pub fn encodeUint256(self: *Encoder, value: u256) !void { const bytes = uint_utils.u256ToBytes(value); - try self.buffer.appendSlice(&bytes); + try self.buffer.appendSlice(self.allocator, &bytes); } /// Encode a uint value of any size (padded to 32 bytes) @@ -36,21 +36,21 @@ pub const Encoder = struct { pub fn encodeInt256(self: *Encoder, value: i256) !void { var bytes: [32]u8 = undefined; std.mem.writeInt(i256, &bytes, value, .big); - try self.buffer.appendSlice(&bytes); + try self.buffer.appendSlice(self.allocator, &bytes); } /// Encode an address (padded to 32 bytes, left-padded with zeros) pub fn encodeAddress(self: *Encoder, addr: Address) !void { var bytes: [32]u8 = [_]u8{0} ** 32; @memcpy(bytes[12..32], &addr.bytes); - try self.buffer.appendSlice(&bytes); + try self.buffer.appendSlice(self.allocator, &bytes); } /// Encode a boolean (0 or 1, padded to 32 bytes) pub fn encodeBool(self: *Encoder, value: bool) !void { var bytes: [32]u8 = [_]u8{0} ** 32; bytes[31] = if (value) 1 else 0; - try self.buffer.appendSlice(&bytes); + try self.buffer.appendSlice(self.allocator, &bytes); } /// Encode fixed-size bytes (right-padded with zeros to 32 bytes) @@ -59,7 +59,7 @@ pub const Encoder = struct { var bytes: [32]u8 = [_]u8{0} ** 32; @memcpy(bytes[0..data.len], data); - try self.buffer.appendSlice(&bytes); + try self.buffer.appendSlice(self.allocator, &bytes); } /// Encode dynamic bytes (length + data, padded) @@ -68,12 +68,12 @@ pub const Encoder = struct { try self.encodeUint(data.len); // Encode data with padding - try self.buffer.appendSlice(data); + try self.buffer.appendSlice(self.allocator, data); // Pad to multiple of 32 bytes const padding = (32 - (data.len % 32)) % 32; if (padding > 0) { - try self.buffer.appendNTimes(0, padding); + try self.buffer.appendNTimes(self.allocator, 0, padding); } } @@ -89,7 +89,7 @@ pub const Encoder = struct { /// Get owned encoded data pub fn toOwnedSlice(self: *Encoder) ![]u8 { - return try self.buffer.toOwnedSlice(); + return try self.buffer.toOwnedSlice(self.allocator); } /// Reset the encoder for reuse @@ -108,13 +108,13 @@ pub fn encodeFunctionCall( return error.ArgumentCountMismatch; } - var encoder = Encoder.init(allocator); + var encoder = try Encoder.init(allocator); defer encoder.deinit(); // Add function selector (first 4 bytes) const selector = try function.getSelector(allocator); defer allocator.free(selector); - try encoder.buffer.appendSlice(selector); + try encoder.buffer.appendSlice(allocator, selector); // Encode arguments for (args, function.inputs) |arg, param| { @@ -179,7 +179,7 @@ pub fn padLeft(allocator: std.mem.Allocator, data: []const u8) ![]u8 { test "encode uint256" { const allocator = std.testing.allocator; - var encoder = Encoder.init(allocator); + var encoder = try Encoder.init(allocator); defer encoder.deinit(); const value: u256 = 42; @@ -193,7 +193,7 @@ test "encode uint256" { test "encode address" { const allocator = std.testing.allocator; - var encoder = Encoder.init(allocator); + var encoder = try Encoder.init(allocator); defer encoder.deinit(); const addr = Address.fromBytes([_]u8{0x12} ** 20); @@ -212,7 +212,7 @@ test "encode address" { test "encode bool" { const allocator = std.testing.allocator; - var encoder = Encoder.init(allocator); + var encoder = try Encoder.init(allocator); defer encoder.deinit(); try encoder.encodeBool(true); @@ -230,7 +230,7 @@ test "encode bool" { test "encode string" { const allocator = std.testing.allocator; - var encoder = Encoder.init(allocator); + var encoder = try Encoder.init(allocator); defer encoder.deinit(); try encoder.encodeString("hello"); @@ -247,7 +247,7 @@ test "encode string" { test "encode fixed bytes" { const allocator = std.testing.allocator; - var encoder = Encoder.init(allocator); + var encoder = try Encoder.init(allocator); defer encoder.deinit(); const data = [_]u8{ 0xde, 0xad, 0xbe, 0xef }; diff --git a/src/abi/packed.zig b/src/abi/packed.zig index 644c56a..30fa1fe 100644 --- a/src/abi/packed.zig +++ b/src/abi/packed.zig @@ -9,21 +9,21 @@ pub const PackedEncoder = struct { allocator: std.mem.Allocator, buffer: std.ArrayList(u8), - pub fn init(allocator: std.mem.Allocator) PackedEncoder { + pub fn init(allocator: std.mem.Allocator) !PackedEncoder { return .{ .allocator = allocator, - .buffer = std.ArrayList(u8).init(allocator), + .buffer = try std.ArrayList(u8).initCapacity(allocator, 0), }; } pub fn deinit(self: *PackedEncoder) void { - self.buffer.deinit(); + self.buffer.deinit(self.allocator); } /// Encode a uint256 (no padding) pub fn encodeUint256(self: *PackedEncoder, value: u256) !void { const bytes = uint_utils.u256ToBytes(value); - try self.buffer.appendSlice(&bytes); + try self.buffer.appendSlice(self.allocator, &bytes); } /// Encode a uint of specific size @@ -34,32 +34,32 @@ pub const PackedEncoder = struct { // Take only the required bytes from the end const offset = 32 - size_bytes; - try self.buffer.appendSlice(bytes[offset..]); + try self.buffer.appendSlice(self.allocator, bytes[offset..]); } /// Encode an address (20 bytes, no padding) pub fn encodeAddress(self: *PackedEncoder, addr: Address) !void { - try self.buffer.appendSlice(&addr.bytes); + try self.buffer.appendSlice(self.allocator, &addr.bytes); } /// Encode a boolean (1 byte: 0x00 or 0x01) pub fn encodeBool(self: *PackedEncoder, value: bool) !void { - try self.buffer.append(if (value) 1 else 0); + try self.buffer.append(self.allocator, if (value) 1 else 0); } /// Encode bytes (no length prefix, no padding) pub fn encodeBytes(self: *PackedEncoder, data: []const u8) !void { - try self.buffer.appendSlice(data); + try self.buffer.appendSlice(self.allocator, data); } /// Encode a string (UTF-8 bytes, no length prefix) pub fn encodeString(self: *PackedEncoder, str: []const u8) !void { - try self.buffer.appendSlice(str); + try self.buffer.appendSlice(self.allocator, str); } /// Encode a hash (32 bytes) pub fn encodeHash(self: *PackedEncoder, hash: Hash) !void { - try self.buffer.appendSlice(&hash.bytes); + try self.buffer.appendSlice(self.allocator, &hash.bytes); } /// Get the encoded data @@ -69,7 +69,7 @@ pub const PackedEncoder = struct { /// Get owned encoded data pub fn toOwnedSlice(self: *PackedEncoder) ![]u8 { - return try self.buffer.toOwnedSlice(); + return try self.buffer.toOwnedSlice(self.allocator); } /// Reset encoder for reuse @@ -83,7 +83,7 @@ pub fn encodePacked( allocator: std.mem.Allocator, values: []const PackedValue, ) ![]u8 { - var encoder = PackedEncoder.init(allocator); + var encoder = try PackedEncoder.init(allocator); defer encoder.deinit(); for (values) |value| { @@ -131,7 +131,7 @@ pub fn hashPacked( test "packed encode uint256" { const allocator = std.testing.allocator; - var encoder = PackedEncoder.init(allocator); + var encoder = try PackedEncoder.init(allocator); defer encoder.deinit(); const value = @as(u256, 42); @@ -145,7 +145,7 @@ test "packed encode uint256" { test "packed encode address" { const allocator = std.testing.allocator; - var encoder = PackedEncoder.init(allocator); + var encoder = try PackedEncoder.init(allocator); defer encoder.deinit(); const addr = Address.fromBytes([_]u8{0x12} ** 20); @@ -160,7 +160,7 @@ test "packed encode address" { test "packed encode bool" { const allocator = std.testing.allocator; - var encoder = PackedEncoder.init(allocator); + var encoder = try PackedEncoder.init(allocator); defer encoder.deinit(); try encoder.encodeBool(true); @@ -174,7 +174,7 @@ test "packed encode bool" { test "packed encode string" { const allocator = std.testing.allocator; - var encoder = PackedEncoder.init(allocator); + var encoder = try PackedEncoder.init(allocator); defer encoder.deinit(); try encoder.encodeString("hello"); @@ -210,14 +210,14 @@ test "packed encode vs standard" { const addr = Address.fromBytes([_]u8{0x12} ** 20); // Packed: exactly 20 bytes - var packed_enc = PackedEncoder.init(allocator); + var packed_enc = try PackedEncoder.init(allocator); defer packed_enc.deinit(); try packed_enc.encodeAddress(addr); try std.testing.expectEqual(@as(usize, 20), packed_enc.toSlice().len); // Standard ABI would be 32 bytes with left padding const Encoder = @import("./encode.zig").Encoder; - var standard = Encoder.init(allocator); + var standard = try Encoder.init(allocator); defer standard.deinit(); try standard.encodeAddress(addr); try std.testing.expectEqual(@as(usize, 32), standard.toSlice().len); diff --git a/src/abi/types.zig b/src/abi/types.zig index 7c14de8..540c07a 100644 --- a/src/abi/types.zig +++ b/src/abi/types.zig @@ -187,19 +187,19 @@ pub const Function = struct { /// Get function signature string pub fn getSignature(self: Function, allocator: std.mem.Allocator) ![]u8 { - var sig = std.ArrayList(u8).init(allocator); - defer sig.deinit(); + var sig = try std.ArrayList(u8).initCapacity(allocator, 0); + defer sig.deinit(allocator); - try sig.appendSlice(self.name); - try sig.append('('); + try sig.appendSlice(allocator, self.name); + try sig.append(allocator, '('); for (self.inputs, 0..) |param, i| { - if (i > 0) try sig.append(','); - try sig.appendSlice(try param.type.toString(allocator)); + if (i > 0) try sig.append(allocator, ','); + try sig.appendSlice(allocator, try param.type.toString(allocator)); } - try sig.append(')'); - return sig.toOwnedSlice(); + try sig.append(allocator, ')'); + return try sig.toOwnedSlice(allocator); } }; @@ -211,19 +211,19 @@ pub const Event = struct { /// Get event signature hash pub fn getSignature(self: Event, allocator: std.mem.Allocator) ![]u8 { - var sig = std.ArrayList(u8).init(allocator); - defer sig.deinit(); + var sig = try std.ArrayList(u8).initCapacity(allocator, self.name.len + 2); + defer sig.deinit(allocator); - try sig.appendSlice(self.name); - try sig.append('('); + try sig.appendSlice(allocator, self.name); + try sig.append(allocator, '('); for (self.inputs, 0..) |param, i| { - if (i > 0) try sig.append(','); - try sig.appendSlice(try param.type.toString(allocator)); + if (i > 0) try sig.append(allocator, ','); + try sig.appendSlice(allocator, try param.type.toString(allocator)); } - try sig.append(')'); - return sig.toOwnedSlice(); + try sig.append(allocator, ')'); + return try sig.toOwnedSlice(allocator); } }; @@ -263,16 +263,25 @@ pub fn toString(self: AbiType, allocator: std.mem.Allocator) ![]const u8 { } test "abi type is dynamic" { - try std.testing.expect(!AbiType.uint256.isDynamic()); - try std.testing.expect(!AbiType.address.isDynamic()); - try std.testing.expect(AbiType.string.isDynamic()); - try std.testing.expect(AbiType.bytes.isDynamic()); + const uint256_type: AbiType = .uint256; + const address_type: AbiType = .address; + const string_type: AbiType = .string; + const bytes_type: AbiType = .bytes; + + try std.testing.expect(!uint256_type.isDynamic()); + try std.testing.expect(!address_type.isDynamic()); + try std.testing.expect(string_type.isDynamic()); + try std.testing.expect(bytes_type.isDynamic()); } test "abi type static size" { - try std.testing.expectEqual(@as(?usize, 32), AbiType.uint256.staticSize()); - try std.testing.expectEqual(@as(?usize, 32), AbiType.address.staticSize()); - try std.testing.expectEqual(@as(?usize, null), AbiType.string.staticSize()); + const uint256_type: AbiType = .uint256; + const address_type: AbiType = .address; + const string_type: AbiType = .string; + + try std.testing.expectEqual(@as(?usize, 32), uint256_type.staticSize()); + try std.testing.expectEqual(@as(?usize, 32), address_type.staticSize()); + try std.testing.expectEqual(@as(?usize, null), string_type.staticSize()); } test "function signature" { diff --git a/src/account_abstraction/bundler.zig b/src/account_abstraction/bundler.zig index 98fae12..e7516a6 100644 --- a/src/account_abstraction/bundler.zig +++ b/src/account_abstraction/bundler.zig @@ -73,17 +73,24 @@ pub const BundlerClient = struct { const entry_point_hex = try self.entry_point.toHex(self.allocator); defer self.allocator.free(entry_point_hex); + // Serialize UserOperation to JSON string then parse to Value + const json_string = try std.json.Stringify.valueAlloc(self.allocator, user_op_json, .{}); + defer self.allocator.free(json_string); + + const parsed = try std.json.parseFromSlice(std.json.Value, self.allocator, json_string, .{}); + defer parsed.deinit(); + const user_op_value = parsed.value; + // Build params array: [userOp, entryPoint] - var params_array = std.ArrayList(std.json.Value).init(self.allocator); + var params_array = std.json.Array.init(self.allocator); defer params_array.deinit(); - const user_op_value = try std.json.Value.jsonStringify(user_op_json, .{}, self.allocator); const entry_point_value = std.json.Value{ .string = entry_point_hex }; try params_array.append(user_op_value); try params_array.append(entry_point_value); - const params = std.json.Value{ .array = params_array.items }; + const params = std.json.Value{ .array = params_array }; // Make RPC call const response = try self.rpc_client.call("eth_sendUserOperation", params); @@ -112,12 +119,11 @@ pub const BundlerClient = struct { defer user_op_json.deinit(self.allocator); // Serialize UserOperation to JSON string - var json_string = std.ArrayList(u8).init(self.allocator); - defer json_string.deinit(); - try std.json.stringify(user_op_json, .{}, json_string.writer()); + const json_string = try std.json.Stringify.valueAlloc(self.allocator, user_op_json, .{}); + defer self.allocator.free(json_string); // Parse JSON string into Value - const parsed = try std.json.parseFromSlice(std.json.Value, self.allocator, json_string.items, .{}); + const parsed = try std.json.parseFromSlice(std.json.Value, self.allocator, json_string, .{}); defer parsed.deinit(); const user_op_value = parsed.value; @@ -127,7 +133,7 @@ pub const BundlerClient = struct { const entry_point_value = std.json.Value{ .string = entry_point_hex }; // Build params array: [userOp, entryPoint] - var params_array = std.ArrayList(std.json.Value).init(self.allocator); + var params_array = std.json.Array.init(self.allocator); defer params_array.deinit(); try params_array.append(user_op_value); try params_array.append(entry_point_value); @@ -166,13 +172,13 @@ pub const BundlerClient = struct { defer self.allocator.free(hash_hex); // Build params array: [userOpHash] - var params_array = std.ArrayList(std.json.Value).init(self.allocator); + var params_array = std.json.Array.init(self.allocator); defer params_array.deinit(); const hash_value = std.json.Value{ .string = hash_hex }; try params_array.append(hash_value); - const params = std.json.Value{ .array = params_array.items }; + const params = std.json.Value{ .array = params_array }; // Make RPC call const response = try self.rpc_client.call("eth_getUserOperationByHash", params); @@ -197,13 +203,13 @@ pub const BundlerClient = struct { defer self.allocator.free(hash_hex); // Build params array: [userOpHash] - var params_array = std.ArrayList(std.json.Value).init(self.allocator); + var params_array = std.json.Array.init(self.allocator); defer params_array.deinit(); const hash_value = std.json.Value{ .string = hash_hex }; try params_array.append(hash_value); - const params = std.json.Value{ .array = params_array.items }; + const params = std.json.Value{ .array = params_array }; // Make RPC call const response = try self.rpc_client.call("eth_getUserOperationReceipt", params); @@ -232,16 +238,16 @@ pub const BundlerClient = struct { // Parse result as array of address strings const addresses_array = response.array; - var result = std.ArrayList(primitives.Address).init(self.allocator); - errdefer result.deinit(); + var result = try std.ArrayList(primitives.Address).initCapacity(self.allocator, 0); + errdefer result.deinit(self.allocator); for (addresses_array) |addr_value| { const addr_str = addr_value.string; const address = try primitives.Address.fromHex(addr_str); - try result.append(address); + try result.append(self.allocator, address); } - return try result.toOwnedSlice(); + return try result.toOwnedSlice(self.allocator); } /// Get chain ID diff --git a/src/account_abstraction/entrypoint.zig b/src/account_abstraction/entrypoint.zig index 51b60cc..8a8bf32 100644 --- a/src/account_abstraction/entrypoint.zig +++ b/src/account_abstraction/entrypoint.zig @@ -6,6 +6,7 @@ const rpc_client = @import("../rpc/client.zig"); const abi = @import("../abi/types.zig"); const encode = @import("../abi/encode.zig"); const decode = @import("../abi/decode.zig"); +const hex = @import("../utils/hex.zig"); /// ERC-4337 EntryPoint contract /// Supports v0.6, v0.7, and v0.8 @@ -60,11 +61,11 @@ pub const EntryPoint = struct { defer self.allocator.free(to_hex); // Convert call data to hex string - const data_hex = try std.fmt.allocPrint(self.allocator, "0x{s}", .{std.fmt.fmtSliceHexLower(call_data)}); + const data_hex = try hex.bytesToHex(self.allocator, call_data); defer self.allocator.free(data_hex); // Build eth_call params - var params_array = std.ArrayList(std.json.Value).init(self.allocator); + var params_array = std.json.Array.init(self.allocator); defer params_array.deinit(); // Call object @@ -97,7 +98,7 @@ pub const EntryPoint = struct { defer self.allocator.free(from_hex); // Convert call data to hex string - const data_hex = try std.fmt.allocPrint(self.allocator, "0x{s}", .{std.fmt.fmtSliceHexLower(call_data)}); + const data_hex = try hex.bytesToHex(self.allocator, call_data); defer self.allocator.free(data_hex); // Convert value to hex @@ -105,7 +106,7 @@ pub const EntryPoint = struct { defer self.allocator.free(value_hex); // Build eth_sendTransaction params - var params_array = std.ArrayList(std.json.Value).init(self.allocator); + var params_array = std.json.Array.init(self.allocator); defer params_array.deinit(); // Transaction object @@ -129,22 +130,22 @@ pub const EntryPoint = struct { /// Call: getNonce(address sender, uint192 key) pub fn getNonce(self: *EntryPoint, sender: primitives.Address, key: u192) !u256 { // Build call data - var call_data = std.ArrayList(u8).init(self.allocator); - defer call_data.deinit(); + var call_data = try std.ArrayList(u8).initCapacity(self.allocator, 0); + defer call_data.deinit(self.allocator); // Function selector: getNonce(address,uint192) = 0x35567e1a - try call_data.appendSlice(&[_]u8{ 0x35, 0x56, 0x7e, 0x1a }); + try call_data.appendSlice(self.allocator, &[_]u8{ 0x35, 0x56, 0x7e, 0x1a }); // Encode address (32 bytes, left-padded) var addr_bytes: [32]u8 = [_]u8{0} ** 32; @memcpy(addr_bytes[12..], &sender.bytes); - try call_data.appendSlice(&addr_bytes); + try call_data.appendSlice(self.allocator, &addr_bytes); // Encode uint192 key (32 bytes) var key_bytes: [32]u8 = [_]u8{0} ** 32; const key_u256: u256 = @intCast(key); std.mem.writeInt(u256, &key_bytes, key_u256, .big); - try call_data.appendSlice(&key_bytes); + try call_data.appendSlice(self.allocator, &key_bytes); // Make eth_call const result_hex = try self.ethCall(call_data.items); @@ -163,16 +164,16 @@ pub const EntryPoint = struct { /// Call: balanceOf(address account) pub fn balanceOf(self: *EntryPoint, account: primitives.Address) !u256 { // Build call data - var call_data = std.ArrayList(u8).init(self.allocator); - defer call_data.deinit(); + var call_data = try std.ArrayList(u8).initCapacity(self.allocator, 0); + defer call_data.deinit(self.allocator); // Function selector: balanceOf(address) = 0x70a08231 - try call_data.appendSlice(&[_]u8{ 0x70, 0xa0, 0x82, 0x31 }); + try call_data.appendSlice(self.allocator, &[_]u8{ 0x70, 0xa0, 0x82, 0x31 }); // Encode address (32 bytes, left-padded) var addr_bytes: [32]u8 = [_]u8{0} ** 32; @memcpy(addr_bytes[12..], &account.bytes); - try call_data.appendSlice(&addr_bytes); + try call_data.appendSlice(self.allocator, &addr_bytes); // Make eth_call const result_hex = try self.ethCall(call_data.items); @@ -192,16 +193,16 @@ pub const EntryPoint = struct { /// Returns: (uint112 deposit, bool staked, uint112 stake, uint32 unstakeDelaySec, uint48 withdrawTime) pub fn getDepositInfo(self: *EntryPoint, account: primitives.Address) !DepositInfo { // Build call data - var call_data = std.ArrayList(u8).init(self.allocator); - defer call_data.deinit(); + var call_data = try std.ArrayList(u8).initCapacity(self.allocator, 0); + defer call_data.deinit(self.allocator); // Function selector: getDepositInfo(address) = 0x5287ce12 - try call_data.appendSlice(&[_]u8{ 0x52, 0x87, 0xce, 0x12 }); + try call_data.appendSlice(self.allocator, &[_]u8{ 0x52, 0x87, 0xce, 0x12 }); // Encode address (32 bytes, left-padded) var addr_bytes: [32]u8 = [_]u8{0} ** 32; @memcpy(addr_bytes[12..], &account.bytes); - try call_data.appendSlice(&addr_bytes); + try call_data.appendSlice(self.allocator, &addr_bytes); // Make eth_call const result_hex = try self.ethCall(call_data.items); @@ -233,12 +234,12 @@ pub const EntryPoint = struct { /// Call: simulateValidation(UserOperation calldata userOp) pub fn simulateValidation(self: *EntryPoint, user_op: types.UserOperation) !ValidationResult { // Build call data - var call_data = std.ArrayList(u8).init(self.allocator); - defer call_data.deinit(); + var call_data = try std.ArrayList(u8).initCapacity(self.allocator, 0); + defer call_data.deinit(self.allocator); // Function selector: simulateValidation((address,uint256,bytes,bytes,uint256,uint256,uint256,uint256,uint256,bytes,bytes)) // v0.6 selector = 0xee219423 - try call_data.appendSlice(&[_]u8{ 0xee, 0x21, 0x94, 0x23 }); + try call_data.appendSlice(self.allocator, &[_]u8{ 0xee, 0x21, 0x94, 0x23 }); // TODO: Full UserOperation encoding (complex tuple encoding) // For now, this is a placeholder showing the structure @@ -264,12 +265,12 @@ pub const EntryPoint = struct { from: primitives.Address, ) !Hash { // Build call data - var call_data = std.ArrayList(u8).init(self.allocator); - defer call_data.deinit(); + var call_data = try std.ArrayList(u8).initCapacity(self.allocator, 0); + defer call_data.deinit(self.allocator); // Function selector: handleOps((address,uint256,bytes,bytes,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[],address) // v0.6 selector = 0x1fad948c - try call_data.appendSlice(&[_]u8{ 0x1f, 0xad, 0x94, 0x8c }); + try call_data.appendSlice(self.allocator, &[_]u8{ 0x1f, 0xad, 0x94, 0x8c }); // TODO: Full UserOperation array encoding (very complex) // Would need to: @@ -293,16 +294,16 @@ pub const EntryPoint = struct { /// Call: depositTo(address account) payable pub fn depositTo(self: *EntryPoint, account: primitives.Address, amount: u256, from: primitives.Address) !Hash { // Build call data - var call_data = std.ArrayList(u8).init(self.allocator); - defer call_data.deinit(); + var call_data = try std.ArrayList(u8).initCapacity(self.allocator, 0); + defer call_data.deinit(self.allocator); // Function selector: depositTo(address) = 0xb760faf9 - try call_data.appendSlice(&[_]u8{ 0xb7, 0x60, 0xfa, 0xf9 }); + try call_data.appendSlice(self.allocator, &[_]u8{ 0xb7, 0x60, 0xfa, 0xf9 }); // Encode address (32 bytes, left-padded) var addr_bytes: [32]u8 = [_]u8{0} ** 32; @memcpy(addr_bytes[12..], &account.bytes); - try call_data.appendSlice(&addr_bytes); + try call_data.appendSlice(self.allocator, &addr_bytes); // Send transaction with value return try self.sendTransaction(call_data.items, from, amount); diff --git a/src/account_abstraction/gas.zig b/src/account_abstraction/gas.zig index 8563d0e..b77298b 100644 --- a/src/account_abstraction/gas.zig +++ b/src/account_abstraction/gas.zig @@ -188,7 +188,7 @@ pub const GasEstimator = struct { /// Get gas prices from RPC fn getGasPricesFromRpc(self: *GasEstimator, rpc: *rpc_mod.RpcClient) !GasPrices { // Get base fee from latest block - const params_empty_list = std.ArrayList(std.json.Value).init(self.allocator); + var params_empty_list = std.json.Array.init(self.allocator); defer params_empty_list.deinit(); const params_empty = std.json.Value{ .array = params_empty_list }; diff --git a/src/account_abstraction/paymaster.zig b/src/account_abstraction/paymaster.zig index 9af530c..038674f 100644 --- a/src/account_abstraction/paymaster.zig +++ b/src/account_abstraction/paymaster.zig @@ -69,19 +69,23 @@ pub const PaymasterClient = struct { defer context_obj.deinit(); try context_obj.put("mode", .{ .string = mode.toString() }); + // Serialize UserOperation to JSON string then parse to Value + const json_string = try std.json.Stringify.valueAlloc(self.allocator, user_op_json, .{}); + defer self.allocator.free(json_string); + + const parsed = try std.json.parseFromSlice(std.json.Value, self.allocator, json_string, .{}); + defer parsed.deinit(); + const user_op_value = parsed.value; + // Build params array: [userOp, entryPoint, context] - var params_array = std.ArrayList(std.json.Value).init(self.allocator); + var params_array = std.json.Array.init(self.allocator); defer params_array.deinit(); - const user_op_value = try std.json.Value.jsonStringify(user_op_json, .{}, self.allocator); - defer if (user_op_value == .object) user_op_value.object.deinit(); - try params_array.append(user_op_value); try params_array.append(.{ .string = entry_point_hex }); try params_array.append(.{ .object = context_obj }); - const params = std.json.Value{ .array = try params_array.toOwnedSlice() }; - defer params.array.deinit(self.allocator); + const params = std.json.Value{ .array = params_array }; // Make RPC call const response = try self.rpc_client.call("pm_sponsorUserOperation", params); @@ -167,7 +171,7 @@ pub const PaymasterClient = struct { defer self.allocator.free(entry_point_hex); // Convert token addresses to hex array - var token_array = std.ArrayList(std.json.Value).init(self.allocator); + var token_array = std.json.Array.init(self.allocator); defer token_array.deinit(); for (tokens) |token| { @@ -176,19 +180,23 @@ pub const PaymasterClient = struct { try token_array.append(.{ .string = try self.allocator.dupe(u8, token_hex) }); } + // Serialize UserOperation to JSON string then parse to Value + const json_string = try std.json.Stringify.valueAlloc(self.allocator, user_op_json, .{}); + defer self.allocator.free(json_string); + + const parsed = try std.json.parseFromSlice(std.json.Value, self.allocator, json_string, .{}); + defer parsed.deinit(); + const user_op_value = parsed.value; + // Build params array: [userOp, entryPoint, tokens] - var params_array = std.ArrayList(std.json.Value).init(self.allocator); + var params_array = std.json.Array.init(self.allocator); defer params_array.deinit(); - const user_op_value = try std.json.Value.jsonStringify(user_op_json, .{}, self.allocator); - defer if (user_op_value == .object) user_op_value.object.deinit(); - try params_array.append(user_op_value); try params_array.append(.{ .string = entry_point_hex }); - try params_array.append(.{ .array = try token_array.toOwnedSlice() }); + try params_array.append(.{ .array = token_array }); - const params = std.json.Value{ .array = try params_array.toOwnedSlice() }; - defer params.array.deinit(self.allocator); + const params = std.json.Value{ .array = params_array }; // Make RPC call const response = try self.rpc_client.call("pm_getERC20TokenQuotes", params); @@ -196,8 +204,8 @@ pub const PaymasterClient = struct { // Parse response array const quotes_array = response.array; - var result = std.ArrayList(TokenQuote).init(self.allocator); - errdefer result.deinit(); + var result = try std.ArrayList(TokenQuote).initCapacity(self.allocator, 0); + errdefer result.deinit(self.allocator); for (quotes_array) |quote_value| { const quote_obj = quote_value.object; @@ -208,7 +216,7 @@ pub const PaymasterClient = struct { const exchange_rate = try parseHexU256(quote_obj.get("etherTokenExchangeRate").?.string); const service_fee = @as(u8, @intCast(quote_obj.get("serviceFeePercent").?.integer)); - try result.append(TokenQuote{ + try result.append(self.allocator, TokenQuote{ .token = token_addr, .symbol = symbol, .decimals = decimals, @@ -217,7 +225,7 @@ pub const PaymasterClient = struct { }); } - return try result.toOwnedSlice(); + return try result.toOwnedSlice(self.allocator); } /// Verify paymaster signature diff --git a/src/account_abstraction/smart_account.zig b/src/account_abstraction/smart_account.zig index af155bb..d290447 100644 --- a/src/account_abstraction/smart_account.zig +++ b/src/account_abstraction/smart_account.zig @@ -245,7 +245,7 @@ pub const SmartAccount = struct { const rpc = self.rpc_client orelse return false; // Get code at address using eth_getCode - var params_array = std.ArrayList(std.json.Value).init(self.allocator); + var params_array = std.json.Array.init(self.allocator); defer params_array.deinit(); const address_hex = try self.address.toHex(self.allocator); @@ -289,44 +289,44 @@ pub const SmartAccount = struct { value: u256, data: []const u8, ) ![]u8 { - var call_data = std.ArrayList(u8).init(self.allocator); - errdefer call_data.deinit(); + var call_data = try std.ArrayList(u8).initCapacity(self.allocator, 0); + errdefer call_data.deinit(self.allocator); // Function selector: execute(address,uint256,bytes) = 0xb61d27f6 - try call_data.appendSlice(&[_]u8{ 0xb6, 0x1d, 0x27, 0xf6 }); + try call_data.appendSlice(self.allocator, &[_]u8{ 0xb6, 0x1d, 0x27, 0xf6 }); // Encode address (32 bytes, left-padded) var addr_bytes: [32]u8 = [_]u8{0} ** 32; @memcpy(addr_bytes[12..], &to.bytes); - try call_data.appendSlice(&addr_bytes); + try call_data.appendSlice(self.allocator, &addr_bytes); // Encode value (32 bytes) var value_bytes: [32]u8 = undefined; std.mem.writeInt(u256, &value_bytes, value, .big); - try call_data.appendSlice(&value_bytes); + try call_data.appendSlice(self.allocator, &value_bytes); // Encode data offset (32 bytes) - points to start of data const data_offset: u256 = 96; // After selector + address + value var offset_bytes: [32]u8 = undefined; std.mem.writeInt(u256, &offset_bytes, data_offset, .big); - try call_data.appendSlice(&offset_bytes); + try call_data.appendSlice(self.allocator, &offset_bytes); // Encode data length (32 bytes) const data_len: u256 = @intCast(data.len); var len_bytes: [32]u8 = undefined; std.mem.writeInt(u256, &len_bytes, data_len, .big); - try call_data.appendSlice(&len_bytes); + try call_data.appendSlice(self.allocator, &len_bytes); // Encode data (padded to 32-byte boundary) - try call_data.appendSlice(data); + try call_data.appendSlice(self.allocator, data); // Pad to 32-byte boundary const padding = (32 - (data.len % 32)) % 32; if (padding > 0) { - try call_data.appendNTimes(0, padding); + try call_data.appendNTimes(self.allocator, 0, padding); } - return try call_data.toOwnedSlice(); + return try call_data.toOwnedSlice(self.allocator); } /// Encode batch execute call data @@ -335,11 +335,11 @@ pub const SmartAccount = struct { self: *SmartAccount, calls: []const Call, ) ![]u8 { - var call_data = std.ArrayList(u8).init(self.allocator); - errdefer call_data.deinit(); + var call_data = try std.ArrayList(u8).initCapacity(self.allocator, 0); + errdefer call_data.deinit(self.allocator); // Function selector: executeBatch(address[],uint256[],bytes[]) = 0x47e1da2a - try call_data.appendSlice(&[_]u8{ 0x47, 0xe1, 0xda, 0x2a }); + try call_data.appendSlice(self.allocator, &[_]u8{ 0x47, 0xe1, 0xda, 0x2a }); // ABI encoding for three dynamic arrays // Layout: [selector][offset_to_dests][offset_to_values][offset_to_funcs][dests_array][values_array][funcs_array] @@ -355,44 +355,44 @@ pub const SmartAccount = struct { var offset_bytes: [32]u8 = undefined; std.mem.writeInt(u256, &offset_bytes, offset_to_dests, .big); - try call_data.appendSlice(&offset_bytes); + try call_data.appendSlice(self.allocator, &offset_bytes); std.mem.writeInt(u256, &offset_bytes, offset_to_values, .big); - try call_data.appendSlice(&offset_bytes); + try call_data.appendSlice(self.allocator, &offset_bytes); std.mem.writeInt(u256, &offset_bytes, offset_to_funcs, .big); - try call_data.appendSlice(&offset_bytes); + try call_data.appendSlice(self.allocator, &offset_bytes); // Encode dests array const array_len: u256 = @intCast(calls.len); std.mem.writeInt(u256, &offset_bytes, array_len, .big); - try call_data.appendSlice(&offset_bytes); + try call_data.appendSlice(self.allocator, &offset_bytes); for (calls) |call| { var addr_bytes: [32]u8 = [_]u8{0} ** 32; @memcpy(addr_bytes[12..], &call.to.bytes); - try call_data.appendSlice(&addr_bytes); + try call_data.appendSlice(self.allocator, &addr_bytes); } // Encode values array std.mem.writeInt(u256, &offset_bytes, array_len, .big); - try call_data.appendSlice(&offset_bytes); + try call_data.appendSlice(self.allocator, &offset_bytes); for (calls) |call| { var value_bytes: [32]u8 = undefined; std.mem.writeInt(u256, &value_bytes, call.value, .big); - try call_data.appendSlice(&value_bytes); + try call_data.appendSlice(self.allocator, &value_bytes); } // Encode funcs array (dynamic bytes array) std.mem.writeInt(u256, &offset_bytes, array_len, .big); - try call_data.appendSlice(&offset_bytes); + try call_data.appendSlice(self.allocator, &offset_bytes); // Calculate offsets for each bytes element var current_offset: u256 = 32 * calls.len; // After all offset slots for (calls) |_| { std.mem.writeInt(u256, &offset_bytes, current_offset, .big); - try call_data.appendSlice(&offset_bytes); + try call_data.appendSlice(self.allocator, &offset_bytes); // Update for next element (need to calculate size) } @@ -401,22 +401,22 @@ pub const SmartAccount = struct { // Length const data_len: u256 = @intCast(call.data.len); std.mem.writeInt(u256, &offset_bytes, data_len, .big); - try call_data.appendSlice(&offset_bytes); + try call_data.appendSlice(self.allocator, &offset_bytes); // Data - try call_data.appendSlice(call.data); + try call_data.appendSlice(self.allocator, call.data); // Padding to 32-byte boundary const padding = (32 - (call.data.len % 32)) % 32; if (padding > 0) { - try call_data.appendNTimes(0, padding); + try call_data.appendNTimes(self.allocator, 0, padding); } // Update offset for next element current_offset += 32 + call.data.len + padding; } - return try call_data.toOwnedSlice(); + return try call_data.toOwnedSlice(self.allocator); } }; @@ -448,27 +448,27 @@ pub const AccountFactory = struct { // For SimpleAccount, the initCodeHash includes the owner // This is a simplified version - actual implementation depends on factory - var data = std.ArrayList(u8).init(self.allocator); - defer data.deinit(); + var data = try std.ArrayList(u8).initCapacity(self.allocator, 0); + defer data.deinit(self.allocator); // 0xff prefix - try data.append(0xff); + try data.append(self.allocator, 0xff); // Factory address (20 bytes) - try data.appendSlice(&self.address.bytes); + try data.appendSlice(self.allocator, &self.address.bytes); // Salt (32 bytes) var salt_bytes: [32]u8 = undefined; std.mem.writeInt(u256, &salt_bytes, salt, .big); - try data.appendSlice(&salt_bytes); + try data.appendSlice(self.allocator, &salt_bytes); // InitCode hash (simplified - includes owner) - var init_hash_data = std.ArrayList(u8).init(self.allocator); - defer init_hash_data.deinit(); - try init_hash_data.appendSlice(&owner.bytes); + var init_hash_data = try std.ArrayList(u8).initCapacity(self.allocator, 0); + defer init_hash_data.deinit(self.allocator); + try init_hash_data.appendSlice(self.allocator, &owner.bytes); const init_code_hash = keccak.hash(init_hash_data.items); - try data.appendSlice(&init_code_hash.bytes); + try data.appendSlice(self.allocator, &init_code_hash.bytes); // Calculate final address const address_hash = keccak.hash(data.items); @@ -483,50 +483,50 @@ pub const AccountFactory = struct { /// Create init code for account deployment (v0.6 format) /// Format: factory_address (20 bytes) ++ createAccount(owner, salt) calldata pub fn createInitCode(self: *AccountFactory, owner: primitives.Address, salt: u256) ![]u8 { - var init_code = std.ArrayList(u8).init(self.allocator); - errdefer init_code.deinit(); + var init_code = try std.ArrayList(u8).initCapacity(self.allocator, 0); + errdefer init_code.deinit(self.allocator); // Factory address (20 bytes) - try init_code.appendSlice(&self.address.bytes); + try init_code.appendSlice(self.allocator, &self.address.bytes); // Function selector: createAccount(address,uint256) = 0x5fbfb9cf - try init_code.appendSlice(&[_]u8{ 0x5f, 0xbf, 0xb9, 0xcf }); + try init_code.appendSlice(self.allocator, &[_]u8{ 0x5f, 0xbf, 0xb9, 0xcf }); // Encode owner address (32 bytes, left-padded) var owner_bytes: [32]u8 = [_]u8{0} ** 32; @memcpy(owner_bytes[12..], &owner.bytes); - try init_code.appendSlice(&owner_bytes); + try init_code.appendSlice(self.allocator, &owner_bytes); // Encode salt (32 bytes) var salt_bytes: [32]u8 = undefined; std.mem.writeInt(u256, &salt_bytes, salt, .big); - try init_code.appendSlice(&salt_bytes); + try init_code.appendSlice(self.allocator, &salt_bytes); - return try init_code.toOwnedSlice(); + return try init_code.toOwnedSlice(self.allocator); } /// Create factory and factory data for v0.7+ format /// Returns: (factory_address, factory_data) pub fn createFactoryData(self: *AccountFactory, owner: primitives.Address, salt: u256) !struct { factory: primitives.Address, data: []u8 } { - var factory_data = std.ArrayList(u8).init(self.allocator); - errdefer factory_data.deinit(); + var factory_data = try std.ArrayList(u8).initCapacity(self.allocator, 0); + errdefer factory_data.deinit(self.allocator); // Function selector: createAccount(address,uint256) = 0x5fbfb9cf - try factory_data.appendSlice(&[_]u8{ 0x5f, 0xbf, 0xb9, 0xcf }); + try factory_data.appendSlice(self.allocator, &[_]u8{ 0x5f, 0xbf, 0xb9, 0xcf }); // Encode owner address (32 bytes, left-padded) var owner_bytes: [32]u8 = [_]u8{0} ** 32; @memcpy(owner_bytes[12..], &owner.bytes); - try factory_data.appendSlice(&owner_bytes); + try factory_data.appendSlice(self.allocator, &owner_bytes); // Encode salt (32 bytes) var salt_bytes: [32]u8 = undefined; std.mem.writeInt(u256, &salt_bytes, salt, .big); - try factory_data.appendSlice(&salt_bytes); + try factory_data.appendSlice(self.allocator, &salt_bytes); return .{ .factory = self.address, - .data = try factory_data.toOwnedSlice(), + .data = try factory_data.toOwnedSlice(self.allocator), }; } }; diff --git a/src/account_abstraction/types.zig b/src/account_abstraction/types.zig index 009568b..c5f088c 100644 --- a/src/account_abstraction/types.zig +++ b/src/account_abstraction/types.zig @@ -1,6 +1,7 @@ const std = @import("std"); const primitives = @import("../primitives/address.zig"); const Hash = @import("../primitives/hash.zig").Hash; +const hex_utils = @import("../utils/hex.zig"); /// EntryPoint version pub const EntryPointVersion = enum { @@ -78,40 +79,40 @@ pub const UserOperationV07 = struct { /// Convert to v0.6 format pub fn toV06(self: UserOperationV07, allocator: std.mem.Allocator) !UserOperationV06 { // Combine factory + factoryData into initCode - var init_code = std.ArrayList(u8).init(allocator); - errdefer init_code.deinit(); + var init_code = try std.ArrayList(u8).initCapacity(allocator, 0); + errdefer init_code.deinit(allocator); if (self.factory) |factory| { // Format: factory_address (20 bytes) ++ factoryData - try init_code.appendSlice(&factory.bytes); - try init_code.appendSlice(self.factoryData); + try init_code.appendSlice(allocator, &factory.bytes); + try init_code.appendSlice(allocator, self.factoryData); } - const init_code_slice = try init_code.toOwnedSlice(); + const init_code_slice = try init_code.toOwnedSlice(allocator); // Combine paymaster fields into paymasterAndData - var paymaster_and_data = std.ArrayList(u8).init(allocator); - errdefer paymaster_and_data.deinit(); + var paymaster_and_data = try std.ArrayList(u8).initCapacity(allocator, 0); + errdefer paymaster_and_data.deinit(allocator); if (self.paymaster) |paymaster| { // Format: paymaster_address (20 bytes) ++ verificationGasLimit (16 bytes) ++ postOpGasLimit (16 bytes) ++ paymasterData - try paymaster_and_data.appendSlice(&paymaster.bytes); + try paymaster_and_data.appendSlice(allocator, &paymaster.bytes); // Encode verification gas limit (u128, 16 bytes) var ver_gas_bytes: [16]u8 = undefined; std.mem.writeInt(u128, &ver_gas_bytes, self.paymasterVerificationGasLimit, .big); - try paymaster_and_data.appendSlice(&ver_gas_bytes); + try paymaster_and_data.appendSlice(allocator, &ver_gas_bytes); // Encode post-op gas limit (u128, 16 bytes) var post_gas_bytes: [16]u8 = undefined; std.mem.writeInt(u128, &post_gas_bytes, self.paymasterPostOpGasLimit, .big); - try paymaster_and_data.appendSlice(&post_gas_bytes); + try paymaster_and_data.appendSlice(allocator, &post_gas_bytes); // Append paymaster-specific data - try paymaster_and_data.appendSlice(self.paymasterData); + try paymaster_and_data.appendSlice(allocator, self.paymasterData); } - const paymaster_and_data_slice = try paymaster_and_data.toOwnedSlice(); + const paymaster_and_data_slice = try paymaster_and_data.toOwnedSlice(allocator); return UserOperationV06{ .sender = self.sender, @@ -237,11 +238,11 @@ pub const UserOperationJson = struct { // v0.7/v0.8: need to combine fields // Combine factory + factoryData into initCode if (user_op.factory) |factory| { - var init_code_data = std.ArrayList(u8).init(allocator); - errdefer init_code_data.deinit(); - try init_code_data.appendSlice(&factory.bytes); - try init_code_data.appendSlice(user_op.factoryData); - const init_code_bytes = try init_code_data.toOwnedSlice(); + var init_code_data = try std.ArrayList(u8).initCapacity(allocator, 0); + errdefer init_code_data.deinit(allocator); + try init_code_data.appendSlice(allocator, &factory.bytes); + try init_code_data.appendSlice(allocator, user_op.factoryData); + const init_code_bytes = try init_code_data.toOwnedSlice(allocator); defer allocator.free(init_code_bytes); init_code_hex = try bytesToHex(allocator, init_code_bytes); } else { @@ -250,24 +251,24 @@ pub const UserOperationJson = struct { // Combine paymaster fields into paymasterAndData if (user_op.paymaster) |paymaster| { - var paymaster_data = std.ArrayList(u8).init(allocator); - errdefer paymaster_data.deinit(); - try paymaster_data.appendSlice(&paymaster.bytes); + var paymaster_data = try std.ArrayList(u8).initCapacity(allocator, 0); + errdefer paymaster_data.deinit(allocator); + try paymaster_data.appendSlice(allocator, &paymaster.bytes); // Add verification gas limit (16 bytes, u128) var ver_gas_bytes: [16]u8 = undefined; std.mem.writeInt(u128, &ver_gas_bytes, user_op.paymasterVerificationGasLimit, .big); - try paymaster_data.appendSlice(&ver_gas_bytes); + try paymaster_data.appendSlice(allocator, &ver_gas_bytes); // Add post-op gas limit (16 bytes, u128) var post_gas_bytes: [16]u8 = undefined; std.mem.writeInt(u128, &post_gas_bytes, user_op.paymasterPostOpGasLimit, .big); - try paymaster_data.appendSlice(&post_gas_bytes); + try paymaster_data.appendSlice(allocator, &post_gas_bytes); // Add paymaster-specific data - try paymaster_data.appendSlice(user_op.paymasterData); + try paymaster_data.appendSlice(allocator, user_op.paymasterData); - const paymaster_bytes = try paymaster_data.toOwnedSlice(); + const paymaster_bytes = try paymaster_data.toOwnedSlice(allocator); defer allocator.free(paymaster_bytes); paymaster_and_data_hex = try bytesToHex(allocator, paymaster_bytes); } else { @@ -384,28 +385,28 @@ pub const PaymasterData = struct { /// Pack paymaster data into bytes (v0.7+ format) /// Format: paymaster_address (20 bytes) + verificationGasLimit (16 bytes) + postOpGasLimit (16 bytes) + data pub fn pack(self: PaymasterData, allocator: std.mem.Allocator) ![]u8 { - var packed_data = std.ArrayList(u8).init(allocator); - errdefer packed_data.deinit(); + var packed_data = try std.ArrayList(u8).initCapacity(allocator, 0); + errdefer packed_data.deinit(allocator); // Paymaster address (20 bytes) - try packed_data.appendSlice(&self.paymaster.bytes); + try packed_data.appendSlice(allocator, &self.paymaster.bytes); // Verification gas limit (16 bytes, u128) var ver_gas_bytes: [16]u8 = undefined; const ver_gas_u128: u128 = @intCast(self.verificationGasLimit); std.mem.writeInt(u128, &ver_gas_bytes, ver_gas_u128, .big); - try packed_data.appendSlice(&ver_gas_bytes); + try packed_data.appendSlice(allocator, &ver_gas_bytes); // Post-op gas limit (16 bytes, u128) var post_gas_bytes: [16]u8 = undefined; const post_gas_u128: u128 = @intCast(self.postOpGasLimit); std.mem.writeInt(u128, &post_gas_bytes, post_gas_u128, .big); - try packed_data.appendSlice(&post_gas_bytes); + try packed_data.appendSlice(allocator, &post_gas_bytes); // Paymaster-specific data - try packed_data.appendSlice(self.data); + try packed_data.appendSlice(allocator, self.data); - return try packed_data.toOwnedSlice(); + return try packed_data.toOwnedSlice(allocator); } /// Unpack paymaster data from bytes (v0.7+ format) @@ -461,8 +462,7 @@ fn bytesToHex(allocator: std.mem.Allocator, bytes: []const u8) ![]const u8 { return try allocator.dupe(u8, "0x"); } - const hex = try std.fmt.allocPrint(allocator, "0x{s}", .{std.fmt.fmtSliceHexLower(bytes)}); - return hex; + return try hex_utils.bytesToHex(allocator, bytes); } /// Convert hex string to bytes diff --git a/src/account_abstraction/utils.zig b/src/account_abstraction/utils.zig index bcea6e5..4deebab 100644 --- a/src/account_abstraction/utils.zig +++ b/src/account_abstraction/utils.zig @@ -34,15 +34,15 @@ pub const UserOpHash = struct { const user_op_hash = keccak.hash(packed_data); // Step 3: Create final hash: keccak256(userOpHash ++ entryPoint ++ chainId) - var final_data = std.ArrayList(u8).init(allocator); - defer final_data.deinit(); + var final_data = try std.ArrayList(u8).initCapacity(allocator, 0); + defer final_data.deinit(allocator); - try final_data.appendSlice(&user_op_hash.bytes); - try final_data.appendSlice(&entry_point.bytes); + try final_data.appendSlice(allocator, &user_op_hash.bytes); + try final_data.appendSlice(allocator, &entry_point.bytes); var chain_id_bytes: [32]u8 = [_]u8{0} ** 32; std.mem.writeInt(u64, chain_id_bytes[24..32][0..8], chain_id, .big); - try final_data.appendSlice(&chain_id_bytes); + try final_data.appendSlice(allocator, &chain_id_bytes); return keccak.hash(final_data.items); } @@ -51,29 +51,29 @@ pub const UserOpHash = struct { fn packUserOperation(allocator: std.mem.Allocator, user_op: anytype) ![]u8 { const UserOpType = @TypeOf(user_op); - var packed_bytes = std.ArrayList(u8).init(allocator); - errdefer packed_bytes.deinit(); + var packed_bytes = try std.ArrayList(u8).initCapacity(allocator, 0); + errdefer packed_bytes.deinit(allocator); // Common fields across all versions - try packed_bytes.appendSlice(&user_op.sender.bytes); + try packed_bytes.appendSlice(allocator, &user_op.sender.bytes); var nonce_bytes: [32]u8 = undefined; std.mem.writeInt(u256, &nonce_bytes, user_op.nonce, .big); - try packed_bytes.appendSlice(&nonce_bytes); + try packed_bytes.appendSlice(allocator, &nonce_bytes); // Hash initCode or factory data if (UserOpType == types.UserOperationV06) { const init_hash = keccak.hash(user_op.initCode); - try packed_bytes.appendSlice(&init_hash.bytes); + try packed_bytes.appendSlice(allocator, &init_hash.bytes); } else { // v0.7/v0.8: hash factoryData const factory_hash = keccak.hash(user_op.factoryData); - try packed_bytes.appendSlice(&factory_hash.bytes); + try packed_bytes.appendSlice(allocator, &factory_hash.bytes); } // Hash callData const call_hash = keccak.hash(user_op.callData); - try packed_bytes.appendSlice(&call_hash.bytes); + try packed_bytes.appendSlice(allocator, &call_hash.bytes); // Gas limits (version-specific encoding) if (UserOpType == types.UserOperationV06) { @@ -81,49 +81,49 @@ pub const UserOpHash = struct { var gas_bytes: [32]u8 = undefined; std.mem.writeInt(u256, &gas_bytes, user_op.callGasLimit, .big); - try packed_bytes.appendSlice(&gas_bytes); + try packed_bytes.appendSlice(allocator, &gas_bytes); std.mem.writeInt(u256, &gas_bytes, user_op.verificationGasLimit, .big); - try packed_bytes.appendSlice(&gas_bytes); + try packed_bytes.appendSlice(allocator, &gas_bytes); std.mem.writeInt(u256, &gas_bytes, user_op.preVerificationGas, .big); - try packed_bytes.appendSlice(&gas_bytes); + try packed_bytes.appendSlice(allocator, &gas_bytes); std.mem.writeInt(u256, &gas_bytes, user_op.maxFeePerGas, .big); - try packed_bytes.appendSlice(&gas_bytes); + try packed_bytes.appendSlice(allocator, &gas_bytes); std.mem.writeInt(u256, &gas_bytes, user_op.maxPriorityFeePerGas, .big); - try packed_bytes.appendSlice(&gas_bytes); + try packed_bytes.appendSlice(allocator, &gas_bytes); // Hash paymasterAndData const paymaster_hash = keccak.hash(user_op.paymasterAndData); - try packed_bytes.appendSlice(&paymaster_hash.bytes); + try packed_bytes.appendSlice(allocator, &paymaster_hash.bytes); } else { // v0.7/v0.8: u128 for most gas fields var gas_bytes_128: [16]u8 = undefined; var gas_bytes_256: [32]u8 = undefined; std.mem.writeInt(u128, &gas_bytes_128, user_op.callGasLimit, .big); - try packed_bytes.appendSlice(&gas_bytes_128); + try packed_bytes.appendSlice(allocator, &gas_bytes_128); std.mem.writeInt(u128, &gas_bytes_128, user_op.verificationGasLimit, .big); - try packed_bytes.appendSlice(&gas_bytes_128); + try packed_bytes.appendSlice(allocator, &gas_bytes_128); std.mem.writeInt(u256, &gas_bytes_256, user_op.preVerificationGas, .big); - try packed_bytes.appendSlice(&gas_bytes_256); + try packed_bytes.appendSlice(allocator, &gas_bytes_256); std.mem.writeInt(u128, &gas_bytes_128, user_op.maxFeePerGas, .big); - try packed_bytes.appendSlice(&gas_bytes_128); + try packed_bytes.appendSlice(allocator, &gas_bytes_128); std.mem.writeInt(u128, &gas_bytes_128, user_op.maxPriorityFeePerGas, .big); - try packed_bytes.appendSlice(&gas_bytes_128); + try packed_bytes.appendSlice(allocator, &gas_bytes_128); // Hash paymasterData const paymaster_hash = keccak.hash(user_op.paymasterData); - try packed_bytes.appendSlice(&paymaster_hash.bytes); + try packed_bytes.appendSlice(allocator, &paymaster_hash.bytes); } - return try packed_bytes.toOwnedSlice(); + return try packed_bytes.toOwnedSlice(allocator); } }; diff --git a/src/contract/call.zig b/src/contract/call.zig index 3186beb..f67ba68 100644 --- a/src/contract/call.zig +++ b/src/contract/call.zig @@ -1,7 +1,6 @@ const std = @import("std"); const Address = @import("../primitives/address.zig").Address; const Hash = @import("../primitives/hash.zig").Hash; -const U256 = @import("../primitives/uint.zig").U256; const Bytes = @import("../primitives/bytes.zig").Bytes; const abi = @import("../abi/types.zig"); const Contract = @import("./contract.zig").Contract; @@ -13,15 +12,15 @@ pub const CallBuilder = struct { function: abi.Function, args: std.ArrayList(abi.AbiValue), from: ?Address, - value: ?U256, + value: ?u256, gas_limit: ?u64, - pub fn init(allocator: std.mem.Allocator, contract: *const Contract, function: abi.Function) CallBuilder { + pub fn init(allocator: std.mem.Allocator, contract: *const Contract, function: abi.Function) !CallBuilder { return .{ .allocator = allocator, .contract = contract, .function = function, - .args = std.ArrayList(abi.AbiValue).init(allocator), + .args = try std.ArrayList(abi.AbiValue).initCapacity(allocator, 0), .from = null, .value = null, .gas_limit = null, @@ -29,12 +28,12 @@ pub const CallBuilder = struct { } pub fn deinit(self: *CallBuilder) void { - self.args.deinit(); + self.args.deinit(self.allocator); } /// Add an argument pub fn addArg(self: *CallBuilder, arg: abi.AbiValue) !void { - try self.args.append(arg); + try self.args.append(self.allocator, arg); } /// Set sender address @@ -43,7 +42,7 @@ pub const CallBuilder = struct { } /// Set value to send (for payable functions) - pub fn setValue(self: *CallBuilder, value: U256) void { + pub fn setValue(self: *CallBuilder, value: u256) void { self.value = value; } @@ -73,7 +72,7 @@ pub const CallParams = struct { from: ?Address, to: Address, data: []const u8, - value: ?U256, + value: ?u256, gas_limit: ?u64, pub fn init(to: Address, data: []const u8) CallParams { @@ -144,7 +143,7 @@ pub fn callMutating( function: abi.Function, args: []const abi.AbiValue, from: Address, - value: ?U256, + value: ?u256, gas_limit: ?u64, ) !Hash { _ = contract; // Will be used in future RPC implementation @@ -190,7 +189,7 @@ test "call builder creation" { const contract = try Contract.init(allocator, addr, &[_]abi.Function{func}, &[_]abi.Event{}); defer contract.deinit(); - var builder = CallBuilder.init(allocator, &contract, func); + var builder = try CallBuilder.init(allocator, &contract, func); defer builder.deinit(); try std.testing.expectEqual(addr, builder.getTo()); @@ -213,12 +212,12 @@ test "call builder add arguments" { const contract = try Contract.init(allocator, addr, &[_]abi.Function{}, &[_]abi.Event{}); defer contract.deinit(); - var builder = CallBuilder.init(allocator, &contract, func); + var builder = try CallBuilder.init(allocator, &contract, func); defer builder.deinit(); const to_addr = Address.fromBytes([_]u8{0x34} ** 20); try builder.addArg(.{ .address = to_addr }); - try builder.addArg(.{ .uint = U256.fromInt(1000) }); + try builder.addArg(.{ .uint = 1000 }); try std.testing.expectEqual(@as(usize, 2), builder.args.items.len); } @@ -237,12 +236,12 @@ test "call builder set parameters" { const contract = try Contract.init(allocator, addr, &[_]abi.Function{}, &[_]abi.Event{}); defer contract.deinit(); - var builder = CallBuilder.init(allocator, &contract, func); + var builder = try CallBuilder.init(allocator, &contract, func); defer builder.deinit(); const from = Address.fromBytes([_]u8{0x34} ** 20); builder.setFrom(from); - builder.setValue(U256.fromInt(1000000)); + builder.setValue(1000000); builder.setGasLimit(100000); try std.testing.expect(builder.from != null); @@ -265,9 +264,10 @@ test "call result decode" { const allocator = std.testing.allocator; // Simulated return data (uint256 = 1000) + // 1000 = 0x3E8, in big-endian 32 bytes the last two bytes are 0x03 0xE8 var return_data: [32]u8 = [_]u8{0} ** 32; - return_data[29] = 0x03; - return_data[30] = 0xE8; // 1000 in hex + return_data[30] = 0x03; + return_data[31] = 0xE8; // 1000 = 0x3E8 in hex const func = abi.Function{ .name = "balanceOf", @@ -291,5 +291,5 @@ test "call result decode" { try std.testing.expectEqual(@as(usize, 1), decoded.len); try std.testing.expect(decoded[0] == .uint); - try std.testing.expect(decoded[0].uint.eql(U256.fromInt(1000))); + try std.testing.expect(decoded[0].uint == 1000); } diff --git a/src/contract/contract.zig b/src/contract/contract.zig index 19d1c03..275f412 100644 --- a/src/contract/contract.zig +++ b/src/contract/contract.zig @@ -1,7 +1,6 @@ const std = @import("std"); const Address = @import("../primitives/address.zig").Address; const Hash = @import("../primitives/hash.zig").Hash; -const U256 = @import("../primitives/uint.zig").U256; const Bytes = @import("../primitives/bytes.zig").Bytes; const abi = @import("../abi/types.zig"); const encode = @import("../abi/encode.zig"); diff --git a/src/contract/deploy.zig b/src/contract/deploy.zig index 0b12c79..433a7c8 100644 --- a/src/contract/deploy.zig +++ b/src/contract/deploy.zig @@ -1,7 +1,6 @@ const std = @import("std"); const Address = @import("../primitives/address.zig").Address; const Hash = @import("../primitives/hash.zig").Hash; -const U256 = @import("../primitives/uint.zig").U256; const Bytes = @import("../primitives/bytes.zig").Bytes; const Transaction = @import("../types/transaction.zig").Transaction; const abi = @import("../abi/types.zig"); @@ -14,7 +13,7 @@ pub const DeployBuilder = struct { constructor_args: std.ArrayList(abi.AbiValue), constructor_types: []const abi.Parameter, from: ?Address, - value: ?U256, + value: ?u256, gas_limit: ?u64, /// Create a new deployment builder @@ -22,11 +21,11 @@ pub const DeployBuilder = struct { allocator: std.mem.Allocator, bytecode: Bytes, constructor_types: []const abi.Parameter, - ) DeployBuilder { + ) !DeployBuilder { return .{ .allocator = allocator, .bytecode = bytecode, - .constructor_args = std.ArrayList(abi.AbiValue).init(allocator), + .constructor_args = try std.ArrayList(abi.AbiValue).initCapacity(allocator, 0), .constructor_types = constructor_types, .from = null, .value = null, @@ -35,12 +34,13 @@ pub const DeployBuilder = struct { } pub fn deinit(self: *DeployBuilder) void { - self.constructor_args.deinit(); + self.constructor_args.deinit(self.allocator); + self.bytecode.deinit(); } /// Add a constructor argument pub fn addArg(self: *DeployBuilder, arg: abi.AbiValue) !void { - try self.constructor_args.append(arg); + try self.constructor_args.append(self.allocator, arg); } /// Set deployer address @@ -49,7 +49,7 @@ pub const DeployBuilder = struct { } /// Set value to send (for payable constructors) - pub fn setValue(self: *DeployBuilder, value: U256) void { + pub fn setValue(self: *DeployBuilder, value: u256) void { self.value = value; } @@ -59,16 +59,16 @@ pub const DeployBuilder = struct { } /// Build the deployment data (bytecode + encoded constructor args) - pub fn buildDeploymentData(self: *DeployBuilder) ![]u8 { - var result = std.ArrayList(u8).init(self.allocator); - defer result.deinit(); + pub fn buildDeploymentData(self: *const DeployBuilder) ![]u8 { + var result = try std.ArrayList(u8).initCapacity(self.allocator, 0); + defer result.deinit(self.allocator); // Add bytecode - try result.appendSlice(self.bytecode.data); + try result.appendSlice(self.allocator, self.bytecode.data); // Encode constructor arguments if any if (self.constructor_args.items.len > 0) { - var encoder = encode.Encoder.init(self.allocator); + var encoder = try encode.Encoder.init(self.allocator); defer encoder.deinit(); // Encode each argument @@ -80,10 +80,10 @@ pub const DeployBuilder = struct { } const encoded_args = encoder.toSlice(); - try result.appendSlice(encoded_args); + try result.appendSlice(self.allocator, encoded_args); } - return try result.toOwnedSlice(); + return try result.toOwnedSlice(self.allocator); } /// Estimate the contract address that will be created @@ -155,7 +155,7 @@ test "deploy builder creation" { .{ .name = "initialSupply", .type = .uint256 }, }; - var builder = DeployBuilder.init(allocator, bytecode, &constructor_params); + var builder = try DeployBuilder.init(allocator, bytecode, &constructor_params); defer builder.deinit(); try std.testing.expectEqual(@as(usize, 4), builder.bytecode.len()); @@ -166,10 +166,10 @@ test "deploy builder add arguments" { const bytecode = try Bytes.fromSlice(allocator, &[_]u8{ 0x60, 0x80 }); - var builder = DeployBuilder.init(allocator, bytecode, &[_]abi.Parameter{}); + var builder = try DeployBuilder.init(allocator, bytecode, &[_]abi.Parameter{}); defer builder.deinit(); - try builder.addArg(.{ .uint = U256.fromInt(1000000) }); + try builder.addArg(.{ .uint = 1000000 }); try std.testing.expectEqual(@as(usize, 1), builder.constructor_args.items.len); } @@ -179,12 +179,12 @@ test "deploy builder set parameters" { const bytecode = try Bytes.fromSlice(allocator, &[_]u8{ 0x60, 0x80 }); - var builder = DeployBuilder.init(allocator, bytecode, &[_]abi.Parameter{}); + var builder = try DeployBuilder.init(allocator, bytecode, &[_]abi.Parameter{}); defer builder.deinit(); const from = Address.fromBytes([_]u8{0x12} ** 20); builder.setFrom(from); - builder.setValue(U256.fromInt(500000)); + builder.setValue(500000); builder.setGasLimit(300000); try std.testing.expect(builder.from != null); @@ -198,7 +198,7 @@ test "deploy builder build data" { const bytecode_data = [_]u8{ 0x60, 0x80, 0x60, 0x40 }; const bytecode = try Bytes.fromSlice(allocator, &bytecode_data); - var builder = DeployBuilder.init(allocator, bytecode, &[_]abi.Parameter{}); + var builder = try DeployBuilder.init(allocator, bytecode, &[_]abi.Parameter{}); defer builder.deinit(); const deploy_data = try builder.buildDeploymentData(); @@ -214,7 +214,7 @@ test "estimate create2 address" { const bytecode = try Bytes.fromSlice(allocator, &[_]u8{ 0x60, 0x80 }); - var builder = DeployBuilder.init(allocator, bytecode, &[_]abi.Parameter{}); + var builder = try DeployBuilder.init(allocator, bytecode, &[_]abi.Parameter{}); defer builder.deinit(); const from = Address.fromBytes([_]u8{0x12} ** 20); diff --git a/src/contract/event.zig b/src/contract/event.zig index 4125f4e..d984d12 100644 --- a/src/contract/event.zig +++ b/src/contract/event.zig @@ -1,7 +1,7 @@ const std = @import("std"); const Address = @import("../primitives/address.zig").Address; const Hash = @import("../primitives/hash.zig").Hash; -const U256 = @import("../primitives/uint.zig").U256; +const u256FromBytes = @import("../primitives/uint.zig").u256FromBytes; const Bytes = @import("../primitives/bytes.zig").Bytes; const Log = @import("../types/log.zig").Log; const abi = @import("../abi/types.zig"); @@ -119,8 +119,8 @@ pub fn parseEvent( } // Extract indexed arguments from topics - var indexed_args = std.ArrayList(abi.AbiValue).init(allocator); - defer indexed_args.deinit(); + var indexed_args = try std.ArrayList(abi.AbiValue).initCapacity(allocator, 0); + defer indexed_args.deinit(allocator); var topic_idx: usize = if (event.anonymous) 0 else 1; // Skip event signature if not anonymous @@ -141,7 +141,7 @@ pub fn parseEvent( break :blk abi.AbiValue{ .address = Address.fromBytes(addr_bytes) }; }, .uint256, .uint128, .uint64, .uint32, .uint16, .uint8 => blk: { - break :blk abi.AbiValue{ .uint = U256.fromBytes(topic.bytes) }; + break :blk abi.AbiValue{ .uint = u256FromBytes(topic.bytes) }; }, .bool_type => blk: { break :blk abi.AbiValue{ .bool_val = topic.bytes[31] != 0 }; @@ -153,14 +153,14 @@ pub fn parseEvent( else => return error.UnsupportedIndexedType, }; - try indexed_args.append(value); + try indexed_args.append(allocator, value); topic_idx += 1; } } // Decode non-indexed arguments from data - var data_args = std.ArrayList(abi.AbiValue).init(allocator); - defer data_args.deinit(); + var data_args = try std.ArrayList(abi.AbiValue).initCapacity(allocator, 0); + defer data_args.deinit(allocator); if (log.data.len() > 0) { var decoder = decode.Decoder.init(allocator, log.data.data); @@ -191,15 +191,15 @@ pub fn parseEvent( else => return error.UnsupportedDataType, }; - try data_args.append(value); + try data_args.append(allocator, value); } } } return ParsedEvent{ .event = event, - .indexed_args = try indexed_args.toOwnedSlice(), - .data_args = try data_args.toOwnedSlice(), + .indexed_args = try indexed_args.toOwnedSlice(allocator), + .data_args = try data_args.toOwnedSlice(allocator), .log = log, .allocator = allocator, }; @@ -211,15 +211,15 @@ pub fn parseEvents( event: abi.Event, logs: []const Log, ) ![]ParsedEvent { - var results = std.ArrayList(ParsedEvent).init(allocator); - defer results.deinit(); + var results = try std.ArrayList(ParsedEvent).initCapacity(allocator, 0); + defer results.deinit(allocator); for (logs) |log| { const parsed = parseEvent(allocator, event, log) catch continue; - try results.append(parsed); + try results.append(allocator, parsed); } - return try results.toOwnedSlice(); + return try results.toOwnedSlice(allocator); } /// Get event signature hash from event definition @@ -239,7 +239,7 @@ test "deploy builder creation" { .{ .name = "initialSupply", .type = .uint256 }, }; - var builder = DeployBuilder.init(allocator, bytecode, &constructor_params); + var builder = try DeployBuilder.init(allocator, bytecode, &constructor_params); defer builder.deinit(); try std.testing.expectEqual(@as(usize, 2), builder.bytecode.len()); @@ -250,10 +250,10 @@ test "deploy builder with arguments" { const bytecode = try Bytes.fromSlice(allocator, &[_]u8{ 0x60, 0x80 }); - var builder = DeployBuilder.init(allocator, bytecode, &[_]abi.Parameter{}); + var builder = try DeployBuilder.init(allocator, bytecode, &[_]abi.Parameter{}); defer builder.deinit(); - try builder.addArg(.{ .uint = U256.fromInt(1000000) }); + try builder.addArg(.{ .uint = 1000000 }); try builder.addArg(.{ .address = Address.fromBytes([_]u8{0x12} ** 20) }); try std.testing.expectEqual(@as(usize, 2), builder.constructor_args.items.len); @@ -302,7 +302,7 @@ test "create2 address estimation" { const bytecode = try Bytes.fromSlice(allocator, &[_]u8{ 0x60, 0x80 }); - var builder = DeployBuilder.init(allocator, bytecode, &[_]abi.Parameter{}); + var builder = try DeployBuilder.init(allocator, bytecode, &[_]abi.Parameter{}); defer builder.deinit(); const from = Address.fromBytes([_]u8{0x12} ** 20); diff --git a/src/crypto/ecdsa.zig b/src/crypto/ecdsa.zig index 08b3c0f..f914d4b 100644 --- a/src/crypto/ecdsa.zig +++ b/src/crypto/ecdsa.zig @@ -149,7 +149,7 @@ test "signer creation" { const signer = Signer.init(pk); // Verify signer was created - try std.testing.expect(!signer.private_key.toU256().isZero()); + try std.testing.expect(signer.private_key.toU256() != 0); } test "transaction signer with chain id" { @@ -165,10 +165,13 @@ test "transaction signer with chain id" { test "personal message format" { // Test message formatting const message = "Hello Ethereum!"; + // Prefix: \x19 (1) + "Ethereum Signed Message:\n" (26) + "15" (2) = 29 bytes + // Wait, let's count exactly: \x19 + "Ethereum Signed Message:" (25) + "\n" (1) + "15" (2) = 28 bytes const expected_prefix = "\x19Ethereum Signed Message:\n15"; const prefix_len = expected_prefix.len; const total_len = prefix_len + message.len; - try std.testing.expectEqual(@as(usize, 30), total_len); + // prefix_len = 28, message.len = 15, total = 43 + try std.testing.expectEqual(@as(usize, 43), total_len); } diff --git a/src/crypto/secp256k1.zig b/src/crypto/secp256k1.zig index 5a8a703..753b4b3 100644 --- a/src/crypto/secp256k1.zig +++ b/src/crypto/secp256k1.zig @@ -2,30 +2,32 @@ const std = @import("std"); const Address = @import("../primitives/address.zig").Address; const Hash = @import("../primitives/hash.zig").Hash; const Signature = @import("../primitives/signature.zig").Signature; -const U256 = @import("../primitives/uint.zig").U256; +const uint_utils = @import("../primitives/uint.zig"); +const u256FromBytes = uint_utils.u256FromBytes; +const u256ToBytes = uint_utils.u256ToBytes; const keccak = @import("./keccak.zig"); const secp = @import("secp256k1"); /// secp256k1 curve parameters pub const Secp256k1 = struct { /// Field prime (p) - pub const P = U256.fromBytes([_]u8{ + pub const P: u256 = u256FromBytes([_]u8{ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFF, 0xFC, 0x2F, }); /// Curve order (n) - pub const N = U256.fromBytes([_]u8{ + pub const N: u256 = u256FromBytes([_]u8{ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xBA, 0xAE, 0xDC, 0xE6, 0xAF, 0x48, 0xA0, 0x3B, 0xBF, 0xD2, 0x5E, 0x8C, 0xD0, 0x36, 0x41, 0x41, }); /// Generator point G - pub const G_X = U256.fromBytes([_]u8{ + pub const G_X: u256 = u256FromBytes([_]u8{ 0x79, 0xBE, 0x66, 0x7E, 0xF9, 0xDC, 0xBB, 0xAC, 0x55, 0xA0, 0x62, 0x95, 0xCE, 0x87, 0x0B, 0x07, 0x02, 0x9B, 0xFC, 0xDB, 0x2D, 0xCE, 0x28, 0xD9, 0x59, 0xF2, 0x81, 0x5B, 0x16, 0xF8, 0x17, 0x98, }); - pub const G_Y = U256.fromBytes([_]u8{ + pub const G_Y: u256 = u256FromBytes([_]u8{ 0x48, 0x3A, 0xDA, 0x77, 0x26, 0xA3, 0xC4, 0x65, 0x5D, 0xA4, 0xFB, 0xFC, 0x0E, 0x11, 0x08, 0xA8, 0xFD, 0x17, 0xB4, 0x48, 0xA6, 0x85, 0x54, 0x19, 0x9C, 0x47, 0xD0, 0x8F, 0xFB, 0x10, 0xD4, 0xB8, }); @@ -38,19 +40,19 @@ pub const PrivateKey = struct { /// Create from bytes pub fn fromBytes(bytes: [32]u8) !PrivateKey { // Verify the key is in valid range (0 < key < N) - const key_value = U256.fromBytes(bytes); - if (key_value.isZero() or key_value.gte(Secp256k1.N)) { + const key_value = u256FromBytes(bytes); + if (key_value == 0 or key_value >= Secp256k1.N) { return error.InvalidPrivateKey; } return .{ .bytes = bytes }; } - /// Create from U256 - pub fn fromU256(value: U256) !PrivateKey { - if (value.isZero() or value.gte(Secp256k1.N)) { + /// Create from u256 + pub fn fromU256(value: u256) !PrivateKey { + if (value == 0 or value >= Secp256k1.N) { return error.InvalidPrivateKey; } - return .{ .bytes = value.toBytes() }; + return .{ .bytes = u256ToBytes(value) }; } /// Generate a random private key @@ -59,8 +61,8 @@ pub const PrivateKey = struct { random.bytes(&bytes); // Ensure the key is valid - const key_value = U256.fromBytes(bytes); - if (key_value.isZero() or key_value.gte(Secp256k1.N)) { + const key_value = u256FromBytes(bytes); + if (key_value == 0 or key_value >= Secp256k1.N) { // Try again recursively (very unlikely to fail twice) return try generate(random); } @@ -68,9 +70,9 @@ pub const PrivateKey = struct { return .{ .bytes = bytes }; } - /// Convert to U256 - pub fn toU256(self: PrivateKey) U256 { - return U256.fromBytes(self.bytes); + /// Convert to u256 + pub fn toU256(self: PrivateKey) u256 { + return u256FromBytes(self.bytes); } }; @@ -118,8 +120,8 @@ pub const PublicKey = struct { const result = try allocator.alloc(u8, 33); // Prefix byte: 0x02 if y is even, 0x03 if y is odd - const y_value = U256.fromBytes(self.y); - result[0] = if (y_value.toU64() & 1 == 0) 0x02 else 0x03; + const y_value = u256FromBytes(self.y); + result[0] = if (y_value & 1 == 0) 0x02 else 0x03; @memcpy(result[1..33], &self.x); return result; @@ -187,8 +189,10 @@ pub fn recoverPublicKey(message_hash: Hash, signature: Signature) !PublicKey { var sig_bytes: [65]u8 = undefined; @memcpy(sig_bytes[0..32], &signature.r); @memcpy(sig_bytes[32..64], &signature.s); - // Convert Ethereum v (27-30) back to recovery ID (0-3) - sig_bytes[64] = signature.v - 27; + // Convert Ethereum v back to recovery ID (0-3) + // For legacy signatures (v = 27 or 28), recovery_id = v - 27 + // For EIP-155 signatures (v >= 35), recovery_id = (v - 35) % 2 + sig_bytes[64] = signature.getCompactV(); const pubkey_65 = try ctx.recoverPubkey(message_hash.bytes, sig_bytes); @@ -200,7 +204,7 @@ test "private key validation" { // Valid private key const valid_bytes = [_]u8{1} ** 32; const pk = try PrivateKey.fromBytes(valid_bytes); - try std.testing.expect(!pk.toU256().isZero()); + try std.testing.expect(pk.toU256() != 0); // Zero private key is invalid const zero_bytes = [_]u8{0} ** 32; @@ -213,7 +217,7 @@ test "private key generation" { const random = prng.random(); const pk = try PrivateKey.generate(random); - try std.testing.expect(!pk.toU256().isZero()); + try std.testing.expect(pk.toU256() != 0); } test "public key uncompressed format" { diff --git a/src/crypto/utils.zig b/src/crypto/utils.zig index 5fe0a9b..c7337b6 100644 --- a/src/crypto/utils.zig +++ b/src/crypto/utils.zig @@ -98,19 +98,23 @@ pub fn deriveKey( // Simple iterative hashing (NOT secure for production) // Use PBKDF2 or better in production - var current = try allocator.alloc(u8, password.len + salt.len); + // Buffer must be at least 32 bytes to hold hash result + const buffer_size = @max(password.len + salt.len, 32 + salt.len); + var current = try allocator.alloc(u8, buffer_size); defer allocator.free(current); @memcpy(current[0..password.len], password); - @memcpy(current[password.len..], salt); + @memcpy(current[password.len .. password.len + salt.len], salt); + // Zero out any remaining bytes + if (buffer_size > password.len + salt.len) { + @memset(current[password.len + salt.len ..], 0); + } var i: u32 = 0; while (i < iterations) : (i += 1) { - const hash_result = keccak.hash(current); + const hash_result = keccak.hash(current[0 .. 32 + salt.len]); @memcpy(current[0..32], &hash_result.bytes); - if (current.len > 32) { - @memcpy(current[32..], salt); - } + @memcpy(current[32 .. 32 + salt.len], salt); } const copy_len = @min(key_len, 32); diff --git a/src/errors.zig b/src/errors.zig index a6f5188..6e8d96a 100644 --- a/src/errors.zig +++ b/src/errors.zig @@ -181,10 +181,10 @@ pub fn formatError( err: anyerror, context: ?ErrorContext, ) ![]const u8 { - var result = std.ArrayList(u8).init(allocator); - errdefer result.deinit(); + var result: std.ArrayList(u8) = .empty; + errdefer result.deinit(allocator); - const writer = result.writer(); + const writer = result.writer(allocator); if (context) |ctx| { try writer.print("[{s}] ", .{ctx.module}); @@ -202,7 +202,7 @@ pub fn formatError( try writer.print("Error: {s}", .{@errorName(err)}); } - return try result.toOwnedSlice(); + return try result.toOwnedSlice(allocator); } /// Log error with context @@ -338,36 +338,36 @@ pub const ErrorFormatter = struct { err: anyerror, context: ?ErrorContext, ) ![]const u8 { - var result = std.ArrayList(u8).init(self.allocator); - errdefer result.deinit(); + var result: std.ArrayList(u8) = .empty; + errdefer result.deinit(self.allocator); - try result.appendSlice("{"); - try result.appendSlice("\"error\":\""); - try result.appendSlice(@errorName(err)); - try result.appendSlice("\""); + try result.appendSlice(self.allocator, "{"); + try result.appendSlice(self.allocator, "\"error\":\""); + try result.appendSlice(self.allocator, @errorName(err)); + try result.appendSlice(self.allocator, "\""); if (context) |ctx| { - try result.appendSlice(",\"module\":\""); - try result.appendSlice(ctx.module); - try result.appendSlice("\",\"operation\":\""); - try result.appendSlice(ctx.operation); - try result.appendSlice("\""); + try result.appendSlice(self.allocator, ",\"module\":\""); + try result.appendSlice(self.allocator, ctx.module); + try result.appendSlice(self.allocator, "\",\"operation\":\""); + try result.appendSlice(self.allocator, ctx.operation); + try result.appendSlice(self.allocator, "\""); if (ctx.details) |details| { - try result.appendSlice(",\"details\":\""); - try result.appendSlice(details); - try result.appendSlice("\""); + try result.appendSlice(self.allocator, ",\"details\":\""); + try result.appendSlice(self.allocator, details); + try result.appendSlice(self.allocator, "\""); } if (ctx.code) |code| { const code_str = try std.fmt.allocPrint(self.allocator, ",\"code\":{}", .{code}); defer self.allocator.free(code_str); - try result.appendSlice(code_str); + try result.appendSlice(self.allocator, code_str); } } - try result.appendSlice("}"); - return try result.toOwnedSlice(); + try result.appendSlice(self.allocator, "}"); + return try result.toOwnedSlice(self.allocator); } /// Format error as human-readable text @@ -376,10 +376,10 @@ pub const ErrorFormatter = struct { err: anyerror, context: ?ErrorContext, ) ![]const u8 { - var result = std.ArrayList(u8).init(self.allocator); - errdefer result.deinit(); + var result: std.ArrayList(u8) = .empty; + errdefer result.deinit(self.allocator); - const writer = result.writer(); + const writer = result.writer(self.allocator); if (self.use_colors) { try writer.writeAll("\x1b[31m"); // Red @@ -406,7 +406,7 @@ pub const ErrorFormatter = struct { } } - return try result.toOwnedSlice(); + return try result.toOwnedSlice(self.allocator); } /// Format error as structured log entry @@ -415,10 +415,10 @@ pub const ErrorFormatter = struct { err: anyerror, context: ?ErrorContext, ) ![]const u8 { - var result = std.ArrayList(u8).init(self.allocator); - errdefer result.deinit(); + var result: std.ArrayList(u8) = .empty; + errdefer result.deinit(self.allocator); - const writer = result.writer(); + const writer = result.writer(self.allocator); try writer.writeAll("[ERROR] "); try writer.writeAll(@errorName(err)); @@ -435,7 +435,7 @@ pub const ErrorFormatter = struct { } } - return try result.toOwnedSlice(); + return try result.toOwnedSlice(self.allocator); } }; @@ -552,7 +552,8 @@ pub const ErrorReporter = struct { defer self.allocator.free(log_entry); if (self.log_file) |file| { - const writer = file.writer(); + var w = file.writer(&.{}); + const writer = &w.interface; try writer.print("[{}] {s}\n", .{ timestamp, log_entry }); } diff --git a/src/main.zig b/src/main.zig index 5943bd2..37e5899 100644 --- a/src/main.zig +++ b/src/main.zig @@ -7,7 +7,8 @@ pub fn main() !void { defer _ = gpa.deinit(); const allocator = gpa.allocator(); - const stdout = std.io.getStdOut().writer(); + var stdout_writer = std.fs.File.stdout().writer(&.{}); + const stdout = &stdout_writer.interface; // Parse command line arguments const args = try std.process.argsAlloc(allocator); diff --git a/src/middleware/gas.zig b/src/middleware/gas.zig index 0466d0a..c4c4fb9 100644 --- a/src/middleware/gas.zig +++ b/src/middleware/gas.zig @@ -258,7 +258,8 @@ pub const GasMiddleware = struct { /// Get gas price in gwei pub fn getGasPriceGwei(self: *GasMiddleware) !f64 { const price = try self.getGasPrice(); - const gwei = @as(f64, @floatFromInt(price.toU64() catch 0)) / 1_000_000_000.0; + const price_u64 = uint_utils.u256ToU64(price) catch 0; + const gwei = @as(f64, @floatFromInt(price_u64)) / 1_000_000_000.0; return gwei; } }; diff --git a/src/middleware/nonce.zig b/src/middleware/nonce.zig index b530723..e9a906e 100644 --- a/src/middleware/nonce.zig +++ b/src/middleware/nonce.zig @@ -49,7 +49,7 @@ pub const NonceMiddleware = struct { // Free pending transaction lists var pending_it = self.pending_txs.iterator(); while (pending_it.next()) |entry| { - entry.value_ptr.deinit(); + entry.value_ptr.deinit(self.allocator); } self.pending_txs.deinit(); @@ -122,13 +122,13 @@ pub const NonceMiddleware = struct { // Check if list exists, create if not if (!self.pending_txs.contains(address)) { - const new_list = std.ArrayList(PendingTransaction).init(self.allocator); + const new_list = try std.ArrayList(PendingTransaction).initCapacity(self.allocator, 0); try self.pending_txs.put(address, new_list); } // Get mutable reference to the list const list_ptr = self.pending_txs.getPtr(address) orelse return; - try list_ptr.append(pending_tx); + try list_ptr.append(self.allocator, pending_tx); } /// Get pending transaction count for an address diff --git a/src/middleware/signer.zig b/src/middleware/signer.zig index 38be111..1ed3f59 100644 --- a/src/middleware/signer.zig +++ b/src/middleware/signer.zig @@ -155,7 +155,7 @@ pub const SignerMiddleware = struct { var stub = try self.allocator.alloc(u8, 65); @memcpy(stub[0..32], &sig.r); @memcpy(stub[32..64], &sig.s); - stub[64] = sig.v; + stub[64] = @intCast(sig.v); return stub; } @@ -211,7 +211,7 @@ test "signer config custom" { test "signer middleware creation" { const allocator = std.testing.allocator; - const private_key = PrivateKey.fromBytes([_]u8{1} ** 32); + const private_key = try PrivateKey.fromBytes([_]u8{1} ** 32); const config = SignerConfig.mainnet(); var middleware = try SignerMiddleware.init(allocator, private_key, config); @@ -223,7 +223,7 @@ test "signer middleware creation" { test "signer middleware get address" { const allocator = std.testing.allocator; - const private_key = PrivateKey.fromBytes([_]u8{1} ** 32); + const private_key = try PrivateKey.fromBytes([_]u8{1} ** 32); const config = SignerConfig.mainnet(); var middleware = try SignerMiddleware.init(allocator, private_key, config); @@ -235,7 +235,7 @@ test "signer middleware get address" { test "signer middleware sign message" { const allocator = std.testing.allocator; - const private_key = PrivateKey.fromBytes([_]u8{1} ** 32); + const private_key = try PrivateKey.fromBytes([_]u8{1} ** 32); const config = SignerConfig.mainnet(); var middleware = try SignerMiddleware.init(allocator, private_key, config); diff --git a/src/primitives/signature.zig b/src/primitives/signature.zig index 35b080c..8da4fcd 100644 --- a/src/primitives/signature.zig +++ b/src/primitives/signature.zig @@ -2,14 +2,14 @@ const std = @import("std"); const hex = @import("../utils/hex.zig"); /// ECDSA signature for Ethereum transactions -/// Consists of r (32 bytes), s (32 bytes), and v (1 byte recovery id) +/// Consists of r (32 bytes), s (32 bytes), and v (recovery id, can be > 255 for EIP-155) pub const Signature = struct { r: [32]u8, s: [32]u8, - v: u8, + v: u64, /// Create signature from components - pub fn init(r: [32]u8, s: [32]u8, v: u8) Signature { + pub fn init(r: [32]u8, s: [32]u8, v: u64) Signature { return .{ .r = r, .s = s, @@ -40,11 +40,12 @@ pub const Signature = struct { } /// Convert signature to bytes (65 bytes: r + s + v) + /// Note: For EIP-155 signatures with v > 255, only the lower 8 bits are stored pub fn toBytes(self: Signature, allocator: std.mem.Allocator) ![]u8 { const result = try allocator.alloc(u8, 65); @memcpy(result[0..32], &self.r); @memcpy(result[32..64], &self.s); - result[64] = self.v; + result[64] = @truncate(self.v); return result; } @@ -56,11 +57,16 @@ pub const Signature = struct { } /// Get compact form (without chain ID for legacy signatures) + /// Returns 0 or 1 (the recovery ID) pub fn getCompactV(self: Signature) u8 { - if (self.v >= 27) { - return self.v - 27; + if (self.v >= 35) { + // EIP-155: v = chain_id * 2 + 35 + recovery_id + return @truncate((self.v - 35) % 2); + } else if (self.v >= 27) { + // Legacy: v = 27 or 28 + return @truncate(self.v - 27); } - return self.v; + return @truncate(self.v); } /// Get recovery ID (0 or 1) @@ -77,8 +83,8 @@ pub const Signature = struct { } /// Create EIP-155 compliant v value - pub fn eip155V(chain_id: u64, recovery_id: u8) u8 { - return @intCast(chain_id * 2 + 35 + recovery_id); + pub fn eip155V(chain_id: u64, recovery_id: u8) u64 { + return chain_id * 2 + 35 + recovery_id; } /// Verify signature is valid (basic checks) @@ -200,11 +206,11 @@ test "signature equality" { test "eip155 v calculation" { // Mainnet (chain_id=1) with recovery_id=0 - try std.testing.expectEqual(@as(u8, 37), Signature.eip155V(1, 0)); + try std.testing.expectEqual(@as(u64, 37), Signature.eip155V(1, 0)); // Mainnet (chain_id=1) with recovery_id=1 - try std.testing.expectEqual(@as(u8, 38), Signature.eip155V(1, 1)); + try std.testing.expectEqual(@as(u64, 38), Signature.eip155V(1, 1)); // Polygon (chain_id=137) with recovery_id=0 - try std.testing.expectEqual(@as(u8, 309), Signature.eip155V(137, 0)); + try std.testing.expectEqual(@as(u64, 309), Signature.eip155V(137, 0)); } diff --git a/src/primitives/uint.zig b/src/primitives/uint.zig index be728c1..bc15048 100644 --- a/src/primitives/uint.zig +++ b/src/primitives/uint.zig @@ -3,10 +3,9 @@ //! Zig has native u256 support. This module provides utility functions //! for Ethereum-specific operations like big-endian byte conversions and hex formatting. //! -//! **Recommended Usage:** -//! - Use native `u256` type for all new code +//! **Usage:** +//! - Use native `u256` type for all code //! - Use utility functions (`u256FromBytes`, `u256ToBytes`, etc.) for Ethereum conversions -//! - The legacy `U256` wrapper struct is provided for backwards compatibility only //! //! **Example:** //! ```zig @@ -63,106 +62,6 @@ pub fn u256ToU64(value: u256) !u64 { return @intCast(value); } -/// Legacy U256 type - DEPRECATED, use native u256 instead -/// Kept for backwards compatibility during migration -pub const U256 = struct { - value: u256, - - pub fn fromInt(v: u64) U256 { - return .{ .value = v }; - } - - pub fn zero() U256 { - return .{ .value = 0 }; - } - - pub fn one() U256 { - return .{ .value = 1 }; - } - - pub fn max() U256 { - return .{ .value = std.math.maxInt(u256) }; - } - - pub fn fromBytes(bytes: [32]u8) U256 { - return .{ .value = u256FromBytes(bytes) }; - } - - pub fn toBytes(self: U256) [32]u8 { - return u256ToBytes(self.value); - } - - pub fn fromHex(hex_str: []const u8) !U256 { - return .{ .value = try u256FromHex(hex_str) }; - } - - pub fn toHex(self: U256, allocator: std.mem.Allocator) ![]u8 { - return u256ToHex(self.value, allocator); - } - - pub fn isZero(self: U256) bool { - return self.value == 0; - } - - pub fn eql(self: U256, other: U256) bool { - return self.value == other.value; - } - - pub fn lt(self: U256, other: U256) bool { - return self.value < other.value; - } - - pub fn lte(self: U256, other: U256) bool { - return self.value <= other.value; - } - - pub fn gt(self: U256, other: U256) bool { - return self.value > other.value; - } - - pub fn gte(self: U256, other: U256) bool { - return self.value >= other.value; - } - - pub fn add(self: U256, other: U256) U256 { - return .{ .value = self.value +% other.value }; - } - - pub fn sub(self: U256, other: U256) U256 { - return .{ .value = self.value -% other.value }; - } - - pub fn mulScalar(self: U256, scalar: u64) U256 { - return .{ .value = self.value *% scalar }; - } - - pub fn divScalar(self: U256, scalar: u64) struct { quotient: U256, remainder: u64 } { - return .{ - .quotient = .{ .value = self.value / scalar }, - .remainder = @intCast(self.value % scalar), - }; - } - - pub fn toU64(self: U256) u64 { - return @intCast(self.value & std.math.maxInt(u64)); - } - - pub fn tryToU64(self: U256) !u64 { - return u256ToU64(self.value); - } - - pub fn format( - self: U256, - comptime fmt: []const u8, - options: std.fmt.FormatOptions, - writer: anytype, - ) !void { - _ = fmt; - _ = options; - try writer.print("0x{x}", .{self.value}); - } -}; - test "u256 from bytes" { var bytes: [32]u8 = [_]u8{0} ** 32; bytes[31] = 42; // Last byte (big-endian) @@ -196,12 +95,3 @@ test "u256 to u64" { const too_large: u256 = @as(u256, std.math.maxInt(u64)) + 1; try std.testing.expectError(error.ValueTooLarge, u256ToU64(too_large)); } - -test "legacy U256 wrapper" { - const val = U256.fromInt(42); - try std.testing.expectEqual(@as(u256, 42), val.value); - try std.testing.expect(!val.isZero()); - - const zero = U256.zero(); - try std.testing.expect(zero.isZero()); -} diff --git a/src/providers/http.zig b/src/providers/http.zig index 27fe1f9..888bcc4 100644 --- a/src/providers/http.zig +++ b/src/providers/http.zig @@ -117,7 +117,7 @@ pub const Networks = struct { test "http provider creation" { const allocator = std.testing.allocator; - const provider = try HttpProvider.init(allocator, "http://localhost:8545"); + var provider = try HttpProvider.init(allocator, "http://localhost:8545"); defer provider.deinit(); try std.testing.expectEqualStrings("http://localhost:8545", provider.provider.getEndpoint()); @@ -126,19 +126,19 @@ test "http provider creation" { test "http provider networks" { const allocator = std.testing.allocator; - const mainnet = try Networks.mainnet(allocator); + var mainnet = try Networks.mainnet(allocator); defer mainnet.deinit(); try std.testing.expect(std.mem.indexOf(u8, mainnet.provider.getEndpoint(), "etherspot.io/v2/1") != null); - const sepolia = try Networks.sepolia(allocator); + var sepolia = try Networks.sepolia(allocator); defer sepolia.deinit(); try std.testing.expect(std.mem.indexOf(u8, sepolia.provider.getEndpoint(), "etherspot.io/v2/11155111") != null); - const polygon = try Networks.polygon(allocator); + var polygon = try Networks.polygon(allocator); defer polygon.deinit(); try std.testing.expect(std.mem.indexOf(u8, polygon.provider.getEndpoint(), "etherspot.io/v2/137") != null); - const localhost = try Networks.localhost(allocator); + var localhost = try Networks.localhost(allocator); defer localhost.deinit(); try std.testing.expectEqualStrings("http://localhost:8545", localhost.provider.getEndpoint()); } diff --git a/src/providers/ipc.zig b/src/providers/ipc.zig index 91434d0..d014566 100644 --- a/src/providers/ipc.zig +++ b/src/providers/ipc.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const builtin = @import("builtin"); const Provider = @import("./provider.zig").Provider; /// IPC provider for local Ethereum nodes (Unix socket communication) @@ -46,7 +47,7 @@ pub const IpcProvider = struct { } // Check OS support for Unix sockets - if (std.builtin.os.tag == .windows) { + if (builtin.os.tag == .windows) { // Windows uses named pipes, not Unix sockets return error.WindowsNamedPipesNotSupported; } @@ -88,15 +89,15 @@ pub const IpcProvider = struct { _ = try stream.write(request); // Read response from socket - var response_buf = std.ArrayList(u8).init(self.allocator); - errdefer response_buf.deinit(); + var response_buf = try std.ArrayList(u8).initCapacity(self.allocator, 0); + errdefer response_buf.deinit(self.allocator); var read_buf: [4096]u8 = undefined; while (true) { const bytes_read = try stream.read(&read_buf); if (bytes_read == 0) break; - try response_buf.appendSlice(read_buf[0..bytes_read]); + try response_buf.appendSlice(self.allocator, read_buf[0..bytes_read]); // Check if we have a complete JSON response // (basic check for matching braces) @@ -111,7 +112,7 @@ pub const IpcProvider = struct { } } - return response_buf.toOwnedSlice(); + return response_buf.toOwnedSlice(self.allocator); } /// Get stream for direct access @@ -133,7 +134,7 @@ pub const SocketPaths = struct { /// Get default path for current OS pub fn getDefault() []const u8 { - return switch (std.builtin.os.tag) { + return switch (builtin.os.tag) { .linux => GETH_UNIX, .macos => GETH_MACOS, .windows => GETH_WINDOWS, @@ -145,7 +146,7 @@ pub const SocketPaths = struct { test "ipc provider creation" { const allocator = std.testing.allocator; - const provider = try IpcProvider.init(allocator, "/tmp/geth.ipc"); + var provider = try IpcProvider.init(allocator, "/tmp/geth.ipc"); defer provider.deinit(); try std.testing.expectEqualStrings("/tmp/geth.ipc", provider.getSocketPath()); @@ -222,7 +223,7 @@ test "ipc provider sendRequest when not connected" { } test "ipc provider windows named pipes not supported" { - if (std.builtin.os.tag != .windows) return error.SkipZigTest; + if (builtin.os.tag != .windows) return error.SkipZigTest; const allocator = std.testing.allocator; diff --git a/src/providers/mock.zig b/src/providers/mock.zig index cd46085..b3b0240 100644 --- a/src/providers/mock.zig +++ b/src/providers/mock.zig @@ -1,7 +1,6 @@ const std = @import("std"); const Address = @import("../primitives/address.zig").Address; const Hash = @import("../primitives/hash.zig").Hash; -const U256 = @import("../primitives/uint.zig").U256; const Block = @import("../types/block.zig").Block; const Transaction = @import("../types/transaction.zig").Transaction; const Receipt = @import("../types/receipt.zig").Receipt; @@ -11,10 +10,10 @@ pub const MockProvider = struct { allocator: std.mem.Allocator, chain_id: u64, block_number: u64, - balances: std.AutoHashMap(Address, U256), + balances: std.AutoHashMap(Address, u256), transactions: std.AutoHashMap(Hash, Transaction), receipts: std.AutoHashMap(Hash, Receipt), - gas_price: U256, + gas_price: u256, /// Create a new mock provider pub fn init(allocator: std.mem.Allocator) MockProvider { @@ -22,10 +21,10 @@ pub const MockProvider = struct { .allocator = allocator, .chain_id = 1, .block_number = 1000000, - .balances = std.AutoHashMap(Address, U256).init(allocator), + .balances = std.AutoHashMap(Address, u256).init(allocator), .transactions = std.AutoHashMap(Hash, Transaction).init(allocator), .receipts = std.AutoHashMap(Hash, Receipt).init(allocator), - .gas_price = U256.fromInt(30_000_000_000), // 30 gwei + .gas_price = 30_000_000_000, // 30 gwei }; } @@ -57,7 +56,7 @@ pub const MockProvider = struct { } /// Set balance for an address - pub fn setBalance(self: *MockProvider, address: Address, balance: U256) !void { + pub fn setBalance(self: *MockProvider, address: Address, balance: u256) !void { try self.balances.put(address, balance); } @@ -72,7 +71,7 @@ pub const MockProvider = struct { } /// Set gas price - pub fn setGasPrice(self: *MockProvider, gas_price: U256) void { + pub fn setGasPrice(self: *MockProvider, gas_price: u256) void { self.gas_price = gas_price; } @@ -85,8 +84,8 @@ pub const MockProvider = struct { return self.block_number; } - pub fn getBalance(self: MockProvider, address: Address) !U256 { - return self.balances.get(address) orelse U256.zero(); + pub fn getBalance(self: MockProvider, address: Address) !u256 { + return self.balances.get(address) orelse 0; } pub fn getTransaction(self: MockProvider, hash: Hash) !Transaction { @@ -97,7 +96,7 @@ pub const MockProvider = struct { return self.receipts.get(hash) orelse error.ReceiptNotFound; } - pub fn getGasPrice(self: MockProvider) !U256 { + pub fn getGasPrice(self: MockProvider) !u256 { return self.gas_price; } @@ -113,7 +112,7 @@ pub const MockProvider = struct { self.receipts.clearAndFree(); self.block_number = 1000000; self.chain_id = 1; - self.gas_price = U256.fromInt(30_000_000_000); + self.gas_price = 30_000_000_000; } }; @@ -137,12 +136,12 @@ test "mock provider set balance" { defer provider.deinit(); const addr = Address.fromBytes([_]u8{0x12} ** 20); - const balance = U256.fromInt(1_000_000_000_000_000_000); // 1 ETH + const balance: u256 = 1_000_000_000_000_000_000; // 1 ETH try provider.setBalance(addr, balance); const retrieved = try provider.getBalance(addr); - try std.testing.expect(retrieved.eql(balance)); + try std.testing.expectEqual(balance, retrieved); } test "mock provider mine block" { @@ -164,11 +163,11 @@ test "mock provider gas price" { var provider = MockProvider.init(allocator); defer provider.deinit(); - const custom_price = U256.fromInt(50_000_000_000); // 50 gwei + const custom_price: u256 = 50_000_000_000; // 50 gwei provider.setGasPrice(custom_price); const price = try provider.getGasPrice(); - try std.testing.expect(price.eql(custom_price)); + try std.testing.expectEqual(custom_price, price); } test "mock provider reset" { @@ -179,7 +178,7 @@ test "mock provider reset" { // Set some state const addr = Address.fromBytes([_]u8{0x12} ** 20); - try provider.setBalance(addr, U256.fromInt(1000)); + try provider.setBalance(addr, 1000); provider.setBlockNumber(2000000); // Reset @@ -190,5 +189,5 @@ test "mock provider reset" { try std.testing.expectEqual(@as(u64, 1000000), block_num); const balance = try provider.getBalance(addr); - try std.testing.expect(balance.isZero()); + try std.testing.expectEqual(@as(u256, 0), balance); } diff --git a/src/providers/provider.zig b/src/providers/provider.zig index e5418c9..d3015b9 100644 --- a/src/providers/provider.zig +++ b/src/providers/provider.zig @@ -51,7 +51,7 @@ pub const Provider = struct { } /// Get the endpoint URL - pub fn getEndpoint(self: *Provider) []const u8 { + pub fn getEndpoint(self: *const Provider) []const u8 { return self.rpc_client.endpoint; } @@ -190,11 +190,17 @@ test "provider creation" { test "provider has all namespaces" { const allocator = std.testing.allocator; - const provider = try Provider.init(allocator, "http://localhost:8545"); + var provider = try Provider.init(allocator, "http://localhost:8545"); defer provider.deinit(); - try std.testing.expect(provider.eth.client.endpoint.len > 0); - try std.testing.expect(provider.net.client.endpoint.len > 0); - try std.testing.expect(provider.web3.client.endpoint.len > 0); - try std.testing.expect(provider.debug.client.endpoint.len > 0); + // Test that we can get all namespaces and they reference the client + const eth = provider.getEth(); + const net = provider.getNet(); + const web3 = provider.getWeb3(); + const debug = provider.getDebug(); + + try std.testing.expect(eth.client.endpoint.len > 0); + try std.testing.expect(net.client.endpoint.len > 0); + try std.testing.expect(web3.client.endpoint.len > 0); + try std.testing.expect(debug.client.endpoint.len > 0); } diff --git a/src/providers/ws.zig b/src/providers/ws.zig index 536b28a..1fe942b 100644 --- a/src/providers/ws.zig +++ b/src/providers/ws.zig @@ -154,25 +154,25 @@ pub const WsProvider = struct { const request_id = self.getNextRequestId(); // Build filter JSON - var filter_json = std.ArrayList(u8).init(self.allocator); - defer filter_json.deinit(); + var filter_json = try std.ArrayList(u8).initCapacity(self.allocator, 0); + defer filter_json.deinit(self.allocator); - try filter_json.appendSlice("{"); + try filter_json.appendSlice(self.allocator, "{"); if (filter.address) |addr| { - try filter_json.appendSlice("\"address\":\""); + try filter_json.appendSlice(self.allocator, "\"address\":\""); const addr_hex = try addr.toHex(self.allocator); defer self.allocator.free(addr_hex); - try filter_json.appendSlice(addr_hex); - try filter_json.appendSlice("\","); + try filter_json.appendSlice(self.allocator, addr_hex); + try filter_json.appendSlice(self.allocator, "\","); } if (filter.topics) |_| { - try filter_json.appendSlice("\"topics\":[],"); + try filter_json.appendSlice(self.allocator, "\"topics\":[],"); } // Remove trailing comma if present if (filter_json.items.len > 1 and filter_json.items[filter_json.items.len - 1] == ',') { _ = filter_json.pop(); } - try filter_json.appendSlice("}"); + try filter_json.appendSlice(self.allocator, "}"); const request = try std.fmt.allocPrint( self.allocator, @@ -418,29 +418,29 @@ const WsClient = struct { const stream = self.stream orelse return error.NotConnected; // Build WebSocket frame (text frame, no masking for simplicity) - var frame = std.ArrayList(u8).init(self.allocator); - defer frame.deinit(); + var frame = try std.ArrayList(u8).initCapacity(self.allocator, 0); + defer frame.deinit(self.allocator); // Opcode: text frame (0x81) - try frame.append(0x81); + try frame.append(self.allocator, 0x81); // Payload length if (message.len < 126) { - try frame.append(@intCast(message.len)); + try frame.append(self.allocator, @intCast(message.len)); } else if (message.len < 65536) { - try frame.append(126); - try frame.append(@intCast(message.len >> 8)); - try frame.append(@intCast(message.len & 0xFF)); + try frame.append(self.allocator, 126); + try frame.append(self.allocator, @intCast(message.len >> 8)); + try frame.append(self.allocator, @intCast(message.len & 0xFF)); } else { - try frame.append(127); + try frame.append(self.allocator, 127); var i: usize = 7; while (i >= 0) : (i -= 1) { - try frame.append(@intCast((message.len >> @intCast(i * 8)) & 0xFF)); + try frame.append(self.allocator, @intCast((message.len >> @intCast(i * 8)) & 0xFF)); } } // Payload - try frame.appendSlice(message); + try frame.appendSlice(self.allocator, message); // Send frame _ = try stream.write(frame.items); @@ -453,8 +453,8 @@ const WsClient = struct { pub fn receiveMessage(self: *WsClient) ![]u8 { const stream = self.stream orelse return error.NotConnected; - var response = std.ArrayList(u8).init(self.allocator); - errdefer response.deinit(); + var response = try std.ArrayList(u8).initCapacity(self.allocator, 0); + errdefer response.deinit(self.allocator); // Read frame header var header: [2]u8 = undefined; @@ -481,18 +481,18 @@ const WsClient = struct { } // Read payload - try response.ensureTotalCapacity(@intCast(payload_len)); + try response.ensureTotalCapacity(self.allocator, @intCast(payload_len)); var remaining = payload_len; while (remaining > 0) { var buf: [4096]u8 = undefined; const to_read = @min(remaining, buf.len); const bytes_read = try stream.read(buf[0..to_read]); if (bytes_read == 0) break; - try response.appendSlice(buf[0..bytes_read]); + try response.appendSlice(self.allocator, buf[0..bytes_read]); remaining -= bytes_read; } - return response.toOwnedSlice(); + return response.toOwnedSlice(self.allocator); } }; diff --git a/src/rlp/decode.zig b/src/rlp/decode.zig index c0ef8c1..1e2fc52 100644 --- a/src/rlp/decode.zig +++ b/src/rlp/decode.zig @@ -1,5 +1,14 @@ const std = @import("std"); +/// RLP decoding errors +pub const DecodeError = error{ + UnexpectedEndOfInput, + InvalidLengthEncoding, + InvalidListLength, + InvalidLength, + OutOfMemory, +}; + /// Decoded RLP value pub const RlpValue = union(enum) { bytes: []const u8, @@ -59,7 +68,7 @@ pub const Decoder = struct { } /// Decode next RLP value - pub fn decode(self: *Decoder) !RlpValue { + pub fn decode(self: *Decoder) DecodeError!RlpValue { if (self.pos >= self.data.len) { return error.UnexpectedEndOfInput; } @@ -123,25 +132,25 @@ pub const Decoder = struct { } /// Decode list payload - fn decodeListPayload(self: *Decoder, end: usize) !RlpValue { - var items = std.ArrayList(RlpValue).init(self.allocator); + fn decodeListPayload(self: *Decoder, end: usize) DecodeError!RlpValue { + var items = try std.ArrayList(RlpValue).initCapacity(self.allocator, 0); errdefer { for (items.items) |item| { item.deinit(self.allocator); } - items.deinit(); + items.deinit(self.allocator); } while (self.pos < end) { const item = try self.decode(); - try items.append(item); + try items.append(self.allocator, item); } if (self.pos != end) { return error.InvalidListLength; } - return RlpValue{ .list = try items.toOwnedSlice() }; + return RlpValue{ .list = try items.toOwnedSlice(self.allocator) }; } /// Check if there's more data @@ -288,8 +297,8 @@ test "decode list of strings" { test "decode nested list" { const allocator = std.testing.allocator; - // [["a"], "b"] - const data = [_]u8{ 0xc4, 0xc1, 0x61, 0x62 }; + // [["a"], "b"] - payload is 3 bytes: C1 61 (inner list) + 62 ("b") + const data = [_]u8{ 0xc3, 0xc1, 0x61, 0x62 }; const value = try decode(allocator, &data); defer value.deinit(allocator); diff --git a/src/rlp/encode.zig b/src/rlp/encode.zig index 3d6e173..20c668d 100644 --- a/src/rlp/encode.zig +++ b/src/rlp/encode.zig @@ -14,15 +14,15 @@ pub const Encoder = struct { buffer: std.ArrayList(u8), allocator: std.mem.Allocator, - pub fn init(allocator: std.mem.Allocator) Encoder { + pub fn init(allocator: std.mem.Allocator) !Encoder { return .{ - .buffer = std.ArrayList(u8).init(allocator), + .buffer = try std.ArrayList(u8).initCapacity(allocator, 0), .allocator = allocator, }; } pub fn deinit(self: *Encoder) void { - self.buffer.deinit(); + self.buffer.deinit(self.allocator); } /// Get the encoded bytes @@ -32,7 +32,7 @@ pub const Encoder = struct { /// Get owned slice and reset encoder pub fn toOwnedSlice(self: *Encoder) ![]u8 { - return try self.buffer.toOwnedSlice(); + return try self.buffer.toOwnedSlice(self.allocator); } /// Encode an RLP item @@ -64,29 +64,29 @@ pub const Encoder = struct { pub fn encodeBytes(self: *Encoder, data: []const u8) !void { if (data.len == 0) { // Empty string - try self.buffer.append(0x80); + try self.buffer.append(self.allocator, 0x80); } else if (data.len == 1 and data[0] < 0x80) { // Single byte < 0x80 - try self.buffer.append(data[0]); + try self.buffer.append(self.allocator, data[0]); } else if (data.len <= 55) { // Short string (0-55 bytes) - try self.buffer.append(0x80 + @as(u8, @intCast(data.len))); - try self.buffer.appendSlice(data); + try self.buffer.append(self.allocator, 0x80 + @as(u8, @intCast(data.len))); + try self.buffer.appendSlice(self.allocator, data); } else { // Long string (> 55 bytes) const len_bytes = try encodeLengthBytes(self.allocator, data.len); defer self.allocator.free(len_bytes); - try self.buffer.append(0xb7 + @as(u8, @intCast(len_bytes.len))); - try self.buffer.appendSlice(len_bytes); - try self.buffer.appendSlice(data); + try self.buffer.append(self.allocator, 0xb7 + @as(u8, @intCast(len_bytes.len))); + try self.buffer.appendSlice(self.allocator, len_bytes); + try self.buffer.appendSlice(self.allocator, data); } } /// Encode a list of items pub fn encodeList(self: *Encoder, items: []const RlpItem) !void { // Encode all items to a temporary buffer - var temp_encoder = Encoder.init(self.allocator); + var temp_encoder = try Encoder.init(self.allocator); defer temp_encoder.deinit(); for (items) |item| { @@ -97,16 +97,16 @@ pub const Encoder = struct { if (payload.len <= 55) { // Short list - try self.buffer.append(0xc0 + @as(u8, @intCast(payload.len))); - try self.buffer.appendSlice(payload); + try self.buffer.append(self.allocator, 0xc0 + @as(u8, @intCast(payload.len))); + try self.buffer.appendSlice(self.allocator, payload); } else { // Long list const len_bytes = try encodeLengthBytes(self.allocator, payload.len); defer self.allocator.free(len_bytes); - try self.buffer.append(0xf7 + @as(u8, @intCast(len_bytes.len))); - try self.buffer.appendSlice(len_bytes); - try self.buffer.appendSlice(payload); + try self.buffer.append(self.allocator, 0xf7 + @as(u8, @intCast(len_bytes.len))); + try self.buffer.appendSlice(self.allocator, len_bytes); + try self.buffer.appendSlice(self.allocator, payload); } } }; @@ -140,7 +140,7 @@ fn encodeLengthBytes(allocator: std.mem.Allocator, length: usize) ![]u8 { /// Encode a single item and return owned slice pub fn encodeItem(allocator: std.mem.Allocator, item: RlpItem) ![]u8 { - var encoder = Encoder.init(allocator); + var encoder = try Encoder.init(allocator); defer encoder.deinit(); try encoder.encode(item); @@ -149,7 +149,7 @@ pub fn encodeItem(allocator: std.mem.Allocator, item: RlpItem) ![]u8 { /// Encode a list of items and return owned slice pub fn encodeList(allocator: std.mem.Allocator, items: []const RlpItem) ![]u8 { - var encoder = Encoder.init(allocator); + var encoder = try Encoder.init(allocator); defer encoder.deinit(); try encoder.encodeList(items); @@ -158,7 +158,7 @@ pub fn encodeList(allocator: std.mem.Allocator, items: []const RlpItem) ![]u8 { /// Encode bytes and return owned slice pub fn encodeBytes(allocator: std.mem.Allocator, data: []const u8) ![]u8 { - var encoder = Encoder.init(allocator); + var encoder = try Encoder.init(allocator); defer encoder.deinit(); try encoder.encodeBytes(data); @@ -254,9 +254,10 @@ test "encode nested list" { const encoded = try encodeList(allocator, &items); defer allocator.free(encoded); - // Inner list ["a"] = 0xc1 0x61 - // Outer list [["a"], "b"] = 0xc4 0xc1 0x61 0x62 - const expected = [_]u8{ 0xc4, 0xc1, 0x61, 0x62 }; + // Inner list ["a"] = 0xc1 0x61 (2 bytes) + // Outer list [["a"], "b"] = 0xc3 0xc1 0x61 0x62 + // Payload: C1 61 (inner list) + 62 ("b") = 3 bytes, so 0xc0 + 3 = 0xc3 + const expected = [_]u8{ 0xc3, 0xc1, 0x61, 0x62 }; try std.testing.expectEqualSlices(u8, &expected, encoded); } @@ -305,7 +306,7 @@ test "encode uint large" { test "encode item union" { const allocator = std.testing.allocator; - var encoder = Encoder.init(allocator); + var encoder = try Encoder.init(allocator); defer encoder.deinit(); try encoder.encode(.{ .bytes = "hello" }); diff --git a/src/rlp/packed.zig b/src/rlp/packed.zig index 41d7a62..6847c05 100644 --- a/src/rlp/packed.zig +++ b/src/rlp/packed.zig @@ -3,8 +3,9 @@ const encode = @import("./encode.zig"); const decode = @import("./decode.zig"); const Address = @import("../primitives/address.zig").Address; const Hash = @import("../primitives/hash.zig").Hash; -const U256 = @import("../primitives/uint.zig").U256; // Legacy compatibility const uint_utils = @import("../primitives/uint.zig"); +const u256ToBytes = uint_utils.u256ToBytes; +const u256FromBytes = uint_utils.u256FromBytes; const Signature = @import("../primitives/signature.zig").Signature; const Transaction = @import("../types/transaction.zig").Transaction; const TransactionType = @import("../types/transaction.zig").TransactionType; @@ -20,38 +21,38 @@ pub const TransactionEncoder = struct { return error.NotLegacyTransaction; } - var items = std.ArrayList(encode.RlpItem).init(allocator); - defer items.deinit(); + var items = try std.ArrayList(encode.RlpItem).initCapacity(allocator, 0); + defer items.deinit(allocator); // nonce - try items.append(.{ .uint = tx.nonce }); + try items.append(allocator, .{ .uint = tx.nonce }); // gas_price const gas_price_bytes = uint_utils.u256ToBytes(tx.gas_price.?); - try items.append(.{ .bytes = &gas_price_bytes }); + try items.append(allocator, .{ .bytes = &gas_price_bytes }); // gas_limit - try items.append(.{ .uint = tx.gas_limit }); + try items.append(allocator, .{ .uint = tx.gas_limit }); // to (or empty for contract creation) if (tx.to) |to_addr| { - try items.append(.{ .bytes = &to_addr.bytes }); + try items.append(allocator, .{ .bytes = &to_addr.bytes }); } else { - try items.append(.{ .bytes = &[_]u8{} }); + try items.append(allocator, .{ .bytes = &[_]u8{} }); } // value const value_bytes = uint_utils.u256ToBytes(tx.value); - try items.append(.{ .bytes = &value_bytes }); + try items.append(allocator, .{ .bytes = &value_bytes }); // data - try items.append(.{ .bytes = tx.data.data }); + try items.append(allocator, .{ .bytes = tx.data.data }); // For EIP-155: v, r, s (chain_id, 0, 0) if (tx.chain_id) |chain_id| { - try items.append(.{ .uint = chain_id }); - try items.append(.{ .uint = 0 }); - try items.append(.{ .uint = 0 }); + try items.append(allocator, .{ .uint = chain_id }); + try items.append(allocator, .{ .uint = 0 }); + try items.append(allocator, .{ .uint = 0 }); } return try encode.encodeList(allocator, items.items); @@ -70,40 +71,40 @@ pub const TransactionEncoder = struct { return error.TransactionNotSigned; } - var items = std.ArrayList(encode.RlpItem).init(allocator); - defer items.deinit(); + var items = try std.ArrayList(encode.RlpItem).initCapacity(allocator, 0); + defer items.deinit(allocator); // nonce - try items.append(.{ .uint = tx.nonce }); + try items.append(allocator, .{ .uint = tx.nonce }); // gas_price const gas_price_bytes = uint_utils.u256ToBytes(tx.gas_price.?); - try items.append(.{ .bytes = &gas_price_bytes }); + try items.append(allocator, .{ .bytes = &gas_price_bytes }); // gas_limit - try items.append(.{ .uint = tx.gas_limit }); + try items.append(allocator, .{ .uint = tx.gas_limit }); // to if (tx.to) |to_addr| { - try items.append(.{ .bytes = &to_addr.bytes }); + try items.append(allocator, .{ .bytes = &to_addr.bytes }); } else { - try items.append(.{ .bytes = &[_]u8{} }); + try items.append(allocator, .{ .bytes = &[_]u8{} }); } // value const value_bytes = uint_utils.u256ToBytes(tx.value); - try items.append(.{ .bytes = &value_bytes }); + try items.append(allocator, .{ .bytes = &value_bytes }); // data - try items.append(.{ .bytes = tx.data.data }); + try items.append(allocator, .{ .bytes = tx.data.data }); // Signature (v, r, s) const sig = tx.signature.?; - try items.append(.{ .uint = sig.v }); + try items.append(allocator, .{ .uint = sig.v }); // r and s are already [32]u8 arrays - try items.append(.{ .bytes = &sig.r }); - try items.append(.{ .bytes = &sig.s }); + try items.append(allocator, .{ .bytes = &sig.r }); + try items.append(allocator, .{ .bytes = &sig.s }); return try encode.encodeList(allocator, items.items); } @@ -121,9 +122,9 @@ pub const EthereumEncoder = struct { return try encode.encodeBytes(allocator, &hash.bytes); } - /// Encode a U256 - pub fn encodeU256(allocator: std.mem.Allocator, value: U256) ![]u8 { - const bytes = value.toBytes(); + /// Encode a u256 + pub fn encodeU256(allocator: std.mem.Allocator, value: u256) ![]u8 { + const bytes = u256ToBytes(value); // Find first non-zero byte var start: usize = 0; @@ -142,11 +143,11 @@ pub const EthereumEncoder = struct { allocator: std.mem.Allocator, addresses: []const Address, ) ![]u8 { - var items = std.ArrayList(encode.RlpItem).init(allocator); - defer items.deinit(); + var items = try std.ArrayList(encode.RlpItem).initCapacity(allocator, 0); + defer items.deinit(allocator); for (addresses) |addr| { - try items.append(.{ .bytes = &addr.bytes }); + try items.append(allocator, .{ .bytes = &addr.bytes }); } return try encode.encodeList(allocator, items.items); @@ -157,11 +158,11 @@ pub const EthereumEncoder = struct { allocator: std.mem.Allocator, hashes: []const Hash, ) ![]u8 { - var items = std.ArrayList(encode.RlpItem).init(allocator); - defer items.deinit(); + var items = try std.ArrayList(encode.RlpItem).initCapacity(allocator, 0); + defer items.deinit(allocator); for (hashes) |hash| { - try items.append(.{ .bytes = &hash.bytes }); + try items.append(allocator, .{ .bytes = &hash.bytes }); } return try encode.encodeList(allocator, items.items); @@ -190,10 +191,10 @@ pub const EthereumDecoder = struct { return Hash.fromBytes(bytes); } - /// Decode a U256 from RLP bytes - pub fn decodeU256(data: []const u8) !U256 { + /// Decode a u256 from RLP bytes + pub fn decodeU256(data: []const u8) !u256 { if (data.len == 0) { - return U256.zero(); + return 0; } if (data.len > 32) { return error.ValueTooLarge; @@ -204,7 +205,7 @@ pub const EthereumDecoder = struct { const offset = 32 - data.len; @memcpy(bytes[offset..], data); - return U256.fromBytes(bytes); + return u256FromBytes(bytes); } }; @@ -232,10 +233,10 @@ test "encode hash" { try std.testing.expectEqual(@as(u8, 0x80 + 32), encoded[0]); } -test "encode U256 zero" { +test "encode u256 zero" { const allocator = std.testing.allocator; - const value = U256.zero(); + const value: u256 = 0; const encoded = try EthereumEncoder.encodeU256(allocator, value); defer allocator.free(encoded); @@ -243,10 +244,10 @@ test "encode U256 zero" { try std.testing.expectEqualSlices(u8, &[_]u8{0x80}, encoded); } -test "encode U256 small" { +test "encode u256 small" { const allocator = std.testing.allocator; - const value = U256.fromInt(42); + const value: u256 = 42; const encoded = try EthereumEncoder.encodeU256(allocator, value); defer allocator.free(encoded); @@ -268,14 +269,14 @@ test "decode hash" { try std.testing.expectEqual(Hash.fromBytes(hash_bytes), hash); } -test "decode U256 zero" { +test "decode u256 zero" { const value = try EthereumDecoder.decodeU256(&[_]u8{}); - try std.testing.expect(value.isZero()); + try std.testing.expectEqual(@as(u256, 0), value); } -test "decode U256 small" { +test "decode u256 small" { const value = try EthereumDecoder.decodeU256(&[_]u8{0x2a}); - try std.testing.expect(value.eql(U256.fromInt(42))); + try std.testing.expectEqual(@as(u256, 42), value); } test "encode address list" { @@ -308,10 +309,10 @@ test "encode hash list" { try std.testing.expect(encoded.len > 64); } -test "roundtrip U256 encoding" { +test "roundtrip u256 encoding" { const allocator = std.testing.allocator; - const original = U256.fromInt(0x123456); + const original: u256 = 0x123456; const encoded = try EthereumEncoder.encodeU256(allocator, original); defer allocator.free(encoded); @@ -322,5 +323,5 @@ test "roundtrip U256 encoding" { const bytes = try rlp_value.getBytes(); const decoded = try EthereumDecoder.decodeU256(bytes); - try std.testing.expect(decoded.eql(original)); + try std.testing.expectEqual(original, decoded); } diff --git a/src/root.zig b/src/root.zig index 59ce990..60d75bd 100644 --- a/src/root.zig +++ b/src/root.zig @@ -12,15 +12,9 @@ pub const primitives = struct { pub const Bytes = @import("primitives/bytes.zig").Bytes; pub const Signature = @import("primitives/signature.zig").Signature; - /// Legacy U256 wrapper - DEPRECATED: Use native `u256` type instead - /// For Ethereum-specific conversions, use the utility functions: - /// - u256FromBytes() - Convert from big-endian bytes - /// - u256ToBytes() - Convert to big-endian bytes - /// - u256FromHex() - Parse from hex string - /// - u256ToHex() - Format as hex string - pub const U256 = @import("primitives/uint.zig").U256; - // u256 Ethereum utility functions + // Use native `u256` type for values + // Use these functions for Ethereum-specific conversions: pub const u256FromBytes = @import("primitives/uint.zig").u256FromBytes; pub const u256ToBytes = @import("primitives/uint.zig").u256ToBytes; pub const u256FromHex = @import("primitives/uint.zig").u256FromHex; @@ -288,4 +282,81 @@ pub const account_abstraction = struct { test { std.testing.refAllDecls(@This()); + + // Primitives + _ = @import("primitives/address.zig"); + _ = @import("primitives/hash.zig"); + _ = @import("primitives/bytes.zig"); + _ = @import("primitives/signature.zig"); + _ = @import("primitives/uint.zig"); + _ = @import("primitives/bloom.zig"); + + // Types + _ = @import("types/transaction.zig"); + _ = @import("types/block.zig"); + _ = @import("types/receipt.zig"); + _ = @import("types/log.zig"); + _ = @import("types/access_list.zig"); + + // Crypto + _ = @import("crypto/keccak.zig"); + _ = @import("crypto/secp256k1.zig"); + _ = @import("crypto/ecdsa.zig"); + _ = @import("crypto/utils.zig"); + + // ABI + _ = @import("abi/types.zig"); + _ = @import("abi/encode.zig"); + _ = @import("abi/decode.zig"); + _ = @import("abi/packed.zig"); + + // RLP + _ = @import("rlp/encode.zig"); + _ = @import("rlp/decode.zig"); + _ = @import("rlp/packed.zig"); + + // Providers + _ = @import("providers/provider.zig"); + _ = @import("providers/http.zig"); + _ = @import("providers/ws.zig"); + _ = @import("providers/ipc.zig"); + _ = @import("providers/mock.zig"); + + // RPC + _ = @import("rpc/client.zig"); + _ = @import("rpc/types.zig"); + _ = @import("rpc/eth.zig"); + _ = @import("rpc/net.zig"); + _ = @import("rpc/web3.zig"); + _ = @import("rpc/debug.zig"); + + // Contract + _ = @import("contract/contract.zig"); + _ = @import("contract/call.zig"); + _ = @import("contract/deploy.zig"); + _ = @import("contract/event.zig"); + + // Sol + _ = @import("sol/types.zig"); + _ = @import("sol/macros.zig"); + + // Signer + _ = @import("signer/signer.zig"); + _ = @import("signer/wallet.zig"); + _ = @import("signer/keystore.zig"); + _ = @import("signer/ledger.zig"); + + // Utils + _ = @import("utils/hex.zig"); + _ = @import("utils/format.zig"); + _ = @import("utils/units.zig"); + _ = @import("utils/checksum.zig"); + + // Middleware + _ = @import("middleware/gas.zig"); + _ = @import("middleware/nonce.zig"); + _ = @import("middleware/signer.zig"); + + // Errors + _ = @import("errors.zig"); } diff --git a/src/rpc/client.zig b/src/rpc/client.zig index cac4e4e..1b39c09 100644 --- a/src/rpc/client.zig +++ b/src/rpc/client.zig @@ -39,10 +39,8 @@ pub const RpcClient = struct { const request = try types.JsonRpcRequest.init(self.allocator, method, params, id); // Serialize request to JSON - var request_str = std.ArrayList(u8).init(self.allocator); - defer request_str.deinit(); - - try std.json.stringify(request, .{}, request_str.writer()); + const request_str = try std.json.Stringify.valueAlloc(self.allocator, request, .{}); + defer self.allocator.free(request_str); // Make HTTP request var transport = try HttpTransport.init(self.allocator, self.endpoint); @@ -50,7 +48,7 @@ pub const RpcClient = struct { try transport.addHeader("Content-Type", "application/json"); - const response_body = try transport.send(request_str.items); + const response_body = try transport.send(request_str); defer self.allocator.free(response_body); // Parse response @@ -102,9 +100,10 @@ pub const RpcClient = struct { } }; -/// HTTP transport for RPC client +/// HTTP transport for RPC client (Zig 0.15.x compatible) pub const HttpTransport = struct { allocator: std.mem.Allocator, + client: std.http.Client, url: []const u8, headers: std.StringHashMap([]const u8), @@ -112,12 +111,14 @@ pub const HttpTransport = struct { const url_copy = try allocator.dupe(u8, url); return .{ .allocator = allocator, + .client = std.http.Client{ .allocator = allocator }, .url = url_copy, .headers = std.StringHashMap([]const u8).init(allocator), }; } pub fn deinit(self: *HttpTransport) void { + self.client.deinit(); self.allocator.free(self.url); var it = self.headers.iterator(); @@ -135,58 +136,49 @@ pub const HttpTransport = struct { } pub fn send(self: *HttpTransport, request: []const u8) ![]u8 { - // Parse URL - const uri = try std.Uri.parse(self.url); - - // Create HTTP client - var client = std.http.Client{ .allocator = self.allocator }; - defer client.deinit(); + // Use arena allocator for temporary allocations + var arena = std.heap.ArenaAllocator.init(self.allocator); + defer arena.deinit(); + const alloc = arena.allocator(); // Build extra headers array - var extra_headers_list = std.ArrayList(std.http.Header).init(self.allocator); - defer extra_headers_list.deinit(); + var header_list = try std.ArrayList(std.http.Header).initCapacity(alloc, 0); + defer header_list.deinit(alloc); var it = self.headers.iterator(); while (it.next()) |entry| { - try extra_headers_list.append(.{ + try header_list.append(alloc, .{ .name = entry.key_ptr.*, .value = entry.value_ptr.*, }); } - // Prepare headers - var header_buffer: [4096]u8 = undefined; - var req = try client.open(.POST, uri, .{ - .server_header_buffer = &header_buffer, - .extra_headers = extra_headers_list.items, + // Create response writer + var body_writer = std.io.Writer.Allocating.init(alloc); + defer body_writer.deinit(); + + // Make HTTP request using fetch API (Zig 0.15.x) + const fetch_result = try self.client.fetch(.{ + .location = .{ .url = self.url }, + .method = .POST, + .payload = request, + .extra_headers = header_list.items, + .response_writer = &body_writer.writer, + .keep_alive = false, }); - defer req.deinit(); - - // Set transfer encoding - req.transfer_encoding = .chunked; - - try req.send(); - - // Write body - try req.writeAll(request); - try req.finish(); - - // Wait for response - try req.wait(); // Check status - if (req.response.status != .ok) { + const status = @intFromEnum(fetch_result.status); + if (status < 200 or status >= 300) { return error.HttpRequestFailed; } - // Read response body - var response_body = std.ArrayList(u8).init(self.allocator); - defer response_body.deinit(); - - const max_size = 10 * 1024 * 1024; // 10 MB max - try req.reader().readAllArrayList(&response_body, max_size); + // Copy response body to caller's allocator + const written = body_writer.written(); + const response_bytes = try self.allocator.alloc(u8, written.len); + @memcpy(response_bytes, written); - return try response_body.toOwnedSlice(); + return response_bytes; } }; diff --git a/src/rpc/debug.zig b/src/rpc/debug.zig index 70e7361..fbdac0e 100644 --- a/src/rpc/debug.zig +++ b/src/rpc/debug.zig @@ -3,7 +3,7 @@ const RpcClient = @import("./client.zig").RpcClient; const types = @import("./types.zig"); const Hash = @import("../primitives/hash.zig").Hash; const Address = @import("../primitives/address.zig").Address; -const U256 = @import("../primitives/uint.zig").U256; +const u256FromHex = @import("../primitives/uint.zig").u256FromHex; /// Debug namespace (debug_*) methods /// These methods are typically only available on development nodes @@ -233,7 +233,7 @@ pub const StructLog = struct { gas: u64, gas_cost: u64, depth: u64, - stack: ?[]U256 = null, + stack: ?[]u256 = null, memory: ?[]const u8 = null, storage: ?std.StringHashMap(Hash) = null, allocator: std.mem.Allocator, @@ -281,7 +281,7 @@ const JsonObjectWrapper = struct { value: std.json.Value, allocator: std.mem.Allocator, - fn deinit(self: JsonObjectWrapper) void { + fn deinit(self: *JsonObjectWrapper) void { if (self.value == .object) { self.value.object.deinit(); } @@ -404,7 +404,7 @@ fn parseTraceResult(allocator: std.mem.Allocator, json: std.json.Value) !TraceRe } // Parse struct logs - var struct_logs = std.ArrayList(StructLog).init(allocator); + var struct_logs = try std.ArrayList(StructLog).initCapacity(allocator, 0); defer struct_logs.deinit(); if (obj.get("structLogs")) |logs_val| { @@ -421,7 +421,7 @@ fn parseTraceResult(allocator: std.mem.Allocator, json: std.json.Value) !TraceRe return TraceResult{ .gas = gas, .return_value = return_value, - .struct_logs = try struct_logs.toOwnedSlice(), + .struct_logs = try struct_logs.toOwnedSlice(allocator), .allocator = allocator, }; } @@ -432,15 +432,15 @@ fn parseTraceResults(allocator: std.mem.Allocator, json: std.json.Value) ![]Trac return error.InvalidResponse; } - var results = std.ArrayList(TraceResult).init(allocator); - defer results.deinit(); + var results = try std.ArrayList(TraceResult).initCapacity(allocator, 0); + defer results.deinit(allocator); for (json.array.items) |item| { const trace = try parseTraceResult(allocator, item); - try results.append(trace); + try results.append(allocator, trace); } - return try results.toOwnedSlice(); + return try results.toOwnedSlice(allocator); } /// Parse StructLog from JSON @@ -476,20 +476,20 @@ fn parseStructLog(allocator: std.mem.Allocator, obj: std.json.ObjectMap) !Struct return error.InvalidFieldType; // Optional fields - var stack: ?[]U256 = null; + var stack: ?[]u256 = null; if (obj.get("stack")) |stack_val| { if (stack_val == .array) { - var stack_items = std.ArrayList(U256).init(allocator); - defer stack_items.deinit(); + var stack_items = try std.ArrayList(u256).initCapacity(allocator, 0); + defer stack_items.deinit(allocator); for (stack_val.array.items) |item| { if (item == .string) { - const value = try U256.fromHex(item.string); - try stack_items.append(value); + const value = try u256FromHex(item.string); + try stack_items.append(allocator, value); } } - stack = try stack_items.toOwnedSlice(); + stack = try stack_items.toOwnedSlice(allocator); } } @@ -497,19 +497,19 @@ fn parseStructLog(allocator: std.mem.Allocator, obj: std.json.ObjectMap) !Struct if (obj.get("memory")) |mem_val| { if (mem_val == .array) { // Memory is returned as array of hex strings - var mem_data = std.ArrayList(u8).init(allocator); - defer mem_data.deinit(); + var mem_data = try std.ArrayList(u8).initCapacity(allocator, 0); + defer mem_data.deinit(allocator); for (mem_val.array.items) |item| { if (item == .string) { const hex_module = @import("../utils/hex.zig"); const bytes = try hex_module.hexToBytes(allocator, item.string); defer allocator.free(bytes); - try mem_data.appendSlice(bytes); + try mem_data.appendSlice(allocator, bytes); } } - memory = try mem_data.toOwnedSlice(); + memory = try mem_data.toOwnedSlice(allocator); } } @@ -568,18 +568,18 @@ fn parseStorageRange(allocator: std.mem.Allocator, obj: std.json.ObjectMap) !Sto /// Parse address array from JSON fn parseAddressArray(allocator: std.mem.Allocator, array: std.json.Array) ![]Address { - var addresses = std.ArrayList(Address).init(allocator); - defer addresses.deinit(); + var addresses = try std.ArrayList(Address).initCapacity(allocator, 0); + defer addresses.deinit(allocator); for (array.items) |item| { if (item != .string) { return error.InvalidFieldType; } const addr = try Address.fromHex(item.string); - try addresses.append(addr); + try addresses.append(allocator, addr); } - return try addresses.toOwnedSlice(); + return try addresses.toOwnedSlice(allocator); } test "debug namespace creation" { @@ -607,7 +607,7 @@ test "trace options to json" { .timeout = "5s", }; - const json_obj = try traceOptionsToJson(allocator, options); + var json_obj = try traceOptionsToJson(allocator, options); defer json_obj.deinit(); try std.testing.expect(json_obj.value == .object); diff --git a/src/rpc/eth.zig b/src/rpc/eth.zig index 407d008..df9d5a1 100644 --- a/src/rpc/eth.zig +++ b/src/rpc/eth.zig @@ -332,7 +332,7 @@ pub const EthNamespace = struct { return error.InvalidResponse; } - var logs = std.ArrayList(Log).init(self.client.allocator); + var logs = try std.ArrayList(Log).initCapacity(self.client.allocator, 0); errdefer { for (logs.items) |log| { log.deinit(); @@ -346,10 +346,10 @@ pub const EthNamespace = struct { } const log = try parseLogFromJson(self.client.allocator, log_json.object); - try logs.append(log); + try logs.append(self.client.allocator, log); } - return try logs.toOwnedSlice(); + return try logs.toOwnedSlice(self.client.allocator); } /// eth_sendRawTransaction - Sends a signed transaction @@ -498,18 +498,18 @@ pub const EthNamespace = struct { return error.InvalidResponse; } - var addresses = std.ArrayList(Address).init(self.client.allocator); - errdefer addresses.deinit(); + var addresses = try std.ArrayList(Address).initCapacity(self.client.allocator, 0); + errdefer addresses.deinit(self.client.allocator); for (result.array.items) |item| { if (item != .string) { return error.InvalidResponse; } const addr = try Address.fromHex(item.string); - try addresses.append(addr); + try addresses.append(self.client.allocator, addr); } - return try addresses.toOwnedSlice(); + return try addresses.toOwnedSlice(self.client.allocator); } /// eth_sign - Signs data with an address @@ -583,8 +583,15 @@ const JsonObjectWrapper = struct { value: std.json.Value, allocator: std.mem.Allocator, - fn deinit(self: JsonObjectWrapper) void { + fn deinit(self: *JsonObjectWrapper) void { if (self.value == .object) { + // Free all string values that were allocated + var iter = self.value.object.iterator(); + while (iter.next()) |entry| { + if (entry.value_ptr.* == .string) { + self.allocator.free(entry.value_ptr.string); + } + } self.value.object.deinit(); } } @@ -613,7 +620,7 @@ fn callParamsToJson(allocator: std.mem.Allocator, params: types.CallParams) !Jso } if (params.value) |value| { - const value_hex = try value.toHex(allocator); + const value_hex = try uint_utils.u256ToHex(value, allocator); try obj.put("value", .{ .string = value_hex }); } @@ -623,7 +630,7 @@ fn callParamsToJson(allocator: std.mem.Allocator, params: types.CallParams) !Jso } if (params.gas_price) |gas_price| { - const gp_hex = try gas_price.toHex(allocator); + const gp_hex = try uint_utils.u256ToHex(gas_price, allocator); try obj.put("gasPrice", .{ .string = gp_hex }); } @@ -652,7 +659,7 @@ fn transactionParamsToJson(allocator: std.mem.Allocator, params: types.Transacti } if (params.value) |value| { - const value_hex = try value.toHex(allocator); + const value_hex = try uint_utils.u256ToHex(value, allocator); try obj.put("value", .{ .string = value_hex }); } @@ -662,7 +669,7 @@ fn transactionParamsToJson(allocator: std.mem.Allocator, params: types.Transacti } if (params.gas_price) |gas_price| { - const gp_hex = try gas_price.toHex(allocator); + const gp_hex = try uint_utils.u256ToHex(gas_price, allocator); try obj.put("gasPrice", .{ .string = gp_hex }); } @@ -739,13 +746,13 @@ fn parseLogFromJson(allocator: std.mem.Allocator, obj: std.json.ObjectMap) !Log const topics_json = obj.get("topics") orelse return error.MissingField; if (topics_json != .array) return error.InvalidFieldType; - var topics = std.ArrayList(Hash).init(allocator); - defer topics.deinit(); + var topics = try std.ArrayList(Hash).initCapacity(allocator, 0); + defer topics.deinit(allocator); for (topics_json.array.items) |topic_val| { if (topic_val != .string) return error.InvalidFieldType; const topic = try Hash.fromHex(topic_val.string); - try topics.append(topic); + try topics.append(allocator, topic); } // Create log with required fields @@ -859,6 +866,32 @@ fn parseTransactionFromJson(allocator: std.mem.Allocator, obj: std.json.ObjectMa } } + // Parse EIP-4844 specific fields + var max_fee_per_blob_gas: ?u256 = null; + var blob_versioned_hashes: ?[]Hash = null; + + if (tx_type == .eip4844) { + if (obj.get("maxFeePerBlobGas")) |blob_fee| { + if (blob_fee == .string) { + max_fee_per_blob_gas = try uint_utils.u256FromHex(blob_fee.string); + } + } + if (obj.get("blobVersionedHashes")) |hashes_val| { + if (hashes_val == .array) { + const hashes_array = hashes_val.array; + if (hashes_array.items.len > 0) { + var hashes = try allocator.alloc(Hash, hashes_array.items.len); + for (hashes_array.items, 0..) |hash_val, i| { + if (hash_val == .string) { + hashes[i] = try Hash.fromHex(hash_val.string); + } + } + blob_versioned_hashes = hashes; + } + } + } + } + // Create base transaction var tx = Transaction{ .type = tx_type, @@ -874,6 +907,8 @@ fn parseTransactionFromJson(allocator: std.mem.Allocator, obj: std.json.ObjectMa .chain_id = null, .access_list = null, .authorization_list = null, + .max_fee_per_blob_gas = max_fee_per_blob_gas, + .blob_versioned_hashes = blob_versioned_hashes, .signature = null, .hash = null, .block_hash = null, @@ -986,13 +1021,13 @@ fn parseReceiptFromJson(allocator: std.mem.Allocator, obj: std.json.ObjectMap) ! const logs_json = obj.get("logs") orelse return error.MissingField; if (logs_json != .array) return error.InvalidFieldType; - var logs = std.ArrayList(Log).init(allocator); - defer logs.deinit(); + var logs = try std.ArrayList(Log).initCapacity(allocator, 0); + defer logs.deinit(allocator); for (logs_json.array.items) |log_json| { if (log_json != .object) return error.InvalidFieldType; const log = try parseLogFromJson(allocator, log_json.object); - try logs.append(log); + try logs.append(allocator, log); } const logs_bloom_str = obj.get("logsBloom") orelse return error.MissingField; @@ -1039,7 +1074,7 @@ fn parseReceiptFromJson(allocator: std.mem.Allocator, obj: std.json.ObjectMap) ! .gas_used = gas_used, .effective_gas_price = effective_gas_price, .contract_address = contract_address, - .logs = try logs.toOwnedSlice(), + .logs = try logs.toOwnedSlice(allocator), .logs_bloom = logs_bloom, .transaction_type = transaction_type, .status = status, @@ -1186,15 +1221,15 @@ fn parseBlockFromJson(allocator: std.mem.Allocator, obj: std.json.ObjectMap, ful const transactions_json = obj.get("transactions") orelse return error.MissingField; if (transactions_json != .array) return error.InvalidFieldType; - var transactions = std.ArrayList(Transaction).init(allocator); - defer transactions.deinit(); + var transactions = try std.ArrayList(Transaction).initCapacity(allocator, 0); + defer transactions.deinit(allocator); if (full_tx) { // Full transaction objects for (transactions_json.array.items) |tx_json| { if (tx_json != .object) return error.InvalidFieldType; const tx = try parseTransactionFromJson(allocator, tx_json.object); - try transactions.append(tx); + try transactions.append(allocator, tx); } } else { // Just transaction hashes - create minimal transaction structs @@ -1218,6 +1253,8 @@ fn parseBlockFromJson(allocator: std.mem.Allocator, obj: std.json.ObjectMap, ful .chain_id = null, .access_list = null, .authorization_list = null, + .max_fee_per_blob_gas = null, + .blob_versioned_hashes = null, .signature = null, .hash = tx_hash, .block_hash = null, @@ -1225,7 +1262,7 @@ fn parseBlockFromJson(allocator: std.mem.Allocator, obj: std.json.ObjectMap, ful .transaction_index = null, .allocator = allocator, }; - try transactions.append(tx); + try transactions.append(allocator, tx); } } @@ -1236,7 +1273,7 @@ fn parseBlockFromJson(allocator: std.mem.Allocator, obj: std.json.ObjectMap, ful return Block{ .hash = hash, .header = header, - .transactions = try transactions.toOwnedSlice(), + .transactions = try transactions.toOwnedSlice(allocator), .uncles = &[_]Hash{}, // TODO: Parse uncles from JSON .total_difficulty = 0, // TODO: Parse total difficulty .size = 0, // TODO: Parse size from JSON @@ -1257,7 +1294,7 @@ test "eth namespace creation" { test "block parameter to string" { const allocator = std.testing.allocator; - const latest = try blockParameterToString(allocator, .latest); + const latest = try blockParameterToString(allocator, .{ .tag = .latest }); defer allocator.free(latest); try std.testing.expectEqualStrings("latest", latest); @@ -1290,7 +1327,7 @@ test "call params to json" { .gas_price = null, }; - const json_obj = try callParamsToJson(allocator, params); + var json_obj = try callParamsToJson(allocator, params); defer json_obj.deinit(); try std.testing.expect(json_obj.value == .object); diff --git a/src/signer/keystore.zig b/src/signer/keystore.zig index f9f198f..085d8ad 100644 --- a/src/signer/keystore.zig +++ b/src/signer/keystore.zig @@ -380,7 +380,7 @@ pub const Keystore = struct { var salt: [32]u8 = undefined; @memcpy(&salt, salt_bytes); - const kdfparams = switch (kdf) { + const kdfparams: KdfParams = switch (kdf) { .scrypt => blk: { var params = ScryptParams.default(); params.salt = salt; @@ -510,11 +510,12 @@ fn decryptAES128CTR(allocator: std.mem.Allocator, ciphertext: []const u8, key: [ /// Calculate MAC for verification fn calculateMAC(key: []const u8, ciphertext: []const u8) ![32]u8 { // MAC = keccak256(derived_key[16:32] + ciphertext) - var data = std.ArrayList(u8).init(std.heap.page_allocator); - defer data.deinit(); + const allocator = std.heap.page_allocator; + var data = try std.ArrayList(u8).initCapacity(allocator, 0); + defer data.deinit(allocator); - try data.appendSlice(key); - try data.appendSlice(ciphertext); + try data.appendSlice(allocator, key); + try data.appendSlice(allocator, ciphertext); return keccak.hash(data.items).bytes; } @@ -530,10 +531,10 @@ test "keystore encrypt and decrypt" { defer keystore.deinit(); const decrypted_key = try keystore.decrypt(password); - const orig_u256 = try private_key.toU256(); - const decrypted_u256 = try decrypted_key.toU256(); + const orig_u256 = private_key.toU256(); + const decrypted_u256 = decrypted_key.toU256(); - try std.testing.expect(orig_u256.eql(decrypted_u256)); + try std.testing.expectEqual(orig_u256, decrypted_u256); } test "keystore wrong password" { @@ -618,9 +619,9 @@ test "keystore json round trip" { const decrypted_key = try imported.decrypt(password); // Verify keys match - const orig_u256 = try private_key.toU256(); - const decrypted_u256 = try decrypted_key.toU256(); - try std.testing.expect(orig_u256.eql(decrypted_u256)); + const orig_u256 = private_key.toU256(); + const decrypted_u256 = decrypted_key.toU256(); + try std.testing.expectEqual(orig_u256, decrypted_u256); } test "keystore aes encryption" { diff --git a/src/signer/ledger.zig b/src/signer/ledger.zig index d1a6bd8..7a4502b 100644 --- a/src/signer/ledger.zig +++ b/src/signer/ledger.zig @@ -194,8 +194,8 @@ pub const LedgerWallet = struct { _ = hash; return Signature.init( - @import("../primitives/uint.zig").U256.zero(), - @import("../primitives/uint.zig").U256.zero(), + [_]u8{0} ** 32, + [_]u8{0} ** 32, 0, ); } @@ -212,8 +212,8 @@ pub const LedgerWallet = struct { _ = chain_id; return Signature.init( - @import("../primitives/uint.zig").U256.zero(), - @import("../primitives/uint.zig").U256.zero(), + [_]u8{0} ** 32, + [_]u8{0} ** 32, 0, ); } @@ -229,8 +229,8 @@ pub const LedgerWallet = struct { _ = message; return Signature.init( - @import("../primitives/uint.zig").U256.zero(), - @import("../primitives/uint.zig").U256.zero(), + [_]u8{0} ** 32, + [_]u8{0} ** 32, 0, ); } @@ -250,8 +250,8 @@ pub const LedgerWallet = struct { _ = message_hash; return Signature.init( - @import("../primitives/uint.zig").U256.zero(), - @import("../primitives/uint.zig").U256.zero(), + [_]u8{0} ** 32, + [_]u8{0} ** 32, 0, ); } diff --git a/src/signer/wallet.zig b/src/signer/wallet.zig index c7ab4d8..0c80343 100644 --- a/src/signer/wallet.zig +++ b/src/signer/wallet.zig @@ -2,7 +2,7 @@ const std = @import("std"); const Address = @import("../primitives/address.zig").Address; const Hash = @import("../primitives/hash.zig").Hash; const Signature = @import("../primitives/signature.zig").Signature; -const U256 = @import("../primitives/uint.zig").U256; +const u256ToU64 = @import("../primitives/uint.zig").u256ToU64; const Transaction = @import("../types/transaction.zig").Transaction; const PrivateKey = @import("../crypto/secp256k1.zig").PrivateKey; const PublicKey = @import("../crypto/secp256k1.zig").PublicKey; @@ -117,12 +117,12 @@ pub const Wallet = struct { var encoder = RlpEncoder.init(self.allocator); defer encoder.deinit(); - switch (tx.transaction_type) { + switch (tx.type) { .legacy => { // Legacy transaction with EIP-155 try encoder.startList(); try encoder.appendItem(.{ .uint = tx.nonce }); - try encoder.appendItem(.{ .uint = tx.gas_price.toU64() catch 0 }); + try encoder.appendItem(.{ .uint = u256ToU64(tx.gas_price orelse 0) catch 0 }); try encoder.appendItem(.{ .uint = tx.gas_limit }); if (tx.to) |to_addr| { @@ -131,8 +131,8 @@ pub const Wallet = struct { try encoder.appendItem(.{ .bytes = &[_]u8{} }); } - try encoder.appendItem(.{ .uint = tx.value.toU64() catch 0 }); - try encoder.appendItem(.{ .bytes = tx.data }); + try encoder.appendItem(.{ .uint = u256ToU64(tx.value) catch 0 }); + try encoder.appendItem(.{ .bytes = tx.data.data }); // EIP-155: add chain_id, 0, 0 try encoder.appendItem(.{ .uint = chain_id }); @@ -150,13 +150,13 @@ pub const Wallet = struct { try encoder.appendItem(.{ .uint = chain_id }); try encoder.appendItem(.{ .uint = tx.nonce }); - switch (tx.transaction_type) { + switch (tx.type) { .eip2930 => { - try encoder.appendItem(.{ .uint = tx.gas_price.toU64() catch 0 }); + try encoder.appendItem(.{ .uint = u256ToU64(tx.gas_price orelse 0) catch 0 }); }, .eip1559, .eip4844, .eip7702 => { - try encoder.appendItem(.{ .uint = tx.max_priority_fee_per_gas.toU64() catch 0 }); - try encoder.appendItem(.{ .uint = tx.max_fee_per_gas.toU64() catch 0 }); + try encoder.appendItem(.{ .uint = u256ToU64(tx.max_priority_fee_per_gas orelse 0) catch 0 }); + try encoder.appendItem(.{ .uint = u256ToU64(tx.max_fee_per_gas orelse 0) catch 0 }); }, else => unreachable, } @@ -169,20 +169,31 @@ pub const Wallet = struct { try encoder.appendItem(.{ .bytes = &[_]u8{} }); } - try encoder.appendItem(.{ .uint = tx.value.toU64() catch 0 }); - try encoder.appendItem(.{ .bytes = tx.data }); + try encoder.appendItem(.{ .uint = u256ToU64(tx.value) catch 0 }); + try encoder.appendItem(.{ .bytes = tx.data.data }); // Access list (empty for now) try encoder.appendItem(.{ .list = &[_]RlpItem{} }); // EIP-4844 specific - if (tx.transaction_type == .eip4844) { - try encoder.appendItem(.{ .uint = tx.max_fee_per_blob_gas.toU64() catch 0 }); - try encoder.appendItem(.{ .list = &[_]RlpItem{} }); // blob versioned hashes + if (tx.type == .eip4844) { + const blob_fee = u256ToU64(tx.max_fee_per_blob_gas orelse 0) catch 0; + try encoder.appendItem(.{ .uint = blob_fee }); + // Encode blob versioned hashes as list of bytes32 + if (tx.blob_versioned_hashes) |hashes| { + var hash_items = try self.allocator.alloc(RlpItem, hashes.len); + defer self.allocator.free(hash_items); + for (hashes, 0..) |h, i| { + hash_items[i] = .{ .bytes = &h.bytes }; + } + try encoder.appendItem(.{ .list = hash_items }); + } else { + try encoder.appendItem(.{ .list = &[_]RlpItem{} }); + } } // EIP-7702 specific - if (tx.transaction_type == .eip7702) { + if (tx.type == .eip7702) { try encoder.appendItem(.{ .list = &[_]RlpItem{} }); // authorization list } @@ -190,7 +201,7 @@ pub const Wallet = struct { defer self.allocator.free(encoded); // Prepend transaction type - const tx_type: u8 = switch (tx.transaction_type) { + const tx_type: u8 = switch (tx.type) { .eip2930 => 0x01, .eip1559 => 0x02, .eip4844 => 0x03, @@ -210,7 +221,7 @@ pub const Wallet = struct { /// Sign a message hash pub fn signHash(self: *Wallet, hash: [32]u8) !Signature { - return try self.signer.signHash(hash); + return try self.signer.signHash(Hash.fromBytes(hash)); } /// Sign a message (with Ethereum prefix) @@ -228,14 +239,14 @@ pub const Wallet = struct { @memcpy(data[34..66], &message_hash); const hash = keccak.hash(&data); - return try self.signer.signHash(hash.bytes); + return try self.signer.signHash(hash); } /// Verify a signature pub fn verifySignature(self: *Wallet, hash: [32]u8, signature: Signature) !bool { const ecdsa = @import("../crypto/ecdsa.zig"); - const recovered_addr = try ecdsa.recoverAddress(hash, signature); - return recovered_addr.eql(self.address); + const recovered_addr = try ecdsa.recoverAddress(Hash.fromBytes(hash), signature); + return std.mem.eql(u8, &recovered_addr.bytes, &self.address.bytes); } /// Get signer interface @@ -320,19 +331,19 @@ pub const Mnemonic = struct { /// Create mnemonic from phrase pub fn fromPhrase(allocator: std.mem.Allocator, phrase: []const u8) !Mnemonic { // Split by spaces - var words = std.ArrayList([]const u8).init(allocator); - defer words.deinit(); + var words = try std.ArrayList([]const u8).initCapacity(allocator, 0); + defer words.deinit(allocator); var iter = std.mem.splitScalar(u8, phrase, ' '); while (iter.next()) |word| { if (word.len > 0) { const word_copy = try allocator.dupe(u8, word); - try words.append(word_copy); + try words.append(allocator, word_copy); } } return .{ - .words = try words.toOwnedSlice(), + .words = try words.toOwnedSlice(allocator), .allocator = allocator, }; } @@ -344,10 +355,10 @@ pub const Mnemonic = struct { const phrase = try self.toPhrase(); defer self.allocator.free(phrase); - var salt = std.ArrayList(u8).init(self.allocator); - defer salt.deinit(); - try salt.appendSlice("mnemonic"); - try salt.appendSlice(passphrase); + var salt = try std.ArrayList(u8).initCapacity(self.allocator, 0); + defer salt.deinit(self.allocator); + try salt.appendSlice(self.allocator, "mnemonic"); + try salt.appendSlice(self.allocator, passphrase); // Derive 64-byte seed using PBKDF2-HMAC-SHA512 var seed: [64]u8 = undefined; @@ -364,15 +375,15 @@ pub const Mnemonic = struct { /// Get phrase as string pub fn toPhrase(self: Mnemonic) ![]u8 { - var phrase = std.ArrayList(u8).init(self.allocator); - defer phrase.deinit(); + var phrase = try std.ArrayList(u8).initCapacity(self.allocator, 0); + defer phrase.deinit(self.allocator); for (self.words, 0..) |word, i| { - if (i > 0) try phrase.append(' '); - try phrase.appendSlice(word); + if (i > 0) try phrase.append(self.allocator, ' '); + try phrase.appendSlice(self.allocator, word); } - return phrase.toOwnedSlice(); + return phrase.toOwnedSlice(self.allocator); } /// Free memory diff --git a/src/sol/macros.zig b/src/sol/macros.zig index 311a5b3..0f55f1b 100644 --- a/src/sol/macros.zig +++ b/src/sol/macros.zig @@ -178,15 +178,15 @@ pub const ValueConversion = struct { const type_info = @typeInfo(T); return switch (type_info) { - .Int => |int_info| { + .int => |int_info| { if (int_info.signedness == .unsigned) { - return .{ .uint = @import("../primitives/uint.zig").U256.fromInt(@as(u64, @intCast(value))) }; + return .{ .uint = @as(u256, @intCast(value)) }; } else { - return .{ .int = @import("../primitives/uint.zig").U256.fromInt(@as(u64, @intCast(@abs(value)))) }; + return .{ .int = @as(i256, @intCast(value)) }; } }, - .Bool => .{ .bool_val = value }, - .Pointer => |ptr_info| { + .bool => .{ .bool_val = value }, + .pointer => |ptr_info| { if (ptr_info.child == u8) { // String or bytes return .{ .bytes = value }; @@ -203,8 +203,8 @@ pub const ValueConversion = struct { return .{ .address = addr }; } - /// Convert U256 to AbiValue - pub fn u256ToAbiValue(value: @import("../primitives/uint.zig").U256) abi.AbiValue { + /// Convert u256 to AbiValue + pub fn u256ToAbiValue(value: u256) abi.AbiValue { return .{ .uint = value }; } }; @@ -254,8 +254,7 @@ test "value conversion uint" { const abi_val = ValueConversion.toAbiValue(u64, value); try std.testing.expect(abi_val == .uint); - const U256 = @import("../primitives/uint.zig").U256; - try std.testing.expect(abi_val.uint.eql(U256.fromInt(1000))); + try std.testing.expect(abi_val.uint == 1000); } test "value conversion bool" { diff --git a/src/sol/types.zig b/src/sol/types.zig index 93da60f..d97beff 100644 --- a/src/sol/types.zig +++ b/src/sol/types.zig @@ -2,7 +2,6 @@ const std = @import("std"); const abi = @import("../abi/types.zig"); const Address = @import("../primitives/address.zig").Address; const Hash = @import("../primitives/hash.zig").Hash; -const U256 = @import("../primitives/uint.zig").U256; const Bytes = @import("../primitives/bytes.zig").Bytes; /// Solidity type to Zig type mappings @@ -49,11 +48,11 @@ pub const SolidityType = enum { .int64 => .int64, .int128 => .int128, .int256 => .int256, - .bytes1 => .{ .fixed_bytes = 1 }, - .bytes2 => .{ .fixed_bytes = 2 }, - .bytes4 => .{ .fixed_bytes = 4 }, - .bytes8 => .{ .fixed_bytes = 8 }, - .bytes16 => .{ .fixed_bytes = 16 }, + .bytes1 => .bytes1, + .bytes2 => .bytes2, + .bytes4 => .bytes4, + .bytes8 => .bytes8, + .bytes16 => .bytes16, .bytes32 => .bytes32, }; } @@ -192,13 +191,13 @@ pub const SolidityValue = union(enum) { uint32: u32, uint64: u64, uint128: u128, - uint256: U256, + uint256: u256, int8: i8, int16: i16, int32: i32, int64: i64, int128: i128, - int256: U256, // Signed represented as U256 for now + int256: i256, fixed_bytes: []const u8, /// Convert to AbiValue @@ -208,17 +207,17 @@ pub const SolidityValue = union(enum) { .bool_val => |b| .{ .bool_val = b }, .string => |s| .{ .string = s }, .bytes => |b| .{ .bytes = b }, - .uint8 => |u| .{ .uint = U256.fromInt(u) }, - .uint16 => |u| .{ .uint = U256.fromInt(u) }, - .uint32 => |u| .{ .uint = U256.fromInt(u) }, - .uint64 => |u| .{ .uint = U256.fromInt(u) }, - .uint128 => |u| .{ .uint = U256.fromInt(u) }, + .uint8 => |u| .{ .uint = @as(u256, u) }, + .uint16 => |u| .{ .uint = @as(u256, u) }, + .uint32 => |u| .{ .uint = @as(u256, u) }, + .uint64 => |u| .{ .uint = @as(u256, u) }, + .uint128 => |u| .{ .uint = @as(u256, u) }, .uint256 => |u| .{ .uint = u }, - .int8 => |i| .{ .int = U256.fromInt(@as(u64, @intCast(@abs(i)))) }, - .int16 => |i| .{ .int = U256.fromInt(@as(u64, @intCast(@abs(i)))) }, - .int32 => |i| .{ .int = U256.fromInt(@as(u64, @intCast(@abs(i)))) }, - .int64 => |i| .{ .int = U256.fromInt(@as(u64, @intCast(@abs(i)))) }, - .int128 => |i| .{ .int = U256.fromInt(@as(u64, @intCast(@abs(i)))) }, + .int8 => |i| .{ .int = @as(i256, i) }, + .int16 => |i| .{ .int = @as(i256, i) }, + .int32 => |i| .{ .int = @as(i256, i) }, + .int64 => |i| .{ .int = @as(i256, i) }, + .int128 => |i| .{ .int = @as(i256, i) }, .int256 => |i| .{ .int = i }, .fixed_bytes => |b| .{ .bytes = b }, }; @@ -726,9 +725,9 @@ test "erc721 interface" { } test "solidity value to abi value" { - const sol_val = SolidityValue{ .uint256 = U256.fromInt(1000) }; + const sol_val = SolidityValue{ .uint256 = 1000 }; const abi_val = sol_val.toAbiValue(); try std.testing.expect(abi_val == .uint); - try std.testing.expect(abi_val.uint.eql(U256.fromInt(1000))); + try std.testing.expect(abi_val.uint == 1000); } diff --git a/src/types/access_list.zig b/src/types/access_list.zig index 20a0e8d..8aca6a4 100644 --- a/src/types/access_list.zig +++ b/src/types/access_list.zig @@ -11,7 +11,7 @@ pub const AccessList = struct { /// Single entry in an access list pub const AccessListEntry = struct { address: Address, - storage_keys: []Hash, + storage_keys: []const Hash, }; /// Create an empty access list diff --git a/src/types/block.zig b/src/types/block.zig index d0632d0..82febab 100644 --- a/src/types/block.zig +++ b/src/types/block.zig @@ -78,7 +78,7 @@ pub const BlockHeader = struct { /// Check if this is a post-merge block (PoS) pub fn isPostMerge(self: BlockHeader) bool { // Post-merge blocks have difficulty = 0 - return self.difficulty.isZero(); + return self.difficulty == 0; } /// Check if this is a post-London block (has base fee) diff --git a/src/types/receipt.zig b/src/types/receipt.zig index 92bf1ab..561188f 100644 --- a/src/types/receipt.zig +++ b/src/types/receipt.zig @@ -148,16 +148,16 @@ pub const Receipt = struct { /// Get logs from a specific address pub fn getLogsFrom(self: Receipt, allocator: std.mem.Allocator, address: Address) ![]Log { - var matching_logs = std.ArrayList(Log).init(allocator); - defer matching_logs.deinit(); + var matching_logs = try std.ArrayList(Log).initCapacity(allocator, 0); + defer matching_logs.deinit(allocator); for (self.logs) |log| { if (std.mem.eql(u8, &log.address.bytes, &address.bytes)) { - try matching_logs.append(log); + try matching_logs.append(allocator, log); } } - return matching_logs.toOwnedSlice(); + return matching_logs.toOwnedSlice(allocator); } }; diff --git a/src/types/transaction.zig b/src/types/transaction.zig index 04603dd..e04b802 100644 --- a/src/types/transaction.zig +++ b/src/types/transaction.zig @@ -115,6 +115,12 @@ pub const Transaction = struct { /// Authorization list (EIP-7702) authorization_list: ?AuthorizationList, + /// Max fee per blob gas (EIP-4844) + max_fee_per_blob_gas: ?u256, + + /// Blob versioned hashes (EIP-4844) + blob_versioned_hashes: ?[]Hash, + /// Transaction signature signature: ?Signature, @@ -156,6 +162,8 @@ pub const Transaction = struct { .chain_id = null, .access_list = null, .authorization_list = null, + .max_fee_per_blob_gas = null, + .blob_versioned_hashes = null, .signature = null, .hash = null, .block_hash = null, @@ -192,6 +200,8 @@ pub const Transaction = struct { .chain_id = chain_id, .access_list = access_list, .authorization_list = null, + .max_fee_per_blob_gas = null, + .blob_versioned_hashes = null, .signature = null, .hash = null, .block_hash = null, @@ -227,6 +237,8 @@ pub const Transaction = struct { .chain_id = chain_id, .access_list = access_list, .authorization_list = null, + .max_fee_per_blob_gas = null, + .blob_versioned_hashes = null, .signature = null, .hash = null, .block_hash = null, @@ -264,6 +276,49 @@ pub const Transaction = struct { .chain_id = chain_id, .access_list = access_list, .authorization_list = authorization_list, + .max_fee_per_blob_gas = null, + .blob_versioned_hashes = null, + .signature = null, + .hash = null, + .block_hash = null, + .block_number = null, + .transaction_index = null, + .allocator = allocator, + }; + } + + /// Create a new EIP-4844 transaction + pub fn newEip4844( + allocator: std.mem.Allocator, + to: ?Address, + value: u256, + data: Bytes, + nonce: u64, + gas_limit: u64, + max_fee_per_gas: u256, + max_priority_fee_per_gas: u256, + max_fee_per_blob_gas: u256, + chain_id: u64, + access_list: ?AccessList, + blob_versioned_hashes: []Hash, + ) !Transaction { + const hashes_copy = try allocator.dupe(Hash, blob_versioned_hashes); + return .{ + .type = .eip4844, + .from = null, + .to = to, + .nonce = nonce, + .gas_limit = gas_limit, + .gas_price = null, + .max_fee_per_gas = max_fee_per_gas, + .max_priority_fee_per_gas = max_priority_fee_per_gas, + .value = value, + .data = data, + .chain_id = chain_id, + .access_list = access_list, + .authorization_list = null, + .max_fee_per_blob_gas = max_fee_per_blob_gas, + .blob_versioned_hashes = hashes_copy, .signature = null, .hash = null, .block_hash = null, @@ -282,6 +337,9 @@ pub const Transaction = struct { if (self.authorization_list) |list| { list.deinit(); } + if (self.blob_versioned_hashes) |hashes| { + self.allocator.free(hashes); + } } /// Check if this is a contract creation transaction diff --git a/src/utils/format.zig b/src/utils/format.zig index 8091e27..2be0586 100644 --- a/src/utils/format.zig +++ b/src/utils/format.zig @@ -1,7 +1,6 @@ const std = @import("std"); const Address = @import("../primitives/address.zig").Address; const Hash = @import("../primitives/hash.zig").Hash; -const U256 = @import("../primitives/uint.zig").U256; /// Format an address for display (shortened format) /// Example: 0x1234...5678 @@ -14,14 +13,14 @@ pub fn formatAddressShort(allocator: std.mem.Allocator, address: Address) ![]u8 } // 0x + first 4 chars + ... + last 4 chars - var result = std.ArrayList(u8).init(allocator); - defer result.deinit(); + var result = try std.ArrayList(u8).initCapacity(allocator, 0); + defer result.deinit(allocator); - try result.appendSlice(hex[0..6]); // 0x1234 - try result.appendSlice("..."); - try result.appendSlice(hex[hex.len - 4 ..]); // 5678 + try result.appendSlice(allocator, hex[0..6]); // 0x1234 + try result.appendSlice(allocator, "..."); // ... + try result.appendSlice(allocator, hex[hex.len - 4 ..]); // 5678 - return try result.toOwnedSlice(); + return try result.toOwnedSlice(allocator); } /// Format a hash for display (shortened format) @@ -34,14 +33,14 @@ pub fn formatHashShort(allocator: std.mem.Allocator, hash: Hash) ![]u8 { return try allocator.dupe(u8, hex); } - var result = std.ArrayList(u8).init(allocator); - defer result.deinit(); + var result = try std.ArrayList(u8).initCapacity(allocator, 0); + defer result.deinit(allocator); - try result.appendSlice(hex[0..6]); // 0xabcd - try result.appendSlice("..."); - try result.appendSlice(hex[hex.len - 4 ..]); // ef01 + try result.appendSlice(allocator, hex[0..6]); // 0xabcd + try result.appendSlice(allocator, "..."); // ... + try result.appendSlice(allocator, hex[hex.len - 4 ..]); // ef01 - return try result.toOwnedSlice(); + return try result.toOwnedSlice(allocator); } /// Format bytes for display with optional length limit @@ -59,56 +58,56 @@ pub fn formatBytes(allocator: std.mem.Allocator, bytes: []const u8, max_length: const prefix_len = (max - 3) / 2; const suffix_len = max - 3 - prefix_len; - var result = std.ArrayList(u8).init(allocator); - defer result.deinit(); + var result = try std.ArrayList(u8).initCapacity(allocator, prefix_len + 3 + suffix_len); + defer result.deinit(allocator); - try result.appendSlice(hex[0..prefix_len]); - try result.appendSlice("..."); - try result.appendSlice(hex[hex.len - suffix_len ..]); + try result.appendSlice(allocator, hex[0..prefix_len]); + try result.appendSlice(allocator, "..."); + try result.appendSlice(allocator, hex[hex.len - suffix_len ..]); - return try result.toOwnedSlice(); + return try result.toOwnedSlice(allocator); } return try allocator.dupe(u8, hex); } -/// Format a U256 as a decimal string -pub fn formatU256(allocator: std.mem.Allocator, value: U256) ![]u8 { +/// Format a native u256 as a decimal string +pub fn formatU256Native(allocator: std.mem.Allocator, value: u256) ![]u8 { // Convert to decimal string - var result = std.ArrayList(u8).init(allocator); - defer result.deinit(); + var result = try std.ArrayList(u8).initCapacity(allocator, 0); + defer result.deinit(allocator); // Handle zero case - if (value.isZero()) { - try result.append('0'); - return try result.toOwnedSlice(); + if (value == 0) { + try result.append(allocator, '0'); + return try result.toOwnedSlice(allocator); } // Use a simple algorithm: repeatedly divide by 10 var temp = value; - var digits = std.ArrayList(u8).init(allocator); - defer digits.deinit(); - - while (!temp.isZero()) { - const div_result = temp.divScalar(10); - const digit = @as(u8, @intCast(div_result.remainder)); - try digits.append('0' + digit); - temp = div_result.quotient; + var digits = try std.ArrayList(u8).initCapacity(allocator, 0); + defer digits.deinit(allocator); + + while (temp > 0) { + const digit = @as(u8, @intCast(temp % 10)); + try digits.append(allocator, '0' + digit); + temp = temp / 10; } // Reverse digits var i: usize = digits.items.len; while (i > 0) { i -= 1; - try result.append(digits.items[i]); + try result.append(allocator, digits.items[i]); } - return try result.toOwnedSlice(); + return try result.toOwnedSlice(allocator); } -/// Format a U256 as a hex string with 0x prefix -pub fn formatU256Hex(allocator: std.mem.Allocator, value: U256) ![]u8 { - return try value.toHex(allocator); +/// Format a native u256 as a hex string with 0x prefix +pub fn formatU256Hex(allocator: std.mem.Allocator, value: u256) ![]u8 { + const u256ToHex = @import("../primitives/uint.zig").u256ToHex; + return try u256ToHex(allocator, value); } /// Format a number with thousand separators @@ -117,8 +116,8 @@ pub fn formatWithSeparators(allocator: std.mem.Allocator, number_str: []const u8 return try allocator.dupe(u8, number_str); } - var result = std.ArrayList(u8).init(allocator); - defer result.deinit(); + var result = try std.ArrayList(u8).initCapacity(allocator, 0); + defer result.deinit(allocator); const len = number_str.len; var count: usize = 0; @@ -127,13 +126,13 @@ pub fn formatWithSeparators(allocator: std.mem.Allocator, number_str: []const u8 while (i > 0) { i -= 1; if (count > 0 and count % 3 == 0) { - try result.insert(0, separator); + try result.insert(allocator, 0, separator); } - try result.insert(0, number_str[i]); + try result.insert(allocator, 0, number_str[i]); count += 1; } - return try result.toOwnedSlice(); + return try result.toOwnedSlice(allocator); } /// Pad a string to a specific length with a character @@ -177,7 +176,7 @@ pub fn truncate(allocator: std.mem.Allocator, str: []const u8, max_length: usize test "format address short" { const allocator = std.testing.allocator; - const addr = Address.fromBytes([_]u8{0x12} ++ [_]u8{0x34} ** 9 ++ [_]u8{0x56}); + const addr = Address.fromBytes([_]u8{0x12} ** 20); const formatted = try formatAddressShort(allocator, addr); defer allocator.free(formatted); @@ -206,21 +205,21 @@ test "format bytes with limit" { try std.testing.expect(formatted.len <= 10); } -test "format U256 decimal" { +test "format u256 decimal" { const allocator = std.testing.allocator; - const value = U256.fromInt(1234567890); - const formatted = try formatU256(allocator, value); + const value: u256 = 1234567890; + const formatted = try formatU256Native(allocator, value); defer allocator.free(formatted); try std.testing.expectEqualStrings("1234567890", formatted); } -test "format U256 zero" { +test "format u256 zero" { const allocator = std.testing.allocator; - const value = U256.zero(); - const formatted = try formatU256(allocator, value); + const value: u256 = 0; + const formatted = try formatU256Native(allocator, value); defer allocator.free(formatted); try std.testing.expectEqualStrings("0", formatted); diff --git a/src/utils/hex.zig b/src/utils/hex.zig index 83f8846..074032c 100644 --- a/src/utils/hex.zig +++ b/src/utils/hex.zig @@ -25,18 +25,32 @@ pub fn hexToBytes(allocator: std.mem.Allocator, hex_str: []const u8) ![]u8 { start = 2; } - const hex_len = hex_str.len - start; - if (hex_len % 2 != 0) { - return error.InvalidHexLength; - } + const hex_part = hex_str[start..]; + const hex_len = hex_part.len; - const result = try allocator.alloc(u8, hex_len / 2); + // Handle odd-length hex strings by padding with leading zero + const is_odd = hex_len % 2 != 0; + const result_len = (hex_len + 1) / 2; + + const result = try allocator.alloc(u8, result_len); errdefer allocator.free(result); - for (0..result.len) |i| { - const high = try hexCharToNibble(hex_str[start + i * 2]); - const low = try hexCharToNibble(hex_str[start + i * 2 + 1]); - result[i] = (high << 4) | low; + if (is_odd) { + // First byte comes from a single nibble (padded with 0) + result[0] = try hexCharToNibble(hex_part[0]); + // Process remaining pairs + for (1..result_len) |i| { + const high = try hexCharToNibble(hex_part[i * 2 - 1]); + const low = try hexCharToNibble(hex_part[i * 2]); + result[i] = (high << 4) | low; + } + } else { + // Even length - process pairs normally + for (0..result_len) |i| { + const high = try hexCharToNibble(hex_part[i * 2]); + const low = try hexCharToNibble(hex_part[i * 2 + 1]); + result[i] = (high << 4) | low; + } } return result; diff --git a/src/utils/units.zig b/src/utils/units.zig index 0124827..1c3bf27 100644 --- a/src/utils/units.zig +++ b/src/utils/units.zig @@ -1,5 +1,5 @@ const std = @import("std"); -const U256 = @import("../primitives/uint.zig").U256; // Legacy compatibility +const u256ToU64 = @import("../primitives/uint.zig").u256ToU64; /// Ethereum unit denominations pub const Unit = enum { @@ -32,14 +32,14 @@ pub const Unit = enum { }; } - /// Get the multiplier as a U256 - pub fn multiplier(self: Unit) U256 { + /// Get the multiplier as a u256 + pub fn multiplier(self: Unit) u256 { const exp = self.exponent(); - var result = U256.one(); + var result: u256 = 1; var i: u8 = 0; while (i < exp) : (i += 1) { - result = result.mulScalar(10); + result = result *% 10; } return result; @@ -47,27 +47,26 @@ pub const Unit = enum { }; /// Convert from wei to another unit -pub fn fromWei(wei: U256, unit: Unit) !WeiConversion { +pub fn fromWei(wei: u256, unit: Unit) WeiConversion { const mult = unit.multiplier(); - - // Divide wei by multiplier - const div_result = wei.divScalar(mult.toU64() catch return error.UnitTooLarge); + const quotient = wei / mult; + const remainder = wei % mult; return WeiConversion{ - .integer_part = div_result.quotient, - .remainder_wei = U256.fromInt(div_result.remainder), + .integer_part = quotient, + .remainder_wei = remainder, .unit = unit, }; } /// Convert to wei from another unit -pub fn toWei(amount: u64, unit: Unit) U256 { +pub fn toWei(amount: u64, unit: Unit) u256 { const mult = unit.multiplier(); - return U256.fromInt(amount).mulScalar(mult.toU64() catch unreachable); + return @as(u256, amount) *% mult; } /// Convert to wei from a floating point amount (ether) -pub fn etherToWei(ether: f64) !U256 { +pub fn etherToWei(ether: f64) !u256 { if (ether < 0) { return error.NegativeValue; } @@ -80,28 +79,27 @@ pub fn etherToWei(ether: f64) !U256 { return error.Overflow; } - return U256.fromInt(@as(u64, @intFromFloat(wei_value))); + return @as(u256, @as(u64, @intFromFloat(wei_value))); } -/// Convert wei to ether as a floating point (native u256 version) -pub fn weiToEther(wei: anytype) !f64 { - const T = @TypeOf(wei); - const wei_u64 = if (T == u256) blk: { - if (wei > std.math.maxInt(u64)) return error.ValueTooLarge; - break :blk @as(u64, @intCast(wei)); - } else if (T == U256) blk: { - break :blk try wei.tryToU64(); - } else { - @compileError("weiToEther expects u256 or U256"); - }; - const wei_per_ether: f64 = 1_000_000_000_000_000_000.0; - return @as(f64, @floatFromInt(wei_u64)) / wei_per_ether; +/// Convert wei to ether as a floating point +pub fn weiToEther(wei: u256) !f64 { + const wei_per_ether: u256 = 1_000_000_000_000_000_000; + const ether_part = wei / wei_per_ether; + const remainder = wei % wei_per_ether; + + // Convert integer part to f64 (may lose precision for very large values) + const ether_f64: f64 = @floatFromInt(ether_part); + const remainder_f64: f64 = @floatFromInt(remainder); + const wei_per_ether_f64: f64 = 1_000_000_000_000_000_000.0; + + return ether_f64 + (remainder_f64 / wei_per_ether_f64); } /// Result of a wei conversion pub const WeiConversion = struct { - integer_part: U256, - remainder_wei: U256, + integer_part: u256, + remainder_wei: u256, unit: Unit, /// Format as a string with decimal places @@ -113,17 +111,17 @@ pub const WeiConversion = struct { const format_module = @import("./format.zig"); // Get integer part - const integer_str = try format_module.formatU256(allocator, self.integer_part); + const integer_str = try format_module.formatU256Native(allocator, self.integer_part); defer allocator.free(integer_str); - if (decimal_places == 0 or self.remainder_wei.isZero()) { + if (decimal_places == 0 or self.remainder_wei == 0) { return try std.fmt.allocPrint(allocator, "{s}", .{integer_str}); } // Calculate decimal part const mult = self.unit.multiplier(); - const remainder_u64 = self.remainder_wei.toU64(); - const divisor = mult.toU64() catch return error.UnitTooLarge; + const remainder_u64 = u256ToU64(self.remainder_wei) catch 0; + const divisor = u256ToU64(mult) catch return error.UnitTooLarge; // Convert remainder to decimal string const decimal_value = (@as(f64, @floatFromInt(remainder_u64)) / @as(f64, @floatFromInt(divisor))) * @@ -131,11 +129,27 @@ pub const WeiConversion = struct { const decimal_int = @as(u64, @intFromFloat(decimal_value)); - return try std.fmt.allocPrint( - allocator, - "{s}.{d:0>[1]}", - .{ integer_str, decimal_int, decimal_places }, - ); + // Format with appropriate decimal places using allocPrint + const decimal_str = try std.fmt.allocPrint(allocator, "{d}", .{decimal_int}); + defer allocator.free(decimal_str); + + // Pad with zeros if needed + var result = try std.ArrayList(u8).initCapacity(allocator, 0); + errdefer result.deinit(allocator); + + try result.appendSlice(allocator, integer_str); + try result.append(allocator, '.'); + + // Add leading zeros if decimal is shorter than decimal_places + if (decimal_str.len < decimal_places) { + var zeros_needed = decimal_places - decimal_str.len; + while (zeros_needed > 0) : (zeros_needed -= 1) { + try result.append(allocator, '0'); + } + } + try result.appendSlice(allocator, decimal_str); + + return try result.toOwnedSlice(allocator); } }; @@ -162,14 +176,14 @@ pub fn parseUnit(str: []const u8) !Unit { /// Common gas price conversions pub const GasPrice = struct { /// Convert gwei to wei - pub fn gweiToWei(gwei: u64) U256 { + pub fn gweiToWei(gwei: u64) u256 { return toWei(gwei, .gwei); } /// Convert wei to gwei - pub fn weiToGwei(wei: U256) !u64 { - const conversion = try fromWei(wei, .gwei); - return try conversion.integer_part.tryToU64(); + pub fn weiToGwei(wei: u256) !u64 { + const conversion = fromWei(wei, .gwei); + return u256ToU64(conversion.integer_part); } }; @@ -181,48 +195,48 @@ test "unit exponents" { test "to wei from ether" { const wei = toWei(1, .ether); - const expected = U256.fromInt(1_000_000_000_000_000_000); - try std.testing.expect(wei.eql(expected)); + const expected: u256 = 1_000_000_000_000_000_000; + try std.testing.expectEqual(expected, wei); } test "to wei from gwei" { const wei = toWei(1, .gwei); - const expected = U256.fromInt(1_000_000_000); - try std.testing.expect(wei.eql(expected)); + const expected: u256 = 1_000_000_000; + try std.testing.expectEqual(expected, wei); } test "from wei to ether" { - const wei = U256.fromInt(1_000_000_000_000_000_000); - const conversion = try fromWei(wei, .ether); + const wei: u256 = 1_000_000_000_000_000_000; + const conversion = fromWei(wei, .ether); - try std.testing.expect(conversion.integer_part.eql(U256.one())); - try std.testing.expect(conversion.remainder_wei.isZero()); + try std.testing.expectEqual(@as(u256, 1), conversion.integer_part); + try std.testing.expectEqual(@as(u256, 0), conversion.remainder_wei); } test "from wei to gwei" { - const wei = U256.fromInt(5_000_000_000); - const conversion = try fromWei(wei, .gwei); + const wei: u256 = 5_000_000_000; + const conversion = fromWei(wei, .gwei); - try std.testing.expect(conversion.integer_part.eql(U256.fromInt(5))); - try std.testing.expect(conversion.remainder_wei.isZero()); + try std.testing.expectEqual(@as(u256, 5), conversion.integer_part); + try std.testing.expectEqual(@as(u256, 0), conversion.remainder_wei); } test "ether to wei float" { const wei = try etherToWei(1.5); - const expected = U256.fromInt(1_500_000_000_000_000_000); - try std.testing.expect(wei.eql(expected)); + const expected: u256 = 1_500_000_000_000_000_000; + try std.testing.expectEqual(expected, wei); } test "wei to ether float" { - const wei = U256.fromInt(1_500_000_000_000_000_000); + const wei: u256 = 1_500_000_000_000_000_000; const ether = try weiToEther(wei); try std.testing.expectApproxEqRel(1.5, ether, 0.0001); } test "gas price conversions" { const wei = GasPrice.gweiToWei(30); - const expected = U256.fromInt(30_000_000_000); - try std.testing.expect(wei.eql(expected)); + const expected: u256 = 30_000_000_000; + try std.testing.expectEqual(expected, wei); const gwei = try GasPrice.weiToGwei(wei); try std.testing.expectEqual(@as(u64, 30), gwei); @@ -239,8 +253,8 @@ test "parse unit" { test "conversion format" { const allocator = std.testing.allocator; - const wei = U256.fromInt(1_500_000_000); - const conversion = try fromWei(wei, .gwei); + const wei: u256 = 1_500_000_000; + const conversion = fromWei(wei, .gwei); const formatted = try conversion.format(allocator, 2); defer allocator.free(formatted);