From b9ddb0c382465ae14e2bc6b87215724f2c896427 Mon Sep 17 00:00:00 2001 From: ch4r10t33r Date: Thu, 9 Oct 2025 20:11:21 +0100 Subject: [PATCH] feat: Replaced custom U256 type with the native one and added utility functions --- examples/01_wallet_creation.zig | 2 - examples/02_query_blockchain.zig | 4 +- examples/03_send_transaction.zig | 22 +- examples/05_transaction_receipts.zig | 10 +- examples/06_event_monitoring.zig | 7 +- examples/07_complete_workflow.zig | 8 +- src/abi/decode.zig | 14 +- src/abi/encode.zig | 13 +- src/abi/packed.zig | 17 +- src/abi/types.zig | 3 +- src/middleware/gas.zig | 62 ++--- src/primitives/uint.zig | 332 +++++++++------------------ src/providers/http.zig | 4 +- src/providers/provider.zig | 6 +- src/rlp/packed.zig | 19 +- src/root.zig | 15 ++ src/rpc/eth.zig | 50 ++-- src/types/block.zig | 37 ++- src/types/receipt.zig | 21 +- src/types/transaction.zig | 89 ++++--- src/utils/units.zig | 16 +- 21 files changed, 324 insertions(+), 427 deletions(-) diff --git a/examples/01_wallet_creation.zig b/examples/01_wallet_creation.zig index f3d381f..a826e86 100644 --- a/examples/01_wallet_creation.zig +++ b/examples/01_wallet_creation.zig @@ -6,7 +6,6 @@ /// - Generate and use mnemonic phrases /// - Create HD wallets /// - Encrypt and store wallets - const std = @import("std"); const zigeth = @import("zigeth"); @@ -163,4 +162,3 @@ pub fn main() !void { std.debug.print("🎉 All wallet examples completed!\n\n", .{}); } - diff --git a/examples/02_query_blockchain.zig b/examples/02_query_blockchain.zig index 14ca5d1..dbf4c17 100644 --- a/examples/02_query_blockchain.zig +++ b/examples/02_query_blockchain.zig @@ -71,8 +71,8 @@ pub fn main() !void { std.debug.print("✅ Gas price: {} wei\n", .{gas_price}); // Convert to gwei - const gas_u64 = gas_price.toU64(); - const gwei = @as(f64, @floatFromInt(gas_u64)) / 1_000_000_000.0; + const gas_u64: u64 = @intCast(gas_price / 1_000_000_000); + const gwei = @as(f64, @floatFromInt(gas_u64)); std.debug.print(" Gas price: {d:.2} gwei\n\n", .{gwei}); } diff --git a/examples/03_send_transaction.zig b/examples/03_send_transaction.zig index e8d6c44..f7af033 100644 --- a/examples/03_send_transaction.zig +++ b/examples/03_send_transaction.zig @@ -50,11 +50,11 @@ pub fn main() !void { var tx = zigeth.types.Transaction.newLegacy( allocator, to_address, - zigeth.primitives.U256.fromInt(1_000_000_000_000_000), // 0.001 ETH + @as(u256, 1_000_000_000_000_000), // 0.001 ETH empty_data, 0, // nonce 21000, // gas_limit - zigeth.primitives.U256.fromInt(20_000_000_000), // 20 gwei gas_price + @as(u256, 20_000_000_000), // 20 gwei gas_price ); tx.from = from_address; @@ -76,12 +76,12 @@ pub fn main() !void { var tx = zigeth.types.Transaction.newEip1559( allocator, to_address, - zigeth.primitives.U256.fromInt(1_000_000_000_000_000), // 0.001 ETH + @as(u256, 1_000_000_000_000_000), // 0.001 ETH empty_data, 1, // nonce 21000, // gas_limit - zigeth.primitives.U256.fromInt(50_000_000_000), // 50 gwei max_fee - zigeth.primitives.U256.fromInt(2_000_000_000), // 2 gwei priority_fee + @as(u256, 50_000_000_000), // 50 gwei max_fee + @as(u256, 2_000_000_000), // 2 gwei priority_fee 11155111, // chain_id (Sepolia) null, // access_list ); @@ -122,12 +122,12 @@ pub fn main() !void { var tx = zigeth.types.Transaction.newEip1559( allocator, to_address, - zigeth.primitives.U256.fromInt(1_000_000_000_000_000), // 0.001 ETH + @as(u256, 1_000_000_000_000_000), // 0.001 ETH empty_data, nonce, 21000, // initial gas_limit (will be estimated) - zigeth.primitives.U256.fromInt(50_000_000_000), // temp values - zigeth.primitives.U256.fromInt(2_000_000_000), + @as(u256, 50_000_000_000), // temp values + @as(u256, 2_000_000_000), 11155111, // Sepolia null, ); @@ -160,12 +160,12 @@ pub fn main() !void { var tx = zigeth.types.Transaction.newEip1559( allocator, to_address, - zigeth.primitives.U256.fromInt(1_000_000_000_000_000), // 0.001 ETH + @as(u256, 1_000_000_000_000_000), // 0.001 ETH empty_data, 0, // nonce 21000, // gas_limit - zigeth.primitives.U256.fromInt(50_000_000_000), // 50 gwei max_fee - zigeth.primitives.U256.fromInt(2_000_000_000), // 2 gwei priority_fee + @as(u256, 50_000_000_000), // 50 gwei max_fee + @as(u256, 2_000_000_000), // 2 gwei priority_fee 11155111, // Sepolia null, // access_list ); diff --git a/examples/05_transaction_receipts.zig b/examples/05_transaction_receipts.zig index edb11e1..59f62ab 100644 --- a/examples/05_transaction_receipts.zig +++ b/examples/05_transaction_receipts.zig @@ -5,7 +5,6 @@ /// - Parse transaction data /// - Calculate transaction fees /// - Filter logs from receipts - const std = @import("std"); const zigeth = @import("zigeth"); @@ -75,16 +74,16 @@ pub fn main() !void { // Simulated receipt data const gas_used: u64 = 21000; - const gas_price = zigeth.primitives.U256.fromInt(50_000_000_000); // 50 gwei + const gas_price: u256 = 50_000_000_000; // 50 gwei - const fee = gas_price.mulScalar(gas_used); + const fee = gas_price * gas_used; std.debug.print(" Gas used: {d}\n", .{gas_used}); std.debug.print(" Gas price: 50 gwei\n", .{}); std.debug.print(" Total fee: {} wei\n", .{fee}); - // Convert to ETH - const fee_u64 = fee.toU64(); + // Convert to ETH (simple cast since we know it's a small value) + const fee_u64: u64 = @intCast(fee); // Safe cast for this example const fee_eth = @as(f64, @floatFromInt(fee_u64)) / 1_000_000_000_000_000_000.0; std.debug.print(" Total fee: {d:.6} ETH\n\n", .{fee_eth}); } @@ -179,4 +178,3 @@ pub fn main() !void { std.debug.print("🎉 All receipt examples completed!\n", .{}); std.debug.print("💡 Tip: Always check receipt.status before considering TX successful\n\n", .{}); } - diff --git a/examples/06_event_monitoring.zig b/examples/06_event_monitoring.zig index bcc424b..0816773 100644 --- a/examples/06_event_monitoring.zig +++ b/examples/06_event_monitoring.zig @@ -5,7 +5,6 @@ /// - Filter and parse event logs /// - Track specific contract events /// - Use real-time subscriptions - const std = @import("std"); const zigeth = @import("zigeth"); @@ -75,10 +74,7 @@ pub fn main() !void { std.debug.print("───────────────────────────────────────\n", .{}); { // USDC contract - simple string literal! - const usdc_address = try zigeth.primitives.Address.fromHex( - - "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" - ); + const usdc_address = try zigeth.primitives.Address.fromHex("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"); const addr_hex = try usdc_address.toHex(allocator); defer allocator.free(addr_hex); @@ -183,4 +179,3 @@ pub fn main() !void { std.debug.print("🎉 All event monitoring examples completed!\n", .{}); std.debug.print("💡 Tip: Use WebSocket for real-time, HTTP for historical data\n\n", .{}); } - diff --git a/examples/07_complete_workflow.zig b/examples/07_complete_workflow.zig index ae6740c..b2ae4b0 100644 --- a/examples/07_complete_workflow.zig +++ b/examples/07_complete_workflow.zig @@ -130,12 +130,12 @@ pub fn main() !void { var tx = zigeth.types.Transaction.newEip1559( allocator, to, - zigeth.primitives.U256.fromInt(100_000_000_000_000_000), // 0.1 ETH + @as(u256, 100_000_000_000_000_000), // 0.1 ETH empty_data, 0, // nonce (would be set by middleware) 21000, // gas_limit - zigeth.primitives.U256.fromInt(50_000_000_000), // max_fee - zigeth.primitives.U256.fromInt(2_000_000_000), // priority_fee + @as(u256, 50_000_000_000), // max_fee + @as(u256, 2_000_000_000), // priority_fee 11155111, // Sepolia null, // access_list ); @@ -271,7 +271,7 @@ pub fn main() !void { std.debug.print(" • zigeth.providers (Networks, HTTP, WebSocket)\n", .{}); std.debug.print(" • zigeth.middleware (Gas, Nonce, Signer)\n", .{}); std.debug.print(" • zigeth.types (Transaction, Receipt)\n", .{}); - std.debug.print(" • zigeth.primitives (Address, Hash, U256)\n", .{}); + std.debug.print(" • zigeth.primitives (Address, Hash, native u256)\n", .{}); std.debug.print(" • zigeth.utils (Units, Hex, Format)\n\n", .{}); std.debug.print("Benefits of using zigeth:\n", .{}); diff --git a/src/abi/decode.zig b/src/abi/decode.zig index b985623..ce4c20d 100644 --- a/src/abi/decode.zig +++ b/src/abi/decode.zig @@ -1,7 +1,7 @@ const std = @import("std"); const Address = @import("../primitives/address.zig").Address; -const U256 = @import("../primitives/uint.zig").U256; const types = @import("./types.zig"); +const uint_utils = @import("../primitives/uint.zig"); /// ABI decoder for Ethereum smart contract responses pub const Decoder = struct { @@ -28,17 +28,17 @@ pub const Decoder = struct { } /// Decode a uint256 - pub fn decodeUint256(self: *Decoder) !U256 { + pub fn decodeUint256(self: *Decoder) !u256 { const bytes = try self.read32(); var arr: [32]u8 = undefined; @memcpy(&arr, bytes); - return U256.fromBytes(arr); + return uint_utils.u256FromBytes(arr); } /// Decode a uint of any size + /// Alias for decodeUint256 for clarity pub fn decodeUint(self: *Decoder) !u256 { - const bytes = try self.read32(); - return std.mem.readInt(u256, bytes[0..32], .big); + return self.decodeUint256(); } /// Decode an address @@ -148,7 +148,7 @@ test "decode uint256" { var decoder = Decoder.init(allocator, &data); const value = try decoder.decodeUint256(); - try std.testing.expect(value.eql(U256.fromInt(42))); + try std.testing.expectEqual(@as(u256, 42), value); } test "decode address" { @@ -206,7 +206,7 @@ test "decode multiple values" { var decoder = Decoder.init(allocator, &data); const val1 = try decoder.decodeUint256(); - try std.testing.expect(val1.eql(U256.fromInt(100))); + try std.testing.expectEqual(@as(u256, 100), val1); const val2 = try decoder.decodeAddress(); try std.testing.expectEqual(@as(u8, 0xAB), val2.bytes[0]); diff --git a/src/abi/encode.zig b/src/abi/encode.zig index f92e275..4f7f078 100644 --- a/src/abi/encode.zig +++ b/src/abi/encode.zig @@ -1,8 +1,8 @@ const std = @import("std"); const Address = @import("../primitives/address.zig").Address; -const U256 = @import("../primitives/uint.zig").U256; const Bytes = @import("../primitives/bytes.zig").Bytes; const types = @import("./types.zig"); +const uint_utils = @import("../primitives/uint.zig"); /// ABI encoder for Ethereum smart contract calls pub const Encoder = struct { @@ -21,16 +21,15 @@ pub const Encoder = struct { } /// Encode a uint256 value - pub fn encodeUint256(self: *Encoder, value: U256) !void { - const bytes = value.toBytes(); + pub fn encodeUint256(self: *Encoder, value: u256) !void { + const bytes = uint_utils.u256ToBytes(value); try self.buffer.appendSlice(&bytes); } /// Encode a uint value of any size (padded to 32 bytes) + /// Alias for encodeUint256 for clarity pub fn encodeUint(self: *Encoder, value: u256) !void { - var bytes: [32]u8 = undefined; - std.mem.writeInt(u256, &bytes, value, .big); - try self.buffer.appendSlice(&bytes); + try self.encodeUint256(value); } /// Encode an int256 value @@ -183,7 +182,7 @@ test "encode uint256" { var encoder = Encoder.init(allocator); defer encoder.deinit(); - const value = U256.fromInt(42); + const value: u256 = 42; try encoder.encodeUint256(value); const result = encoder.toSlice(); diff --git a/src/abi/packed.zig b/src/abi/packed.zig index d72bb29..644c56a 100644 --- a/src/abi/packed.zig +++ b/src/abi/packed.zig @@ -1,7 +1,7 @@ const std = @import("std"); const Address = @import("../primitives/address.zig").Address; -const U256 = @import("../primitives/uint.zig").U256; const Hash = @import("../primitives/hash.zig").Hash; +const uint_utils = @import("../primitives/uint.zig"); /// Packed ABI encoding (tightly packed, no padding) /// Used for hashing and signature generation (e.g., EIP-712) @@ -21,8 +21,8 @@ pub const PackedEncoder = struct { } /// Encode a uint256 (no padding) - pub fn encodeUint256(self: *PackedEncoder, value: U256) !void { - const bytes = value.toBytes(); + pub fn encodeUint256(self: *PackedEncoder, value: u256) !void { + const bytes = uint_utils.u256ToBytes(value); try self.buffer.appendSlice(&bytes); } @@ -30,8 +30,7 @@ pub const PackedEncoder = struct { pub fn encodeUint(self: *PackedEncoder, value: u256, size_bytes: usize) !void { if (size_bytes > 32) return error.InvalidSize; - var bytes: [32]u8 = undefined; - std.mem.writeInt(u256, &bytes, value, .big); + const bytes = uint_utils.u256ToBytes(value); // Take only the required bytes from the end const offset = 32 - size_bytes; @@ -96,7 +95,7 @@ pub fn encodePacked( /// Packed value (tightly packed, no padding) pub const PackedValue = union(enum) { - uint256: U256, + uint256: u256, uint: struct { value: u256, size_bytes: usize }, address: Address, bool_val: bool, @@ -135,7 +134,7 @@ test "packed encode uint256" { var encoder = PackedEncoder.init(allocator); defer encoder.deinit(); - const value = U256.fromInt(42); + const value = @as(u256, 42); try encoder.encodeUint256(value); const result = encoder.toSlice(); @@ -191,7 +190,7 @@ test "packed encode multiple values" { const values = [_]PackedValue{ .{ .address = Address.fromBytes([_]u8{0xAB} ** 20) }, - .{ .uint256 = U256.fromInt(100) }, + .{ .uint256 = @as(u256, 100) }, .{ .bool_val = true }, }; @@ -229,7 +228,7 @@ test "hash packed values" { const values = [_]PackedValue{ .{ .string = "hello" }, - .{ .uint256 = U256.fromInt(123) }, + .{ .uint256 = @as(u256, 123) }, }; const hash = try hashPacked(allocator, &values); diff --git a/src/abi/types.zig b/src/abi/types.zig index 11b2abc..61ed1c0 100644 --- a/src/abi/types.zig +++ b/src/abi/types.zig @@ -1,6 +1,5 @@ const std = @import("std"); const Address = @import("../primitives/address.zig").Address; -const U256 = @import("../primitives/uint.zig").U256; const Bytes = @import("../primitives/bytes.zig").Bytes; /// ABI type system for Solidity types @@ -134,7 +133,7 @@ pub const AbiType = union(enum) { /// ABI encoded value pub const AbiValue = union(enum) { - uint: U256, + uint: u256, int: i256, address: Address, bool_val: bool, diff --git a/src/middleware/gas.zig b/src/middleware/gas.zig index 2d466f6..b18eb5b 100644 --- a/src/middleware/gas.zig +++ b/src/middleware/gas.zig @@ -1,5 +1,5 @@ const std = @import("std"); -const U256 = @import("../primitives/uint.zig").U256; +const uint_utils = @import("../primitives/uint.zig"); const Provider = @import("../providers/provider.zig").Provider; const Address = @import("../primitives/address.zig").Address; const Transaction = @import("../types/transaction.zig").Transaction; @@ -15,8 +15,8 @@ pub const GasStrategy = enum { /// Gas price configuration pub const GasConfig = struct { strategy: GasStrategy, - max_fee_per_gas: ?U256, - max_priority_fee_per_gas: ?U256, + max_fee_per_gas: ?u256, + max_priority_fee_per_gas: ?u256, gas_limit: ?u64, multiplier: f64, // Multiplier for gas price (e.g., 1.1 = 110%) @@ -50,7 +50,7 @@ pub const GasConfig = struct { }; } - pub fn custom(max_fee: U256, max_priority_fee: U256) GasConfig { + pub fn custom(max_fee: u256, max_priority_fee: u256) GasConfig { return .{ .strategy = .custom, .max_fee_per_gas = max_fee, @@ -63,13 +63,13 @@ pub const GasConfig = struct { /// EIP-1559 fee data pub const FeeData = struct { - max_fee_per_gas: U256, - max_priority_fee_per_gas: U256, - base_fee_per_gas: U256, + max_fee_per_gas: u256, + max_priority_fee_per_gas: u256, + base_fee_per_gas: u256, last_block_number: u64, - pub fn estimatedCost(self: FeeData, gas_limit: u64) U256 { - return self.max_fee_per_gas.mulScalar(gas_limit); + pub fn estimatedCost(self: FeeData, gas_limit: u64) u256 { + return self.max_fee_per_gas * gas_limit; } }; @@ -95,13 +95,13 @@ pub const GasMiddleware = struct { } /// Get current gas price (legacy) - pub fn getGasPrice(self: *GasMiddleware) !U256 { + pub fn getGasPrice(self: *GasMiddleware) !u256 { const base_price = try self.provider.getGasPrice(); // Apply multiplier const multiplier_int = @as(u64, @intFromFloat(self.config.multiplier * 100.0)); - const adjusted_price = base_price.mulScalar(multiplier_int); - return adjusted_price.divScalar(100).quotient; + const adjusted_price = base_price * multiplier_int; + return adjusted_price / 100; } /// Get EIP-1559 fee data @@ -121,7 +121,7 @@ pub const GasMiddleware = struct { const fee_data = FeeData{ .max_fee_per_gas = max_fee, .max_priority_fee_per_gas = max_priority, - .base_fee_per_gas = if (max_fee.gte(max_priority)) max_fee.sub(max_priority) else U256.zero(), + .base_fee_per_gas = if (max_fee >= max_priority) max_fee - max_priority else 0, .last_block_number = try self.provider.getBlockNumber(), }; self.cached_fee_data = fee_data; @@ -146,7 +146,7 @@ pub const GasMiddleware = struct { const priority_fee = try self.calculatePriorityFee(base_fee); // Calculate max fee = base fee + priority fee - const max_fee = base_fee.add(priority_fee); + const max_fee = base_fee + priority_fee; const fee_data = FeeData{ .max_fee_per_gas = max_fee, @@ -163,19 +163,19 @@ pub const GasMiddleware = struct { } /// Calculate priority fee based on strategy - fn calculatePriorityFee(self: *GasMiddleware, base_fee: U256) !U256 { + fn calculatePriorityFee(self: *GasMiddleware, base_fee: u256) !u256 { _ = base_fee; // Reserved for future use // Try to get max priority fee from provider const suggested_priority = self.provider.eth.maxPriorityFeePerGas() catch { // Fallback: 2.5 gwei for standard, adjust for strategy - return U256.fromInt(2_500_000_000); + return @as(u256, 2_500_000_000); }; // Apply strategy multiplier const multiplier_int = @as(u64, @intFromFloat(self.config.multiplier * 100.0)); - const adjusted = suggested_priority.mulScalar(multiplier_int); - return adjusted.divScalar(100).quotient; + const adjusted = suggested_priority * multiplier_int; + return adjusted / 100; } /// Estimate gas limit for a transaction @@ -224,7 +224,7 @@ pub const GasMiddleware = struct { } /// Calculate total transaction cost - pub fn calculateTxCost(self: *GasMiddleware, gas_limit: u64) !U256 { + pub fn calculateTxCost(self: *GasMiddleware, gas_limit: u64) !u256 { const fee_data = try self.getFeeData(); return fee_data.estimatedCost(gas_limit); } @@ -233,14 +233,14 @@ pub const GasMiddleware = struct { pub fn checkSufficientBalance( self: *GasMiddleware, from: Address, - value: U256, + value: u256, gas_limit: u64, ) !bool { const balance = try self.provider.getBalance(from); const tx_cost = try self.calculateTxCost(gas_limit); - const total_cost = value.add(tx_cost); + const total_cost = value + tx_cost; - return balance.gte(total_cost); + return balance >= total_cost; } /// Clear cached fee data @@ -282,8 +282,8 @@ test "gas config fast" { } test "gas config custom" { - const max_fee = U256.fromInt(50_000_000_000); - const max_priority = U256.fromInt(2_000_000_000); + const max_fee = @as(u256, 50_000_000_000); + const max_priority = @as(u256, 2_000_000_000); const config = GasConfig.custom(max_fee, max_priority); try std.testing.expectEqual(GasStrategy.custom, config.strategy); @@ -293,14 +293,14 @@ test "gas config custom" { test "fee data estimated cost" { const fee_data = FeeData{ - .max_fee_per_gas = U256.fromInt(50_000_000_000), // 50 gwei - .max_priority_fee_per_gas = U256.fromInt(2_000_000_000), // 2 gwei - .base_fee_per_gas = U256.fromInt(48_000_000_000), // 48 gwei + .max_fee_per_gas = @as(u256, 50_000_000_000), // 50 gwei + .max_priority_fee_per_gas = @as(u256, 2_000_000_000), // 2 gwei + .base_fee_per_gas = @as(u256, 48_000_000_000), // 48 gwei .last_block_number = 1000, }; const cost = fee_data.estimatedCost(21000); // Standard transfer gas - try std.testing.expect(cost.gt(U256.zero())); + try std.testing.expect(cost > 0); } test "gas middleware creation" { @@ -339,9 +339,9 @@ test "gas middleware clear cache" { var middleware = GasMiddleware.init(allocator, &provider, config); middleware.cached_fee_data = FeeData{ - .max_fee_per_gas = U256.fromInt(50_000_000_000), - .max_priority_fee_per_gas = U256.fromInt(2_000_000_000), - .base_fee_per_gas = U256.fromInt(48_000_000_000), + .max_fee_per_gas = @as(u256, 50_000_000_000), + .max_priority_fee_per_gas = @as(u256, 2_000_000_000), + .base_fee_per_gas = @as(u256, 48_000_000_000), .last_block_number = 1000, }; diff --git a/src/primitives/uint.zig b/src/primitives/uint.zig index e10529d..be728c1 100644 --- a/src/primitives/uint.zig +++ b/src/primitives/uint.zig @@ -1,198 +1,156 @@ +//! u256 Utilities for Ethereum +//! +//! 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 +//! - Use utility functions (`u256FromBytes`, `u256ToBytes`, etc.) for Ethereum conversions +//! - The legacy `U256` wrapper struct is provided for backwards compatibility only +//! +//! **Example:** +//! ```zig +//! const value: u256 = 1_000_000_000_000_000_000; // 1 ETH in wei +//! const bytes = u256ToBytes(value); // Convert to Ethereum format (big-endian) +//! const hex_str = try u256ToHex(value, allocator); +//! ``` + const std = @import("std"); const hex = @import("../utils/hex.zig"); -/// 256-bit unsigned integer for Ethereum -/// Used for balances, gas, nonces, etc. +/// Create u256 from bytes (big-endian, 32 bytes) - Ethereum format +pub fn u256FromBytes(bytes: [32]u8) u256 { + return std.mem.readInt(u256, &bytes, .big); +} + +/// Convert u256 to bytes (big-endian, 32 bytes) - Ethereum format +pub fn u256ToBytes(value: u256) [32]u8 { + var bytes: [32]u8 = undefined; + std.mem.writeInt(u256, &bytes, value, .big); + return bytes; +} + +/// Create u256 from hex string +pub fn u256FromHex(hex_str: []const u8) !u256 { + var temp_allocator_buffer: [1024]u8 = undefined; + var fba = std.heap.FixedBufferAllocator.init(&temp_allocator_buffer); + const allocator = fba.allocator(); + + const bytes = try hex.hexToBytes(allocator, hex_str); + if (bytes.len > 32) { + return error.ValueTooLarge; + } + + // Pad with zeros if needed + var padded: [32]u8 = [_]u8{0} ** 32; + const offset = 32 - bytes.len; + @memcpy(padded[offset..], bytes); + + return u256FromBytes(padded); +} + +/// Convert u256 to hex string +pub fn u256ToHex(value: u256, allocator: std.mem.Allocator) ![]u8 { + const bytes = u256ToBytes(value); + return try hex.bytesToHex(allocator, &bytes); +} + +/// Try to convert u256 to u64 (returns error if too large) +pub fn u256ToU64(value: u256) !u64 { + if (value > std.math.maxInt(u64)) { + return error.ValueTooLarge; + } + return @intCast(value); +} + +/// Legacy U256 type - DEPRECATED, use native u256 instead +/// Kept for backwards compatibility during migration pub const U256 = struct { - limbs: [4]u64, // Little-endian: limbs[0] is least significant + value: u256, - /// Create from a u64 - pub fn fromInt(value: u64) U256 { - return .{ - .limbs = [_]u64{ value, 0, 0, 0 }, - }; + pub fn fromInt(v: u64) U256 { + return .{ .value = v }; } - /// Create zero pub fn zero() U256 { - return .{ - .limbs = [_]u64{ 0, 0, 0, 0 }, - }; + return .{ .value = 0 }; } - /// Create one pub fn one() U256 { - return .{ - .limbs = [_]u64{ 1, 0, 0, 0 }, - }; + return .{ .value = 1 }; } - /// Create max value (2^256 - 1) pub fn max() U256 { - return .{ - .limbs = [_]u64{ std.math.maxInt(u64), std.math.maxInt(u64), std.math.maxInt(u64), std.math.maxInt(u64) }, - }; + return .{ .value = std.math.maxInt(u256) }; } - /// Create from bytes (big-endian, 32 bytes) pub fn fromBytes(bytes: [32]u8) U256 { - var result = U256.zero(); - // Convert big-endian bytes to little-endian limbs - for (0..4) |i| { - const offset = (3 - i) * 8; // Read from end (big-endian) - result.limbs[i] = std.mem.readInt(u64, bytes[offset..][0..8], .big); - } - return result; + return .{ .value = u256FromBytes(bytes) }; } - /// Convert to bytes (big-endian, 32 bytes) pub fn toBytes(self: U256) [32]u8 { - var bytes: [32]u8 = undefined; - // Convert little-endian limbs to big-endian bytes - for (0..4) |i| { - const offset = (3 - i) * 8; // Write to end (big-endian) - std.mem.writeInt(u64, bytes[offset..][0..8], self.limbs[i], .big); - } - return bytes; + return u256ToBytes(self.value); } - /// Create from hex string pub fn fromHex(hex_str: []const u8) !U256 { - var temp_allocator_buffer: [1024]u8 = undefined; - var fba = std.heap.FixedBufferAllocator.init(&temp_allocator_buffer); - const allocator = fba.allocator(); - - const bytes = try hex.hexToBytes(allocator, hex_str); - if (bytes.len > 32) { - return error.ValueTooLarge; - } - - // Pad with zeros if needed - var padded: [32]u8 = [_]u8{0} ** 32; - const offset = 32 - bytes.len; - @memcpy(padded[offset..], bytes); - - return fromBytes(padded); + return .{ .value = try u256FromHex(hex_str) }; } - /// Convert to hex string pub fn toHex(self: U256, allocator: std.mem.Allocator) ![]u8 { - const bytes = self.toBytes(); - return try hex.bytesToHex(allocator, &bytes); + return u256ToHex(self.value, allocator); } - /// Check if zero pub fn isZero(self: U256) bool { - return self.limbs[0] == 0 and self.limbs[1] == 0 and self.limbs[2] == 0 and self.limbs[3] == 0; + return self.value == 0; } - /// Compare equality pub fn eql(self: U256, other: U256) bool { - return std.mem.eql(u64, &self.limbs, &other.limbs); + return self.value == other.value; } - /// Compare less than pub fn lt(self: U256, other: U256) bool { - var i: usize = 4; - while (i > 0) { - i -= 1; - if (self.limbs[i] < other.limbs[i]) return true; - if (self.limbs[i] > other.limbs[i]) return false; - } - return false; + return self.value < other.value; } - /// Compare less than or equal pub fn lte(self: U256, other: U256) bool { - return self.eql(other) or self.lt(other); + return self.value <= other.value; } - /// Compare greater than pub fn gt(self: U256, other: U256) bool { - return !self.lte(other); + return self.value > other.value; } - /// Compare greater than or equal pub fn gte(self: U256, other: U256) bool { - return !self.lt(other); + return self.value >= other.value; } - /// Add two U256 values pub fn add(self: U256, other: U256) U256 { - var result = U256.zero(); - var carry: u64 = 0; - - for (0..4) |i| { - const sum = @addWithOverflow(self.limbs[i], other.limbs[i]); - const sum_with_carry = @addWithOverflow(sum[0], carry); - - result.limbs[i] = sum_with_carry[0]; - carry = sum[1] + sum_with_carry[1]; - } - - return result; + return .{ .value = self.value +% other.value }; } - /// Subtract two U256 values (assumes self >= other) pub fn sub(self: U256, other: U256) U256 { - var result = U256.zero(); - var borrow: u64 = 0; - - for (0..4) |i| { - const diff = @subWithOverflow(self.limbs[i], other.limbs[i]); - const diff_with_borrow = @subWithOverflow(diff[0], borrow); - - result.limbs[i] = diff_with_borrow[0]; - borrow = diff[1] + diff_with_borrow[1]; - } - - return result; + return .{ .value = self.value -% other.value }; } - /// Multiply by a u64 pub fn mulScalar(self: U256, scalar: u64) U256 { - var result = U256.zero(); - var carry: u64 = 0; - - for (0..4) |i| { - const prod = @as(u128, self.limbs[i]) * @as(u128, scalar) + carry; - result.limbs[i] = @truncate(prod); - carry = @truncate(prod >> 64); - } - - return result; + return .{ .value = self.value *% scalar }; } - /// Divide by a u64, returns (quotient, remainder) pub fn divScalar(self: U256, scalar: u64) struct { quotient: U256, remainder: u64 } { - var quotient = U256.zero(); - var remainder: u64 = 0; - - var i: usize = 4; - while (i > 0) { - i -= 1; - const dividend = (@as(u128, remainder) << 64) | self.limbs[i]; - quotient.limbs[i] = @truncate(dividend / scalar); - remainder = @truncate(dividend % scalar); - } - - return .{ .quotient = quotient, .remainder = remainder }; + return .{ + .quotient = .{ .value = self.value / scalar }, + .remainder = @intCast(self.value % scalar), + }; } - /// Convert to u64 (truncates if too large) pub fn toU64(self: U256) u64 { - return self.limbs[0]; + return @intCast(self.value & std.math.maxInt(u64)); } - /// Try to convert to u64 (returns error if too large) pub fn tryToU64(self: U256) !u64 { - if (self.limbs[1] != 0 or self.limbs[2] != 0 or self.limbs[3] != 0) { - return error.ValueTooLarge; - } - return self.limbs[0]; + return u256ToU64(self.value); } - /// Format for printing pub fn format( self: U256, comptime fmt: []const u8, @@ -201,117 +159,49 @@ pub const U256 = struct { ) !void { _ = fmt; _ = options; - - // Simple decimal conversion for display - if (self.isZero()) { - try writer.print("0", .{}); - return; - } - - // For simplicity, just show hex representation - try writer.print("0x", .{}); - var started = false; - var i: usize = 4; - while (i > 0) { - i -= 1; - if (started or self.limbs[i] != 0) { - if (started) { - try writer.print("{x:0>16}", .{self.limbs[i]}); - } else { - try writer.print("{x}", .{self.limbs[i]}); - started = true; - } - } - } + try writer.print("0x{x}", .{self.value}); } }; -test "u256 from int" { - const val = U256.fromInt(42); - try std.testing.expectEqual(@as(u64, 42), val.limbs[0]); - try std.testing.expectEqual(@as(u64, 0), val.limbs[1]); -} - -test "u256 zero and one" { - const zero = U256.zero(); - const one = U256.one(); - - try std.testing.expect(zero.isZero()); - try std.testing.expect(!one.isZero()); - try std.testing.expectEqual(@as(u64, 1), one.limbs[0]); -} - test "u256 from bytes" { var bytes: [32]u8 = [_]u8{0} ** 32; bytes[31] = 42; // Last byte (big-endian) - const val = U256.fromBytes(bytes); - try std.testing.expectEqual(@as(u64, 42), val.limbs[0]); + const val = u256FromBytes(bytes); + try std.testing.expectEqual(@as(u256, 42), val); } test "u256 to bytes" { - const val = U256.fromInt(42); - const bytes = val.toBytes(); + const val: u256 = 42; + const bytes = u256ToBytes(val); try std.testing.expectEqual(@as(u8, 42), bytes[31]); try std.testing.expectEqual(@as(u8, 0), bytes[0]); } -test "u256 comparison" { - const a = U256.fromInt(100); - const b = U256.fromInt(200); - const c = U256.fromInt(100); - - try std.testing.expect(a.lt(b)); - try std.testing.expect(!b.lt(a)); - try std.testing.expect(a.eql(c)); - try std.testing.expect(!a.eql(b)); -} - -test "u256 addition" { - const a = U256.fromInt(100); - const b = U256.fromInt(50); - const result = a.add(b); - - try std.testing.expectEqual(@as(u64, 150), result.limbs[0]); -} - -test "u256 subtraction" { - const a = U256.fromInt(100); - const b = U256.fromInt(50); - const result = a.sub(b); - - try std.testing.expectEqual(@as(u64, 50), result.limbs[0]); -} +test "u256 from hex" { + const allocator = std.testing.allocator; -test "u256 multiplication" { - const a = U256.fromInt(100); - const result = a.mulScalar(3); + const val = try u256FromHex("0x2a"); // 42 in decimal + try std.testing.expectEqual(@as(u256, 42), val); - try std.testing.expectEqual(@as(u64, 300), result.limbs[0]); + const hex_str = try u256ToHex(val, allocator); + defer allocator.free(hex_str); } -test "u256 division" { - const a = U256.fromInt(100); - const result = a.divScalar(3); +test "u256 to u64" { + const val: u256 = 42; + try std.testing.expectEqual(@as(u64, 42), try u256ToU64(val)); - try std.testing.expectEqual(@as(u64, 33), result.quotient.limbs[0]); - try std.testing.expectEqual(@as(u64, 1), result.remainder); + const too_large: u256 = @as(u256, std.math.maxInt(u64)) + 1; + try std.testing.expectError(error.ValueTooLarge, u256ToU64(too_large)); } -test "u256 from hex" { - const allocator = std.testing.allocator; - - const val = try U256.fromHex("0x2a"); // 42 in decimal - try std.testing.expectEqual(@as(u64, 42), val.limbs[0]); - - const hex_str = try val.toHex(allocator); - defer allocator.free(hex_str); -} +test "legacy U256 wrapper" { + const val = U256.fromInt(42); + try std.testing.expectEqual(@as(u256, 42), val.value); + try std.testing.expect(!val.isZero()); -test "u256 max value" { - const max_val = U256.max(); - try std.testing.expect(!max_val.isZero()); - try std.testing.expectEqual(std.math.maxInt(u64), max_val.limbs[0]); - try std.testing.expectEqual(std.math.maxInt(u64), max_val.limbs[3]); + const zero = U256.zero(); + try std.testing.expect(zero.isZero()); } diff --git a/src/providers/http.zig b/src/providers/http.zig index 489aaba..e2f3471 100644 --- a/src/providers/http.zig +++ b/src/providers/http.zig @@ -26,7 +26,7 @@ pub const HttpProvider = struct { return try self.provider.getBlockNumber(); } - pub fn getBalance(self: HttpProvider, address: @import("../primitives/address.zig").Address) !@import("../primitives/uint.zig").U256 { + pub fn getBalance(self: HttpProvider, address: @import("../primitives/address.zig").Address) !u256 { return try self.provider.getBalance(address); } @@ -38,7 +38,7 @@ pub const HttpProvider = struct { return try self.provider.getTransactionCount(address); } - pub fn getGasPrice(self: HttpProvider) !@import("../primitives/uint.zig").U256 { + pub fn getGasPrice(self: HttpProvider) !u256 { return try self.provider.getGasPrice(); } diff --git a/src/providers/provider.zig b/src/providers/provider.zig index 78fbb3e..bff6f02 100644 --- a/src/providers/provider.zig +++ b/src/providers/provider.zig @@ -6,7 +6,7 @@ const Web3Namespace = @import("../rpc/web3.zig").Web3Namespace; const DebugNamespace = @import("../rpc/debug.zig").DebugNamespace; const Address = @import("../primitives/address.zig").Address; const Hash = @import("../primitives/hash.zig").Hash; -const U256 = @import("../primitives/uint.zig").U256; +const uint_utils = @import("../primitives/uint.zig"); const Block = @import("../types/block.zig").Block; const Transaction = @import("../types/transaction.zig").Transaction; const Receipt = @import("../types/receipt.zig").Receipt; @@ -57,7 +57,7 @@ pub const Provider = struct { } /// Get account balance - pub fn getBalance(self: Provider, address: Address) !U256 { + pub fn getBalance(self: Provider, address: Address) !u256 { return try self.eth.getBalance(address, .{ .tag = .latest }); } @@ -82,7 +82,7 @@ pub const Provider = struct { } /// Get gas price - pub fn getGasPrice(self: Provider) !U256 { + pub fn getGasPrice(self: Provider) !u256 { return try self.eth.gasPrice(); } diff --git a/src/rlp/packed.zig b/src/rlp/packed.zig index 17750b5..41d7a62 100644 --- a/src/rlp/packed.zig +++ b/src/rlp/packed.zig @@ -3,7 +3,8 @@ 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; +const U256 = @import("../primitives/uint.zig").U256; // Legacy compatibility +const uint_utils = @import("../primitives/uint.zig"); const Signature = @import("../primitives/signature.zig").Signature; const Transaction = @import("../types/transaction.zig").Transaction; const TransactionType = @import("../types/transaction.zig").TransactionType; @@ -26,7 +27,7 @@ pub const TransactionEncoder = struct { try items.append(.{ .uint = tx.nonce }); // gas_price - const gas_price_bytes = tx.gas_price.?.toBytes(); + const gas_price_bytes = uint_utils.u256ToBytes(tx.gas_price.?); try items.append(.{ .bytes = &gas_price_bytes }); // gas_limit @@ -40,7 +41,7 @@ pub const TransactionEncoder = struct { } // value - const value_bytes = tx.value.toBytes(); + const value_bytes = uint_utils.u256ToBytes(tx.value); try items.append(.{ .bytes = &value_bytes }); // data @@ -76,7 +77,7 @@ pub const TransactionEncoder = struct { try items.append(.{ .uint = tx.nonce }); // gas_price - const gas_price_bytes = tx.gas_price.?.toBytes(); + const gas_price_bytes = uint_utils.u256ToBytes(tx.gas_price.?); try items.append(.{ .bytes = &gas_price_bytes }); // gas_limit @@ -90,7 +91,7 @@ pub const TransactionEncoder = struct { } // value - const value_bytes = tx.value.toBytes(); + const value_bytes = uint_utils.u256ToBytes(tx.value); try items.append(.{ .bytes = &value_bytes }); // data @@ -100,11 +101,9 @@ pub const TransactionEncoder = struct { const sig = tx.signature.?; try items.append(.{ .uint = sig.v }); - const r_bytes = sig.r.toBytes(); - try items.append(.{ .bytes = &r_bytes }); - - const s_bytes = sig.s.toBytes(); - try items.append(.{ .bytes = &s_bytes }); + // r and s are already [32]u8 arrays + try items.append(.{ .bytes = &sig.r }); + try items.append(.{ .bytes = &sig.s }); return try encode.encodeList(allocator, items.items); } diff --git a/src/root.zig b/src/root.zig index cb5958e..ce607e8 100644 --- a/src/root.zig +++ b/src/root.zig @@ -11,7 +11,22 @@ pub const primitives = struct { pub const Hash = @import("primitives/hash.zig").Hash; 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 + pub const u256FromBytes = @import("primitives/uint.zig").u256FromBytes; + pub const u256ToBytes = @import("primitives/uint.zig").u256ToBytes; + pub const u256FromHex = @import("primitives/uint.zig").u256FromHex; + pub const u256ToHex = @import("primitives/uint.zig").u256ToHex; + pub const u256ToU64 = @import("primitives/uint.zig").u256ToU64; + pub const Bloom = @import("primitives/bloom.zig").Bloom; }; pub const types = struct { diff --git a/src/rpc/eth.zig b/src/rpc/eth.zig index 77743ed..72f0fb2 100644 --- a/src/rpc/eth.zig +++ b/src/rpc/eth.zig @@ -3,7 +3,7 @@ const RpcClient = @import("./client.zig").RpcClient; const types = @import("./types.zig"); const Address = @import("../primitives/address.zig").Address; const Hash = @import("../primitives/hash.zig").Hash; -const U256 = @import("../primitives/uint.zig").U256; +const uint_utils = @import("../primitives/uint.zig"); const Bytes = @import("../primitives/bytes.zig").Bytes; const Signature = @import("../primitives/signature.zig").Signature; const Block = @import("../types/block.zig").Block; @@ -33,7 +33,7 @@ pub const EthNamespace = struct { } /// eth_getBalance - Returns the balance of an account - pub fn getBalance(self: EthNamespace, address: Address, block: types.BlockParameter) !U256 { + pub fn getBalance(self: EthNamespace, address: Address, block: types.BlockParameter) !u256 { const addr_hex = try address.toHex(self.client.allocator); defer self.client.allocator.free(addr_hex); @@ -51,7 +51,7 @@ pub const EthNamespace = struct { return error.InvalidResponse; } - return try U256.fromHex(result.string); + return try uint_utils.u256FromHex(result.string); } /// eth_getTransactionCount - Returns the number of transactions sent from an address @@ -209,25 +209,25 @@ pub const EthNamespace = struct { } /// eth_gasPrice - Returns the current gas price in wei - pub fn gasPrice(self: EthNamespace) !U256 { + pub fn gasPrice(self: EthNamespace) !u256 { const result = try self.client.callNoParams("eth_gasPrice"); if (result != .string) { return error.InvalidResponse; } - return try U256.fromHex(result.string); + return try uint_utils.u256FromHex(result.string); } /// eth_maxPriorityFeePerGas - Returns the current max priority fee per gas - pub fn maxPriorityFeePerGas(self: EthNamespace) !U256 { + pub fn maxPriorityFeePerGas(self: EthNamespace) !u256 { const result = try self.client.callNoParams("eth_maxPriorityFeePerGas"); if (result != .string) { return error.InvalidResponse; } - return try U256.fromHex(result.string); + return try uint_utils.u256FromHex(result.string); } /// eth_feeHistory - Returns historical gas information @@ -290,11 +290,11 @@ pub const EthNamespace = struct { } /// eth_getStorageAt - Returns the value from a storage position - pub fn getStorageAt(self: EthNamespace, address: Address, position: U256, block: types.BlockParameter) !Hash { + pub fn getStorageAt(self: EthNamespace, address: Address, position: u256, block: types.BlockParameter) !Hash { const addr_hex = try address.toHex(self.client.allocator); defer self.client.allocator.free(addr_hex); - const position_hex = try position.toHex(self.client.allocator); + const position_hex = try uint_utils.u256ToHex(position, self.client.allocator); defer self.client.allocator.free(position_hex); const block_param = try blockParameterToString(self.client.allocator, block); @@ -815,7 +815,7 @@ fn parseTransactionFromJson(allocator: std.mem.Allocator, obj: std.json.ObjectMa const value_str = obj.get("value") orelse return error.MissingField; if (value_str != .string) return error.InvalidFieldType; - const value = try U256.fromHex(value_str.string); + const value = try uint_utils.u256FromHex(value_str.string); const data_str = obj.get("input") orelse obj.get("data") orelse return error.MissingField; if (data_str != .string) return error.InvalidFieldType; @@ -832,14 +832,14 @@ fn parseTransactionFromJson(allocator: std.mem.Allocator, obj: std.json.ObjectMa } // Parse gas price fields based on transaction type - var gas_price: ?U256 = null; - var max_fee_per_gas: ?U256 = null; - var max_priority_fee_per_gas: ?U256 = null; + var gas_price: ?u256 = null; + var max_fee_per_gas: ?u256 = null; + var max_priority_fee_per_gas: ?u256 = null; if (tx_type == .legacy or tx_type == .eip2930) { if (obj.get("gasPrice")) |gp| { if (gp == .string) { - gas_price = try U256.fromHex(gp.string); + gas_price = try uint_utils.u256FromHex(gp.string); } } } @@ -847,12 +847,12 @@ fn parseTransactionFromJson(allocator: std.mem.Allocator, obj: std.json.ObjectMa if (tx_type == .eip1559 or tx_type == .eip4844) { if (obj.get("maxFeePerGas")) |max_fee| { if (max_fee == .string) { - max_fee_per_gas = try U256.fromHex(max_fee.string); + max_fee_per_gas = try uint_utils.u256FromHex(max_fee.string); } } if (obj.get("maxPriorityFeePerGas")) |max_priority| { if (max_priority == .string) { - max_priority_fee_per_gas = try U256.fromHex(max_priority.string); + max_priority_fee_per_gas = try uint_utils.u256FromHex(max_priority.string); } } } @@ -925,10 +925,10 @@ fn parseTransactionFromJson(allocator: std.mem.Allocator, obj: std.json.ObjectMa if (v_val != null and r_val != null and s_val != null) { if (v_val.? == .string and r_val.? == .string and s_val.? == .string) { const v = try parseHexU64(v_val.?.string); - const r = try U256.fromHex(r_val.?.string); - const s = try U256.fromHex(s_val.?.string); + const r = try uint_utils.u256FromHex(r_val.?.string); + const s = try uint_utils.u256FromHex(s_val.?.string); - tx.signature = Signature.init(r.toBytes(), s.toBytes(), @intCast(v)); + tx.signature = Signature.init(uint_utils.u256ToBytes(r), uint_utils.u256ToBytes(s), @intCast(v)); } } @@ -978,7 +978,7 @@ fn parseReceiptFromJson(allocator: std.mem.Allocator, obj: std.json.ObjectMap) ! const eff_gas_price_str = obj.get("effectiveGasPrice") orelse return error.MissingField; if (eff_gas_price_str != .string) return error.InvalidFieldType; - const effective_gas_price = try U256.fromHex(eff_gas_price_str.string); + const effective_gas_price = try uint_utils.u256FromHex(eff_gas_price_str.string); // Parse logs const logs_json = obj.get("logs") orelse return error.MissingField; @@ -1088,7 +1088,7 @@ fn parseBlockFromJson(allocator: std.mem.Allocator, obj: std.json.ObjectMap, ful const difficulty_str = obj.get("difficulty") orelse return error.MissingField; if (difficulty_str != .string) return error.InvalidFieldType; - const difficulty = try U256.fromHex(difficulty_str.string); + const difficulty = try uint_utils.u256FromHex(difficulty_str.string); const number_str = obj.get("number") orelse return error.MissingField; if (number_str != .string) return error.InvalidFieldType; @@ -1121,10 +1121,10 @@ fn parseBlockFromJson(allocator: std.mem.Allocator, obj: std.json.ObjectMap, ful const block_nonce = try parseHexU64(nonce_str.string); // Optional fields for different forks - var base_fee_per_gas: ?U256 = null; + var base_fee_per_gas: ?u256 = null; if (obj.get("baseFeePerGas")) |base_fee| { if (base_fee == .string) { - base_fee_per_gas = try U256.fromHex(base_fee.string); + base_fee_per_gas = try uint_utils.u256FromHex(base_fee.string); } } @@ -1211,7 +1211,7 @@ fn parseBlockFromJson(allocator: std.mem.Allocator, obj: std.json.ObjectMap, ful .gas_price = null, .max_fee_per_gas = null, .max_priority_fee_per_gas = null, - .value = U256.zero(), + .value = 0, .data = empty_data, .chain_id = null, .access_list = null, @@ -1236,7 +1236,7 @@ fn parseBlockFromJson(allocator: std.mem.Allocator, obj: std.json.ObjectMap, ful .header = header, .transactions = try transactions.toOwnedSlice(), .uncles = &[_]Hash{}, // TODO: Parse uncles from JSON - .total_difficulty = U256.fromInt(0), // TODO: Parse total difficulty + .total_difficulty = 0, // TODO: Parse total difficulty .size = 0, // TODO: Parse size from JSON .allocator = allocator, }; diff --git a/src/types/block.zig b/src/types/block.zig index 3398e5f..d0632d0 100644 --- a/src/types/block.zig +++ b/src/types/block.zig @@ -2,7 +2,6 @@ const std = @import("std"); const Address = @import("../primitives/address.zig").Address; const Hash = @import("../primitives/hash.zig").Hash; const Bloom = @import("../primitives/bloom.zig").Bloom; -const U256 = @import("../primitives/uint.zig").U256; const Bytes = @import("../primitives/bytes.zig").Bytes; const Transaction = @import("./transaction.zig").Transaction; @@ -30,7 +29,7 @@ pub const BlockHeader = struct { logs_bloom: Bloom, /// Difficulty - difficulty: U256, + difficulty: u256, /// Block number number: u64, @@ -54,7 +53,7 @@ pub const BlockHeader = struct { nonce: u64, /// Base fee per gas (EIP-1559, post-London) - base_fee_per_gas: ?U256, + base_fee_per_gas: ?u256, /// Withdrawals root (post-Shanghai) withdrawals_root: ?Hash, @@ -108,7 +107,7 @@ pub const Block = struct { uncles: []Hash, /// Total difficulty up to this block - total_difficulty: U256, + total_difficulty: u256, /// Block size in bytes size: u64, @@ -122,7 +121,7 @@ pub const Block = struct { header: BlockHeader, transactions: []const Transaction, uncles: []const Hash, - total_difficulty: U256, + total_difficulty: u256, size: u64, ) !Block { const transactions_copy = try allocator.dupe(Transaction, transactions); @@ -176,10 +175,10 @@ pub const Block = struct { } /// Calculate block reward (simplified - doesn't include uncle rewards) - pub fn calculateBaseReward(self: Block) U256 { + pub fn calculateBaseReward(self: Block) u256 { // Post-merge: no block reward if (self.header.isPostMerge()) { - return U256.zero(); + return 0; } // Pre-merge block rewards: @@ -189,11 +188,11 @@ pub const Block = struct { const block_num = self.header.number; if (block_num < 4_370_000) { - return U256.fromInt(5_000_000_000_000_000_000); // 5 ETH + return @as(u256, 5_000_000_000_000_000_000); // 5 ETH } else if (block_num < 7_280_000) { - return U256.fromInt(3_000_000_000_000_000_000); // 3 ETH + return @as(u256, 3_000_000_000_000_000_000); // 3 ETH } else { - return U256.fromInt(2_000_000_000_000_000_000); // 2 ETH + return @as(u256, 2_000_000_000_000_000_000); // 2 ETH } } @@ -220,7 +219,7 @@ test "block header post-merge detection" { .transactions_root = Hash.zero(), .receipts_root = Hash.zero(), .logs_bloom = Bloom.empty(), - .difficulty = U256.zero(), // Post-merge: difficulty = 0 + .difficulty = 0, // Post-merge: difficulty = 0 .number = 15537394, .gas_limit = 30000000, .gas_used = 15000000, @@ -228,7 +227,7 @@ test "block header post-merge detection" { .extra_data = Bytes.empty(std.testing.allocator), .mix_hash = Hash.zero(), .nonce = 0, - .base_fee_per_gas = U256.fromInt(15000000000), + .base_fee_per_gas = @as(u256, 15000000000), .withdrawals_root = null, .blob_gas_used = null, .excess_blob_gas = null, @@ -253,7 +252,7 @@ test "block creation" { .transactions_root = Hash.zero(), .receipts_root = Hash.zero(), .logs_bloom = Bloom.empty(), - .difficulty = U256.fromInt(1000000), + .difficulty = @as(u256, 1000000), .number = 12345, .gas_limit = 30000000, .gas_used = 15000000, @@ -274,7 +273,7 @@ test "block creation" { header, &[_]Transaction{}, // no transactions &[_]Hash{}, // no uncles - U256.fromInt(5000000), + @as(u256, 5000000), 1024, ); defer block.deinit(); @@ -296,7 +295,7 @@ test "block gas utilization" { .transactions_root = Hash.zero(), .receipts_root = Hash.zero(), .logs_bloom = Bloom.empty(), - .difficulty = U256.fromInt(1000000), + .difficulty = @as(u256, 1000000), .number = 12345, .gas_limit = 30000000, .gas_used = 15000000, // 50% utilization @@ -317,7 +316,7 @@ test "block gas utilization" { header, &[_]Transaction{}, &[_]Hash{}, - U256.fromInt(5000000), + @as(u256, 5000000), 1024, ); defer block.deinit(); @@ -340,7 +339,7 @@ test "block reward calculation" { .transactions_root = Hash.zero(), .receipts_root = Hash.zero(), .logs_bloom = Bloom.empty(), - .difficulty = U256.fromInt(1000000), // Non-zero difficulty + .difficulty = @as(u256, 1000000), // Non-zero difficulty .number = 1000000, .gas_limit = 30000000, .gas_used = 15000000, @@ -361,11 +360,11 @@ test "block reward calculation" { header, &[_]Transaction{}, &[_]Hash{}, - U256.fromInt(5000000), + @as(u256, 5000000), 1024, ); defer block.deinit(); const reward = block.calculateBaseReward(); - try std.testing.expect(reward.eql(U256.fromInt(5_000_000_000_000_000_000))); + try std.testing.expectEqual(@as(u256, 5_000_000_000_000_000_000), reward); } diff --git a/src/types/receipt.zig b/src/types/receipt.zig index 101b89b..92bf1ab 100644 --- a/src/types/receipt.zig +++ b/src/types/receipt.zig @@ -2,7 +2,6 @@ const std = @import("std"); const Address = @import("../primitives/address.zig").Address; const Hash = @import("../primitives/hash.zig").Hash; const Bloom = @import("../primitives/bloom.zig").Bloom; -const U256 = @import("../primitives/uint.zig").U256; const Log = @import("./log.zig").Log; /// Transaction execution status @@ -40,7 +39,7 @@ pub const Receipt = struct { gas_used: u64, /// Effective gas price paid - effective_gas_price: U256, + effective_gas_price: u256, /// Contract address created (if contract creation transaction) contract_address: ?Address, @@ -73,7 +72,7 @@ pub const Receipt = struct { to: ?Address, cumulative_gas_used: u64, gas_used: u64, - effective_gas_price: U256, + effective_gas_price: u256, logs: []const Log, logs_bloom: Bloom, status: TransactionStatus, @@ -133,8 +132,8 @@ pub const Receipt = struct { } /// Calculate transaction fee (gas_used * effective_gas_price) - pub fn calculateFee(self: Receipt) U256 { - return self.effective_gas_price.mulScalar(self.gas_used); + pub fn calculateFee(self: Receipt) u256 { + return self.effective_gas_price * self.gas_used; } /// Check if receipt contains logs from a specific address @@ -180,7 +179,7 @@ test "receipt creation" { to, 21000, // cumulative_gas_used 21000, // gas_used - U256.fromInt(20000000000), // effective_gas_price + @as(u256, 20000000000), // effective_gas_price &[_]Log{}, // no logs Bloom.empty(), .success, @@ -209,7 +208,7 @@ test "receipt transaction fee" { null, 21000, 21000, - U256.fromInt(20000000000), // 20 gwei + @as(u256, 20000000000), // 20 gwei &[_]Log{}, Bloom.empty(), .success, @@ -218,7 +217,7 @@ test "receipt transaction fee" { const fee = receipt.calculateFee(); // 21000 * 20000000000 = 420000000000000 - try std.testing.expect(fee.eql(U256.fromInt(420000000000000))); + try std.testing.expectEqual(@as(u256, 420000000000000), fee); } test "receipt success and failure" { @@ -239,7 +238,7 @@ test "receipt success and failure" { null, 21000, 21000, - U256.fromInt(20000000000), + @as(u256, 20000000000), &[_]Log{}, Bloom.empty(), .success, @@ -260,7 +259,7 @@ test "receipt success and failure" { null, 21000, 21000, - U256.fromInt(20000000000), + @as(u256, 20000000000), &[_]Log{}, Bloom.empty(), .failed, @@ -289,7 +288,7 @@ test "receipt contract creation" { null, // no 'to' for contract creation 100000, 100000, - U256.fromInt(20000000000), + @as(u256, 20000000000), &[_]Log{}, Bloom.empty(), .success, diff --git a/src/types/transaction.zig b/src/types/transaction.zig index e2fddd6..04603dd 100644 --- a/src/types/transaction.zig +++ b/src/types/transaction.zig @@ -3,7 +3,6 @@ const Address = @import("../primitives/address.zig").Address; const Hash = @import("../primitives/hash.zig").Hash; const Bytes = @import("../primitives/bytes.zig").Bytes; const Signature = @import("../primitives/signature.zig").Signature; -const U256 = @import("../primitives/uint.zig").U256; const AccessList = @import("./access_list.zig").AccessList; /// Transaction type @@ -31,8 +30,8 @@ pub const Authorization = struct { nonce: u64, /// Authorization signature (y_parity, r, s) y_parity: u8, - r: U256, - s: U256, + r: u256, + s: u256, }; /// Authorization list for EIP-7702 transactions @@ -93,16 +92,16 @@ pub const Transaction = struct { gas_limit: u64, /// Gas price (legacy and EIP-2930) - gas_price: ?U256, + gas_price: ?u256, /// Max fee per gas (EIP-1559) - max_fee_per_gas: ?U256, + max_fee_per_gas: ?u256, /// Max priority fee per gas (EIP-1559) - max_priority_fee_per_gas: ?U256, + max_priority_fee_per_gas: ?u256, /// Value to transfer (in wei) - value: U256, + value: u256, /// Transaction data / input data: Bytes, @@ -137,11 +136,11 @@ pub const Transaction = struct { pub fn newLegacy( allocator: std.mem.Allocator, to: ?Address, - value: U256, + value: u256, data: Bytes, nonce: u64, gas_limit: u64, - gas_price: U256, + gas_price: u256, ) Transaction { return .{ .type = .legacy, @@ -170,12 +169,12 @@ pub const Transaction = struct { pub fn newEip1559( allocator: std.mem.Allocator, to: ?Address, - value: U256, + value: u256, data: Bytes, nonce: u64, gas_limit: u64, - max_fee_per_gas: U256, - max_priority_fee_per_gas: U256, + max_fee_per_gas: u256, + max_priority_fee_per_gas: u256, chain_id: u64, access_list: ?AccessList, ) Transaction { @@ -206,11 +205,11 @@ pub const Transaction = struct { pub fn newEip2930( allocator: std.mem.Allocator, to: ?Address, - value: U256, + value: u256, data: Bytes, nonce: u64, gas_limit: u64, - gas_price: U256, + gas_price: u256, chain_id: u64, access_list: AccessList, ) Transaction { @@ -241,12 +240,12 @@ pub const Transaction = struct { pub fn newEip7702( allocator: std.mem.Allocator, to: ?Address, - value: U256, + value: u256, data: Bytes, nonce: u64, gas_limit: u64, - max_fee_per_gas: U256, - max_priority_fee_per_gas: U256, + max_fee_per_gas: u256, + max_priority_fee_per_gas: u256, chain_id: u64, access_list: ?AccessList, authorization_list: AuthorizationList, @@ -296,7 +295,7 @@ pub const Transaction = struct { } /// Get the effective gas price - pub fn getGasPrice(self: Transaction) ?U256 { + pub fn getGasPrice(self: Transaction) ?u256 { return switch (self.type) { .legacy, .eip2930 => self.gas_price, .eip1559, .eip4844, .eip7702 => self.max_fee_per_gas, @@ -318,7 +317,7 @@ test "legacy transaction creation" { const allocator = std.testing.allocator; const to = Address.fromBytes([_]u8{0x12} ** 20); - const value = U256.fromInt(1000); + const value = @as(u256, 1000); const data = try Bytes.fromSlice(allocator, &[_]u8{ 1, 2, 3 }); const tx = Transaction.newLegacy( @@ -328,7 +327,7 @@ test "legacy transaction creation" { data, 0, // nonce 21000, // gas_limit - U256.fromInt(20000000000), // gas_price (20 gwei) + @as(u256, 20000000000), // gas_price (20 gwei) ); defer tx.deinit(); @@ -341,7 +340,7 @@ test "eip1559 transaction creation" { const allocator = std.testing.allocator; const to = Address.fromBytes([_]u8{0x12} ** 20); - const value = U256.fromInt(1000); + const value = @as(u256, 1000); const data = try Bytes.fromSlice(allocator, &[_]u8{}); const tx = Transaction.newEip1559( @@ -351,8 +350,8 @@ test "eip1559 transaction creation" { data, 5, // nonce 21000, // gas_limit - U256.fromInt(30000000000), // max_fee_per_gas - U256.fromInt(2000000000), // max_priority_fee_per_gas + @as(u256, 30000000000), // max_fee_per_gas + @as(u256, 2000000000), // max_priority_fee_per_gas 1, // chain_id (mainnet) null, // no access list ); @@ -371,11 +370,11 @@ test "contract creation transaction" { const tx = Transaction.newLegacy( allocator, null, // no recipient = contract creation - U256.zero(), + 0, bytecode, 0, 100000, - U256.fromInt(20000000000), + @as(u256, 20000000000), ); defer tx.deinit(); @@ -392,28 +391,28 @@ test "transaction gas price" { const legacy_tx = Transaction.newLegacy( allocator, to, - U256.zero(), + 0, data, 0, 21000, - U256.fromInt(20000000000), + @as(u256, 20000000000), ); const legacy_price = legacy_tx.getGasPrice(); try std.testing.expect(legacy_price != null); - try std.testing.expect(legacy_price.?.eql(U256.fromInt(20000000000))); + try std.testing.expectEqual(@as(u256, 20000000000), legacy_price.?); // EIP-1559 transaction const data2 = try Bytes.fromSlice(allocator, &[_]u8{}); const eip1559_tx = Transaction.newEip1559( allocator, to, - U256.zero(), + 0, data2, 0, 21000, - U256.fromInt(30000000000), - U256.fromInt(2000000000), + @as(u256, 30000000000), + @as(u256, 2000000000), 1, null, ); @@ -421,7 +420,7 @@ test "transaction gas price" { const eip1559_price = eip1559_tx.getGasPrice(); try std.testing.expect(eip1559_price != null); - try std.testing.expect(eip1559_price.?.eql(U256.fromInt(30000000000))); + try std.testing.expectEqual(@as(u256, 30000000000), eip1559_price.?); } test "transaction pending status" { @@ -433,11 +432,11 @@ test "transaction pending status" { var tx = Transaction.newLegacy( allocator, to, - U256.zero(), + 0, data, 0, 21000, - U256.fromInt(20000000000), + @as(u256, 20000000000), ); defer tx.deinit(); @@ -455,8 +454,8 @@ test "authorization list" { .address = Address.fromBytes([_]u8{0x12} ** 20), .nonce = 5, .y_parity = 0, - .r = U256.fromInt(12345), - .s = U256.fromInt(67890), + .r = @as(u256, 12345), + .s = @as(u256, 67890), }; const auths = [_]Authorization{auth}; @@ -472,7 +471,7 @@ test "eip7702 transaction creation" { const allocator = std.testing.allocator; const to = Address.fromBytes([_]u8{0x12} ** 20); - const value = U256.fromInt(1000); + const value = @as(u256, 1000); const data = try Bytes.fromSlice(allocator, &[_]u8{ 1, 2, 3 }); const auth = Authorization{ @@ -480,8 +479,8 @@ test "eip7702 transaction creation" { .address = Address.fromBytes([_]u8{0xAB} ** 20), .nonce = 0, .y_parity = 1, - .r = U256.fromInt(11111), - .s = U256.fromInt(22222), + .r = @as(u256, 11111), + .s = @as(u256, 22222), }; const auths = [_]Authorization{auth}; @@ -494,8 +493,8 @@ test "eip7702 transaction creation" { data, 5, // nonce 21000, // gas_limit - U256.fromInt(30000000000), // max_fee_per_gas - U256.fromInt(2000000000), // max_priority_fee_per_gas + @as(u256, 30000000000), // max_fee_per_gas + @as(u256, 2000000000), // max_priority_fee_per_gas 1, // chain_id (mainnet) null, // no access list auth_list, @@ -519,12 +518,12 @@ test "eip7702 gas price" { const tx = Transaction.newEip7702( allocator, to, - U256.zero(), + 0, data, 0, 21000, - U256.fromInt(30000000000), // max_fee_per_gas - U256.fromInt(2000000000), // max_priority_fee_per_gas + @as(u256, 30000000000), // max_fee_per_gas + @as(u256, 2000000000), // max_priority_fee_per_gas 1, null, auth_list, @@ -533,5 +532,5 @@ test "eip7702 gas price" { const gas_price = tx.getGasPrice(); try std.testing.expect(gas_price != null); - try std.testing.expect(gas_price.?.eql(U256.fromInt(30000000000))); + try std.testing.expectEqual(@as(u256, 30000000000), gas_price.?); } diff --git a/src/utils/units.zig b/src/utils/units.zig index b17599e..0124827 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; +const U256 = @import("../primitives/uint.zig").U256; // Legacy compatibility /// Ethereum unit denominations pub const Unit = enum { @@ -83,9 +83,17 @@ pub fn etherToWei(ether: f64) !U256 { return U256.fromInt(@as(u64, @intFromFloat(wei_value))); } -/// Convert wei to ether as a floating point -pub fn weiToEther(wei: U256) !f64 { - const wei_u64 = try wei.tryToU64(); +/// 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; }