Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 24 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
# Zigeth

[![CI](https://github.com/yourusername/zigeth/actions/workflows/ci.yml/badge.svg)](https://github.com/yourusername/zigeth/actions/workflows/ci.yml)
[![CI](https://github.com/ch4r10t33r/zigeth/actions/workflows/ci.yml/badge.svg)](https://github.com/ch4r10t33r/zigeth/actions/workflows/ci.yml)
[![Zig](https://img.shields.io/badge/Zig-0.14.1-orange.svg)](https://ziglang.org/)
[![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)

A comprehensive Ethereum library for Zig, providing complete cryptographic primitives, transaction handling, RPC client framework, and utilities for seamless integration with Ethereum networks.

**Current Status**: 109 tests passing | 35% complete | Production-ready crypto & primitives
**Current Status**: 132 tests passing | 40% complete | Production-ready crypto, ABI & primitives

## 🏗️ Architecture

Expand Down Expand Up @@ -37,11 +37,11 @@ zigeth/
│ │ ├── ecdsa.zig # Digital signatures ✅
│ │ └── utils.zig # Crypto utilities ✅
│ │
│ ├── abi/ # Application Binary Interface (TODO)
│ │ ├── encode.zig # ABI encoding
│ │ ├── decode.zig # ABI decoding
│ │ ├── types.zig # ABI type definitions
│ │ └── packed.zig # Packed encoding
│ ├── abi/ # Application Binary Interface ✅ IMPLEMENTED
│ │ ├── encode.zig # ABI encoding
│ │ ├── decode.zig # ABI decoding
│ │ ├── types.zig # ABI type definitions
│ │ └── packed.zig # Packed encoding (EIP-712) ✅
│ │
│ ├── rlp/ # Recursive Length Prefix (TODO)
│ │ ├── encode.zig # RLP encoding
Expand Down Expand Up @@ -134,16 +134,24 @@ zigeth/
- `debug_*` namespace (7 methods)
- Type-safe request/response handling

- **📦 ABI Encoding/Decoding** (4 modules, 23 tests):
- Complete ABI type system (uint, int, address, bool, bytes, string, arrays, tuples)
- Standard ABI encoding (32-byte aligned, padded)
- Standard ABI decoding with type safety
- Packed encoding for EIP-712 and hashing
- Function selector generation
- Event signature generation

- **🧰 Utilities**:
- Hex encoding/decoding with 0x prefix support
- Memory-safe allocations
- Comprehensive error handling

### 🚧 **Planned Features**

- **📦 ABI & RLP**: Encoding/decoding for Ethereum data formats
- **🌐 Providers**: HTTP, WebSocket, IPC provider implementations
- **📝 Smart Contracts**: Contract deployment, interaction, and event parsing
- **📜 RLP Encoding**: Recursive Length Prefix for transaction encoding
- **🌐 Providers**: HTTP, WebSocket, IPC provider implementations with JSON-RPC
- **📝 Smart Contracts**: High-level contract deployment and interaction
- **🔑 Wallet Management**: Software wallets, keystore, and hardware wallet support
- **⚙️ Middleware**: Gas estimation, nonce management, and transaction signing
- **🌍 Network Support**: Pre-configured settings for major Ethereum networks
Expand All @@ -167,7 +175,7 @@ Add zigeth to your project's `build.zig.zon`:
```zig
.dependencies = .{
.zigeth = .{
.url = "https://github.com/yourusername/zigeth/archive/main.tar.gz",
.url = "https://github.com/ch4r10t33r/zigeth/archive/main.tar.gz",
.hash = "...", // Run `zig build` to get the hash
},
},
Expand Down Expand Up @@ -616,11 +624,12 @@ All Ethereum transaction types are fully supported:

## 📊 Testing & Quality

- **Total Tests**: 109 passing ✓
- **Total Tests**: 132 passing ✓
- Primitives: 48 tests
- Types: 23 tests
- Crypto: 27 tests
- RPC: 13 tests
- ABI: 23 tests
- Utilities: 8 tests
- **Code Coverage**: Comprehensive
- **Linting**: Enforced via `zig build lint`
Expand All @@ -635,6 +644,7 @@ All Ethereum transaction types are fully supported:
- [x] Primitives (Address, Hash, Signature, U256, Bloom, Bytes)
- [x] Protocol Types (Transaction, Block, Receipt, Log)
- [x] Cryptography (Keccak-256, ECDSA, secp256k1)
- [x] ABI encoding/decoding (standard & packed)
- [x] Build system & CI/CD

### Phase 2: Communication Layer 🚧 In Progress
Expand All @@ -644,9 +654,9 @@ All Ethereum transaction types are fully supported:
- [ ] JSON serialization/deserialization
- [ ] WebSocket support

### Phase 3: Data Encoding ⏳ Planned
### Phase 3: Data Encoding 🚧 In Progress
- [x] ABI encoding/decoding (standard & packed)
- [ ] RLP encoding/decoding
- [ ] ABI encoding/decoding
- [ ] Typed data signing (EIP-712)

### Phase 4: High-Level APIs ⏳ Planned
Expand Down
218 changes: 218 additions & 0 deletions src/abi/decode.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
const std = @import("std");
const Address = @import("../primitives/address.zig").Address;
const U256 = @import("../primitives/uint.zig").U256;
const types = @import("./types.zig");

/// ABI decoder for Ethereum smart contract responses
pub const Decoder = struct {
allocator: std.mem.Allocator,
data: []const u8,
position: usize,

pub fn init(allocator: std.mem.Allocator, data: []const u8) Decoder {
return .{
.allocator = allocator,
.data = data,
.position = 0,
};
}

/// Read 32 bytes at current position
fn read32(self: *Decoder) ![]const u8 {
if (self.position + 32 > self.data.len) {
return error.InsufficientData;
}
const slice = self.data[self.position .. self.position + 32];
self.position += 32;
return slice;
}

/// Decode a uint256
pub fn decodeUint256(self: *Decoder) !U256 {
const bytes = try self.read32();
var arr: [32]u8 = undefined;
@memcpy(&arr, bytes);
return U256.fromBytes(arr);
}

/// Decode a uint of any size
pub fn decodeUint(self: *Decoder) !u256 {
const bytes = try self.read32();
return std.mem.readInt(u256, bytes[0..32], .big);
}

/// Decode an address
pub fn decodeAddress(self: *Decoder) !Address {
const bytes = try self.read32();
// Address is the last 20 bytes
var addr_bytes: [20]u8 = undefined;
@memcpy(&addr_bytes, bytes[12..32]);
return Address.fromBytes(addr_bytes);
}

/// Decode a boolean
pub fn decodeBool(self: *Decoder) !bool {
const bytes = try self.read32();
return bytes[31] != 0;
}

/// Decode fixed-size bytes
pub fn decodeFixedBytes(self: *Decoder, size: usize) ![]u8 {
if (size > 32) return error.InvalidSize;

const bytes = try self.read32();
const result = try self.allocator.alloc(u8, size);
@memcpy(result, bytes[0..size]);
return result;
}

/// Decode dynamic bytes
pub fn decodeDynamicBytes(self: *Decoder) ![]u8 {
const length = try self.decodeUint();
if (length > self.data.len) return error.InvalidLength;

const len_usize = @as(usize, @intCast(length));
if (self.position + len_usize > self.data.len) {
return error.InsufficientData;
}

const result = try self.allocator.alloc(u8, len_usize);
@memcpy(result, self.data[self.position .. self.position + len_usize]);

// Skip data and padding
const padding = (32 - (len_usize % 32)) % 32;
self.position += len_usize + padding;

return result;
}

/// Decode a string
pub fn decodeString(self: *Decoder) ![]u8 {
return try self.decodeDynamicBytes();
}

/// Get current position
pub fn getPosition(self: Decoder) usize {
return self.position;
}

/// Set position
pub fn setPosition(self: *Decoder, pos: usize) void {
self.position = pos;
}

/// Check if more data is available
pub fn hasMore(self: Decoder) bool {
return self.position < self.data.len;
}
};

/// Decode function return data
pub fn decodeFunctionReturn(
allocator: std.mem.Allocator,
data: []const u8,
output_types: []const types.Parameter,
) ![]types.AbiValue {
var decoder = Decoder.init(allocator, data);
var results = std.ArrayList(types.AbiValue).init(allocator);
defer results.deinit();

for (output_types) |param| {
const value = try decodeValue(&decoder, param.type);
try results.append(value);
}

return try results.toOwnedSlice();
}

/// Decode a single value
fn decodeValue(decoder: *Decoder, abi_type: types.AbiType) !types.AbiValue {
return switch (abi_type) {
.uint256, .uint128, .uint64, .uint32, .uint16, .uint8 => .{ .uint = try decoder.decodeUint256() },
.int256, .int128, .int64, .int32, .int16, .int8 => .{ .int = @bitCast(try decoder.decodeUint()) },
.address => .{ .address = try decoder.decodeAddress() },
.bool_type => .{ .bool_val = try decoder.decodeBool() },
.bytes32, .bytes16, .bytes8, .bytes4, .bytes2, .bytes1 => .{ .fixed_bytes = try decoder.decodeFixedBytes(32) },
.string => .{ .string = try decoder.decodeString() },
.bytes => .{ .bytes = try decoder.decodeDynamicBytes() },
else => error.NotImplemented,
};
}

test "decode uint256" {
const allocator = std.testing.allocator;

var data: [32]u8 = [_]u8{0} ** 32;
data[31] = 42;

var decoder = Decoder.init(allocator, &data);
const value = try decoder.decodeUint256();

try std.testing.expect(value.eql(U256.fromInt(42)));
}

test "decode address" {
const allocator = std.testing.allocator;

var data: [32]u8 = [_]u8{0} ** 32;
@memset(data[12..32], 0xAB);

var decoder = Decoder.init(allocator, &data);
const addr = try decoder.decodeAddress();

try std.testing.expectEqual(@as(u8, 0xAB), addr.bytes[0]);
}

test "decode bool" {
const allocator = std.testing.allocator;

var data_true: [32]u8 = [_]u8{0} ** 32;
data_true[31] = 1;

var decoder_true = Decoder.init(allocator, &data_true);
try std.testing.expect(try decoder_true.decodeBool());

var data_false: [32]u8 = [_]u8{0} ** 32;
var decoder_false = Decoder.init(allocator, &data_false);
try std.testing.expect(!try decoder_false.decodeBool());
}

test "decode string" {
const allocator = std.testing.allocator;

// Encoded "hello": length (32 bytes) + data (5 bytes) + padding (27 bytes)
var data: [64]u8 = [_]u8{0} ** 64;
data[31] = 5; // length
@memcpy(data[32..37], "hello");

var decoder = Decoder.init(allocator, &data);
const str = try decoder.decodeString();
defer allocator.free(str);

try std.testing.expectEqualStrings("hello", str);
}

test "decode multiple values" {
const allocator = std.testing.allocator;

var data: [96]u8 = [_]u8{0} ** 96;
// First value: uint256 = 100
data[31] = 100;
// Second value: address
@memset(data[44..64], 0xAB);
// Third value: bool = true
data[95] = 1;

var decoder = Decoder.init(allocator, &data);

const val1 = try decoder.decodeUint256();
try std.testing.expect(val1.eql(U256.fromInt(100)));

const val2 = try decoder.decodeAddress();
try std.testing.expectEqual(@as(u8, 0xAB), val2.bytes[0]);

const val3 = try decoder.decodeBool();
try std.testing.expect(val3);

try std.testing.expect(!decoder.hasMore());
}
Loading
Loading