Skip to content

Commit

Permalink
Merge pull request #84 from Raiden1411/decoding
Browse files Browse the repository at this point in the history
decoder: refactor abi decoder implementation
  • Loading branch information
Raiden1411 authored Aug 6, 2024
2 parents c25c63b + 578c7a2 commit e1f78f0
Show file tree
Hide file tree
Showing 12 changed files with 700 additions and 1,108 deletions.
6 changes: 3 additions & 3 deletions bench/benchmark.zig
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const Signer = zabi_root.Signer;
const TransactionEnvelope = zabi_root.types.transactions.TransactionEnvelope;

// Functions
const decodeAbiParameters = zabi_root.decoding.abi_decoder.decodeAbiParameters;
const decodeAbiParameter = zabi_root.decoding.abi_decoder.decodeAbiParameter;
const decodeLogs = zabi_root.decoding.logs_decoder.decodeLogs;
const decodeRlp = zabi_root.decoding.rlp.decodeRlp;
const encodeAbiParameters = zabi_root.encoding.abi_encoding.encodeAbiParameters;
Expand Down Expand Up @@ -235,8 +235,8 @@ pub fn decodingFunctions(allocator: Allocator, printer: BenchmarkPrinter) !void

const result = try benchmark.benchmark(
allocator,
decodeAbiParameters,
.{ allocator, constants.params, encoded.data, .{} },
decodeAbiParameter,
.{ zabi_root.meta.abi.AbiParametersToPrimative(constants.params), allocator, encoded.data, .{} },
.{ .warmup_runs = 5, .runs = 100 },
);
result.printSummary();
Expand Down
6 changes: 4 additions & 2 deletions examples/contract/contract.zig
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,10 @@ pub fn main() !void {

std.debug.print("Transaction receipt: {}", .{receipt.response});

const balance = try contract.readContractFunction(struct { u256 }, "balanceOf", .{contract.wallet.getWalletAddress()}, .{
const balance = try contract.readContractFunction(u256, "balanceOf", .{contract.wallet.getWalletAddress()}, .{
.london = .{ .to = try utils.addressToBytes("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48") },
});
std.debug.print("BALANCE: {d}\n", .{balance[0]});
defer balance.deinit();

std.debug.print("BALANCE: {d}\n", .{balance.result});
}
7 changes: 3 additions & 4 deletions examples/watch/logs.zig
Original file line number Diff line number Diff line change
Expand Up @@ -36,16 +36,15 @@ pub fn main() !void {
const event = try socket.getLogsSubEvent();
defer event.deinit();

const value = try zabi.decoding.abi_decoder.decodeAbiParameters(gpa.allocator(), &.{
.{ .type = .{ .uint = 256 }, .name = "tokenId" },
}, event.response.params.result.data, .{});
const value = try zabi.decoding.abi_decoder.decodeAbiParameter(u256, gpa.allocator(), event.response.params.result.data, .{});
defer value.deinit();

const topics = try zabi.decoding.logs_decoder.decodeLogsComptime(&.{
.{ .type = .{ .address = {} }, .name = "from", .indexed = true },
.{ .type = .{ .address = {} }, .name = "to", .indexed = true },
}, event.response.params.result.topics);

std.debug.print("Transfer event found. Value transfered: {d} dollars\n", .{value[0] / 1000000});
std.debug.print("Transfer event found. Value transfered: {d} dollars\n", .{value.result / 1000000});
std.debug.print("From: 0x{s}\n", .{std.fmt.fmtSliceHexLower(&topics[1])});
std.debug.print("To: 0x{s}\n", .{std.fmt.fmtSliceHexLower(&topics[2])});
}
Expand Down
100 changes: 15 additions & 85 deletions src/abi/abi.zig
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@ const testing = std.testing;
const types = @import("../types/ethereum.zig");

// Types
const AbiDecoded = decoder.AbiDecoded;
const AbiEncoded = encoder.AbiEncoded;
const AbiParameter = @import("abi_parameter.zig").AbiParameter;
const AbiEventParameter = @import("abi_parameter.zig").AbiEventParameter;
const AbiParameter = @import("abi_parameter.zig").AbiParameter;
const Allocator = std.mem.Allocator;
const DecodeOptions = decoder.DecodeOptions;
const Extract = meta.utils.Extract;
const Hash = types.Hash;
const Keccak256 = std.crypto.hash.sha3.Keccak256;
Expand Down Expand Up @@ -118,33 +120,10 @@ pub const Function = struct {
///
/// Consider using `decodeAbiFunction` if the struct is
/// comptime know and you dont want to provided the return type.
pub fn decode(self: @This(), allocator: Allocator, comptime T: type, encoded: []const u8, opts: decoder.DecodeOptions) !decoder.AbiSignatureDecodedRuntime(T) {
std.debug.assert(encoded.len > 7);

const hashed_func_name = encoded[0..8];
const prepared = try self.allocPrepare(allocator);
defer allocator.free(prepared);

var hashed: [Keccak256.digest_length]u8 = undefined;
Keccak256.hash(prepare, &hashed, .{});

const hash_hex = std.fmt.bytesToHex(hashed, .lower);

if (!std.mem.eql(u8, hashed_func_name, hash_hex[0..8]))
return error.InvalidAbiSignature;

const data = encoded[8..];
const func_name = try std.mem.concat(allocator, u8, &.{ "0x", hashed_func_name });
errdefer allocator.free(func_name);

if (data.len == 0 and self.inputs.len > 0)
return error.InvalidDecodeDataSize;

const decoded = try decoder.decodeAbiParametersRuntime(allocator, T, self.inputs, data, opts);

return .{ .arena = decoded.arena, .name = func_name, .values = decoded.values };
pub fn decode(self: @This(), comptime T: type, allocator: Allocator, encoded: []const u8, options: DecodeOptions) !AbiDecoded(T) {
_ = self;
return decoder.decodeAbiFunction(T, allocator, encoded, options);
}

/// Decode a encoded function based on itself.
/// Runtime reflection based on the provided values will occur to determine
/// what is the correct method to use to encode the values.
Expand All @@ -154,33 +133,10 @@ pub const Function = struct {
///
/// Consider using `decodeAbiFunction` if the struct is
/// comptime know and you dont want to provided the return type.
pub fn decodeOutputs(self: @This(), allocator: Allocator, comptime T: type, encoded: []const u8, opts: decoder.DecodeOptions) !decoder.AbiSignatureDecodedRuntime(T) {
std.debug.assert(encoded.len > 7);

const hashed_func_name = encoded[0..8];
const prepared = try self.allocPrepare(allocator);
defer allocator.free(prepared);

var hashed: [Keccak256.digest_length]u8 = undefined;
Keccak256.hash(prepare, &hashed, .{});

const hash_hex = std.fmt.bytesToHex(hashed, .lower);

if (!std.mem.eql(u8, hashed_func_name, hash_hex[0..8]))
return error.InvalidAbiSignature;

const data = encoded[8..];
const func_name = try std.mem.concat(allocator, u8, &.{ "0x", hashed_func_name });
errdefer allocator.free(func_name);

if (data.len == 0 and self.outputs.len > 0)
return error.InvalidDecodeDataSize;

const decoded = try decoder.decodeAbiParametersRuntime(allocator, T, self.outputs, data, opts);

return .{ .arena = decoded.arena, .name = func_name, .values = decoded.values };
pub fn decodeOutputs(self: @This(), comptime T: type, allocator: Allocator, encoded: []const u8, options: DecodeOptions) !AbiDecoded(T) {
_ = self;
return decoder.decodeAbiFunctionOutputs(T, allocator, encoded, options);
}

/// Format the struct into a human readable string.
pub fn format(self: @This(), comptime layout: []const u8, opts: std.fmt.FormatOptions, writer: anytype) !void {
try writer.print("{s}", .{@tagName(self.type)});
Expand Down Expand Up @@ -383,7 +339,6 @@ pub const Error = struct {

return buffer;
}

/// Decode a encoded error based on itself.
/// Runtime reflection based on the provided values will occur to determine
/// what is the correct method to use to encode the values.
Expand All @@ -393,33 +348,10 @@ pub const Error = struct {
///
/// Consider using `decodeAbiError` if the struct is
/// comptime know and you dont want to provided the return type.
pub fn decode(self: @This(), allocator: Allocator, comptime T: type, encoded: []const u8, opts: decoder.DecodeOptions) !decoder.AbiSignatureDecodedRuntime(T) {
std.debug.assert(encoded.len > 7);

const hashed_func_name = encoded[0..8];
const prepared = try self.allocPrepare(allocator);
defer allocator.free(prepared);

var hashed: [Keccak256.digest_length]u8 = undefined;
Keccak256.hash(prepare, &hashed, .{});

const hash_hex = std.fmt.bytesToHex(hashed, .lower);

if (!std.mem.eql(u8, hashed_func_name, hash_hex[0..8]))
return error.InvalidAbiSignature;

const data = encoded[8..];
const func_name = try std.mem.concat(allocator, u8, &.{ "0x", hashed_func_name });
errdefer allocator.free(func_name);

if (data.len == 0 and self.inputs.len > 0)
return error.InvalidDecodeDataSize;

const decoded = try decoder.decodeAbiErrorRuntime(allocator, T, self.inputs, data, opts);

return .{ .arena = decoded.arena, .name = func_name, .values = decoded.values };
pub fn decode(self: @This(), comptime T: type, allocator: Allocator, encoded: []const u8, options: DecodeOptions) !AbiDecoded(T) {
_ = self;
return decoder.decodeAbiError(T, allocator, encoded, options);
}

/// Format the struct into a human readable string.
/// Intended to use for hashing purposes.
///
Expand All @@ -438,7 +370,6 @@ pub const Error = struct {

return buf_writter.getWritten();
}

/// Format the struct into a human readable string.
/// Intended to use for hashing purposes.
pub fn prepare(self: @This(), writer: anytype) !void {
Expand Down Expand Up @@ -506,10 +437,9 @@ pub const Constructor = struct {
///
/// Consider using `decodeAbiConstructor` if the struct is
/// comptime know and you dont want to provided the return type.
pub fn decode(self: @This(), allocator: Allocator, comptime T: type, encoded: []const u8, opts: decoder.DecodeOptions) !decoder.AbiSignatureDecodedRuntime(T) {
const decoded = try decoder.decodeAbiConstructorRuntime(allocator, T, self.inputs, encoded, opts);

return .{ .arena = decoded.arena, .name = "", .values = decoded.values };
pub fn decode(self: @This(), comptime T: type, allocator: Allocator, encoded: []const u8, options: DecodeOptions) !AbiDecoded(T) {
_ = self;
return decoder.decodeAbiConstructor(T, allocator, encoded, options);
}
};

Expand Down
9 changes: 4 additions & 5 deletions src/abi/abi_parameter.zig
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ const std = @import("std");
const testing = std.testing;

// Types
const AbiDecoded = decoder.AbiDecoded;
const Allocator = std.mem.Allocator;
const DecodeOptions = decoder.DecodeOptions;
const ParamType = @import("param_type.zig").ParamType;

/// Struct to represent solidity Abi Paramters
Expand Down Expand Up @@ -39,7 +41,6 @@ pub const AbiParameter = struct {

return hexed;
}

/// Decode the paramters based on self.
/// Runtime reflection based on the provided values will occur to determine
/// what is the correct method to use to encode the values
Expand All @@ -48,10 +49,8 @@ pub const AbiParameter = struct {
///
/// Consider using `decodeAbiParameters` if the parameter is
/// comptime know and you want better typesafety from the compiler
pub fn decode(self: @This(), allocator: Allocator, comptime T: type, encoded: []const u8, opts: decoder.DecodeOptions) !T {
const decoded = try decoder.decodeAbiParametersRuntime(allocator, T, &.{self}, encoded, opts);

return decoded;
pub fn decode(self: @This(), comptime T: type, allocator: Allocator, encoded: []const u8, options: DecodeOptions) !AbiDecoded(T) {
return decoder.decodeAbiParameter(allocator, T, &.{self}, encoded, options);
}

/// Format the struct into a human readable string.
Expand Down
13 changes: 9 additions & 4 deletions src/clients/contract.zig
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const utils = @import("../utils/utils.zig");

// Types
const Abi = abitype.Abi;
const AbiDecoded = decoder.AbiDecoded;
const Abitype = abitype.Abitype;
const AbiItem = abitype.AbiItem;
const AbiParametersToPrimative = meta.AbiParametersToPrimative;
Expand Down Expand Up @@ -115,7 +116,11 @@ pub fn ContractComptime(comptime client_type: ClientType) type {
/// It won't commit a transaction to the network.
///
/// RPC Method: [`eth_call`](https://ethereum.org/en/developers/docs/apis/json-rpc#eth_call)
pub fn readContractFunction(self: *ContractComptime(client_type), comptime func: Function, opts: FunctionOpts(func, EthCall)) !AbiParametersToPrimative(func.outputs) {
pub fn readContractFunction(
self: *ContractComptime(client_type),
comptime func: Function,
opts: FunctionOpts(func, EthCall),
) !AbiDecoded(AbiParametersToPrimative(func.outputs)) {
var copy = opts.overrides;

switch (func.stateMutability) {
Expand All @@ -138,7 +143,7 @@ pub fn ContractComptime(comptime client_type: ClientType) type {
const data = try self.wallet.rpc_client.sendEthCall(copy, .{});
defer data.deinit();

const decoded = try decoder.decodeAbiParameters(self.wallet.allocator, func.outputs, data.response, .{});
const decoded = try decoder.decodeAbiParameter(AbiParametersToPrimative(func.outputs), self.wallet.allocator, data.response, .{});

return decoded;
}
Expand Down Expand Up @@ -302,7 +307,7 @@ pub fn Contract(comptime client_type: ClientType) type {
/// It won't commit a transaction to the network.
///
/// RPC Method: [`eth_call`](https://ethereum.org/en/developers/docs/apis/json-rpc#eth_call)
pub fn readContractFunction(self: *Contract(client_type), comptime T: type, function_name: []const u8, function_args: anytype, overrides: EthCall) !T {
pub fn readContractFunction(self: *Contract(client_type), comptime T: type, function_name: []const u8, function_args: anytype, overrides: EthCall) !AbiDecoded(T) {
const function_item = try getAbiItem(self.abi, .function, function_name);
var copy = overrides;

Expand All @@ -326,7 +331,7 @@ pub fn Contract(comptime client_type: ClientType) type {
const data = try self.wallet.rpc_client.sendEthCall(copy, .{});
defer data.deinit();

const decoded = try decoder.decodeAbiParametersRuntime(self.wallet.allocator, T, function_item.abiFunction.outputs, data.response, .{});
const decoded = try decoder.decodeAbiParameter(T, self.wallet.allocator, data.response, .{});

return decoded;
}
Expand Down
Loading

0 comments on commit e1f78f0

Please sign in to comment.