使用 Zig 中的签名对函数选择器进行编码

问题描述 投票:0回答:1

我正在开发一个 Zig 项目,该项目涉及根据 ABI 编码规则对参数进行编码。该项目包含一个函数 abiEncodeWithSignature,其目的是获取函数签名和一组参数,然后返回 ABI 编码的字节。该函数利用 switch 语句来处理不同的参数类型,包括 256 位无符号整数 (@"u256") 和无符号字节切片 ([]const u8 和 []u8) 的特定情况。

对此函数运行单元测试时会出现问题。第一个测试对不带参数的函数签名进行编码,顺利通过。然而,当我开始添加参数时,第二个测试失败了。

是否有人对如何解决此问题有见解或建议,以确保所有参数类型都得到正确支持和编码?

供参考:https://noxx.substack.com/p/evm-deep-dives-the-path-to-shadowy?s=r

const std = @import("std");
const crypto = std.crypto;
const mem = std.mem;
const testing = std.testing;

const @"u256" = [32]u8;

pub fn abiEncodeWithSignature(signature: []const u8, args: anytype) ![]const u8 {
    // Convert the function signature to a Keccak-256 hash
    var hash: [32]u8 = undefined;
    crypto.hash.sha3.Keccak256.hash(signature, &hash, .{});

    // Take the first 4 bytes of the hash as the function selector
    const selector = hash[0..4];

    // Create a list to store the encoded arguments
    var encoded_args = std.ArrayList(u8).init(std.heap.page_allocator);
    defer encoded_args.deinit();

    // Encode each argument according to the ABI encoding rules
    inline for (args, 0..) |arg, i| {
        const arg_type = @TypeOf(arg);
        std.debug.print("Argument at index {}: type={s}, value={any}\n", .{ i, @typeName(arg_type), arg });

        switch (arg_type) {
            @"u256" => {
                std.debug.print("Appending u256 argument: {any}\n", .{arg});
                try encoded_args.appendSlice(&arg);
            },
            []const u8 => {
                if (arg.len == 20) {
                    std.debug.print("Appending address argument: {any}\n", .{arg});
                    try encoded_args.appendNTimes(0, 12); // Pad with 12 zero bytes
                    try encoded_args.appendSlice(arg); // Append the 20-byte address
                } else {
                    return error.InvalidAddressLength;
                }
            },
            []u8 => {
                std.debug.print("Appending fixed-size address argument: {any}\n", .{arg});
                try encoded_args.appendNTimes(0, 12); // Pad with 12 zero bytes
                try encoded_args.appendSlice(&arg); // Append the 20-byte address
            },
            *const [20]u8 => {
                std.debug.print("Appending address argument: {any}\n", .{arg});
                // Assuming encoded_args is a slice you're appending to, and you want to append the raw bytes
                try encoded_args.appendSlice((@as(*const [20]u8, @ptrCast(arg))).*[0..20]);
            },
            *[32]u8 => {
                std.debug.print("Appending u256 argument: {any}\n", .{arg});
                try encoded_args.appendSlice(arg[0..]); // Convert the array to a slice
            },
            else => {
                std.debug.print("Unsupported argument type at index {}: {s}\n", .{ i, @typeName(arg_type) });
                return error.UnsupportedArgumentType;
            },
        }
    }

    // Concatenate the function selector and the encoded arguments
    var result = try std.heap.page_allocator.alloc(u8, selector.len + encoded_args.items.len);
    mem.copy(u8, result[0..selector.len], selector);
    mem.copy(u8, result[selector.len..], encoded_args.items);

    return result;
}

pub fn main() !void {
    std.debug.print("Run `zig test` to run the tests.\n", .{});
}

test "abiEncodeWithSignature.store(uint256)" {
    const signature = "store(uint256)";

    const encoded = try abiEncodeWithSignature(signature, .{});
    defer std.heap.page_allocator.free(encoded);

    // Expected result: 6057361d
    const expected = [_]u8{ 0x60, 0x57, 0x36, 0x1d };

    try testing.expectEqualStrings(encoded, &expected);
}

test "abiEncodeWithSignature.store(uint256, address)" {
    const signature = "store(uint256,address)";

    // Adjust amount_bytes to be a [32]u8 array, representing a uint256.
    var amount_bytes: [32]u8 = undefined;
    amount_bytes[31] = 0x01; // Assuming little-endian, place the value at the end.

    const address_bytes = [_]u8{ 0x12, 0x34, 0x56, 0x78, 0x90, 0x12, 0x34, 0x56, 0x78, 0x90, 0x12, 0x34, 0x56, 0x78, 0x90, 0x12, 0x34, 0x56, 0x78, 0x90 };

    const encoded = try abiEncodeWithSignature(signature, .{ &amount_bytes, address_bytes[0..] });
    defer std.heap.page_allocator.free(encoded);

    const expected = [_]u8{
        0x6f, 0x34, 0x0e, 0x6b, // Function selector
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, // amount
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x34, 0x56, 0x78, 0x90, 0x12, 0x34, 0x56, 0x78, 0x90, 0x12, 0x34, 0x56, 0x78, 0x90, 0x12, 0x34, 0x56, 0x78, 0x90, // address
    };

    try testing.expectEqualSlices(u8, encoded, &expected);
}

Test [2/2] test.abiEncodeWithSignature.store(uint256, address)... Argument at index 0: type=*[32]u8, value=[32]u8@16b5b6310
Appending u256 argument: [32]u8@16b5b6310
Argument at index 1: type=*const [20]u8, value=[20]u8@104907bcc
Appending address argument: [20]u8@104907bcc
slices differ. first difference occurs at index 0 (0x0)

============ expected this output: =============  len: 56 (0x38)

CE AA 31 82 AA AA AA AA  AA AA AA AA AA AA AA AA  ..1.............
AA AA AA AA AA AA AA AA  AA AA AA AA AA AA AA AA  ................
AA AA AA 01 12 34 56 78  90 12 34 56 78 90 12 34  .....4Vx..4Vx..4
56 78 90 12 34 56 78 90                           Vx..4Vx.

============= instead found this: ==============  len: 68 (0x44)

6F 34 0E 6B 00 00 00 00  00 00 00 00 00 00 00 00  o4.k............
00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  ................
00 00 00 01 00 00 00 00  00 00 00 00 00 00 00 00  ................
12 34 56 78 90 12 34 56  78 90 12 34 56 78 90 12  .4Vx..4Vx..4Vx..
34 56 78 90                                       4Vx.

================================================

Test [2/2] test.abiEncodeWithSignature.store(uint256, address)... FAIL (TestExpectedEqual)
/opt/homebrew/Cellar/zig/0.11.0/lib/zig/std/testing.zig:380:5: 0x10484caeb in expectEqualSlices__anon_1727 (test)
    return error.TestExpectedEqual;
    ^
/Users/christopher.bradley/boringlabs/zigsol/src/main.zig:102:5: 0x10484d1a3 in test.abiEncodeWithSignature.store(uint256, address) (test)
    try testing.expectEqualSlices(u8, encoded, &expected);
    ^
1 passed; 0 skipped; 1 failed.
error: the following test command failed with exit code 1:
/Users/christopher.bradley/boringlabs/zigsol/zig-cache/o/29627c5bf39569b46dda51a33e32e0e7/test
encoding cryptography solidity abi zig
1个回答
0
投票

amount_bytes
address_bytes
不是切片,它们是数组

@compileLog(@TypeOf(amount_bytes)); // @as(type, [1]u8)
@compileLog(@TypeOf(address_bytes)); // @as(type, [20]u8)

通常转换为切片就像

&array
array[0..]
一样简单,但在本例中您使用的是
anytype
,因此您必须明确:
@as([]const u8, &array)
。像这样:

const encoded = try abiEncodeWithSignature(signature, .{ @as([]const u8, &amount_bytes), @as([]const u8, &address_bytes) });
© www.soinside.com 2019 - 2024. All rights reserved.