Я работаю над проектом Zig, который включает в себя кодирование аргументов в соответствии с правилами кодирования ABI. Проект включает функцию abiEncodeWithSignature, целью которой является получение сигнатуры функции и набора аргументов, а затем возврат байтов, закодированных в ABI. Функция использует оператор переключения для обработки различных типов аргументов, включая конкретный случай для 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) {
[]const u8 => {
std.debug.print("Appending address argument: {any}\n", .{arg});
// Left-pad the address with 12 zero bytes to make it 32 bytes long
var padded_address: [32]u8 = undefined;
for (padded_address[0..12]) |*byte| {
byte.* = 0; // Pad with zeros
}
std.mem.copy(u8, padded_address[12..], arg[0..20]);
try encoded_args.appendSlice(padded_address[0..]); // Append the padded address
},
*[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.expectEqualSlices(u8, &expected, encoded);
}
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, .{ @as([]const u8, &amount_bytes), @as([]const u8, &address_bytes) });
defer std.heap.page_allocator.free(encoded);
const expected = [_]u8{
0xce, 0xaa, 0x31, 0x82, // 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, &expected, encoded);
}
Test [2/2] test.abiEncodeWithSignature.store(uint256, address)... Argument at index 0: type=[]const u8, value = { 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 1 }
Appending address argument: { 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 1 }
Argument at index 1: type=[]const u8, value = { 18, 52, 86, 120, 144, 18, 52, 86, 120, 144, 18, 52, 86, 120, 144, 18, 52, 86, 120, 144 }
Appending address argument: { 18, 52, 86, 120, 144, 18, 52, 86, 120, 144, 18, 52, 86, 120, 144, 18, 52, 86, 120, 144 }
slices differ. first difference occurs at index 16 (0x10)
============ expected this output: ============= len: 68 (0x44)
CE AA 31 82 00 00 00 00 00 00 00 00 00 00 00 00 ..1.............
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.
============= instead found this: ============== len: 68 (0x44)
CE AA 31 82 00 00 00 00 00 00 00 00 00 00 00 00 ..1.............
AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA ................
AA AA AA AA 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: 0x100ecf113 in expectEqualSlices__anon_1697 (test)
return error.TestExpectedEqual;
^
/Users/christopher.bradley/boringlabs/zigsol/src/main.zig:89:5: 0x100ed0613 in test.abiEncodeWithSignature.store(uint256, address) (test)
try testing.expectEqualSlices(u8, &expected, encoded);
^
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
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) });
zig ============ expected this output: ============= len: 68 (0x44) CE AA 31 82 00 00 00 00 00 00 00 00 00 00 00 00 ..1............. 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.
zig ============= instead found this: ============== len: 68 (0x44) CE AA 31 82 00 00 00 00 00 00 00 00 00 00 00 00 ..1............. AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA ................ AA AA AA AA 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.
Я обновил код, чтобы отразить
1. Вы не должны редактировать такие вопросы. 2. Вы оставили большую часть amount_bytes
памяти неинициализированной. Zig устанавливает неинициализированную память в 0xaa в режиме отладки.
Это сработало, но все еще есть проблема с заполнением