Skip to content

Commit

Permalink
BREAKING: Use packed structs (#7)
Browse files Browse the repository at this point in the history
#### Problem

Many of the structs in the SDK use `extern` layout. While this ensures
compatibility with C ABI, it can also be less performant for on-chain
programs.

#### Summary of changes

Move `PublicKey`, `Account.Data`, and `Rent` to be `packed` structs.
This is a breaking change because the underlying layout of many types
will change.
  • Loading branch information
joncinque authored Nov 15, 2024
1 parent ec2d90a commit 4b1f50b
Show file tree
Hide file tree
Showing 4 changed files with 45 additions and 28 deletions.
47 changes: 32 additions & 15 deletions src/account.zig
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,30 @@ const PublicKey = @import("public_key.zig").PublicKey;
pub const ACCOUNT_DATA_PADDING = 10 * 1024;

pub const Account = struct {
pub const DATA_HEADER = 88;
/// A Solana account sliced from what is provided as inputs to the BPF virtual machine.
pub const Data = extern struct {
pub const Data = packed struct {
duplicate_index: u8,
is_signer: bool,
is_writable: bool,
is_executable: bool,
_: [4]u8,
is_signer: u8,
is_writable: u8,
is_executable: u8,
original_data_len: u32,
id: PublicKey,
owner_id: PublicKey,
lamports: u64,
data_len: usize,
data_len: u64,

comptime {
std.debug.assert(@offsetOf(Account.Data, "duplicate_index") == 0);
std.debug.assert(@offsetOf(Account.Data, "is_signer") == 0 + 1);
std.debug.assert(@offsetOf(Account.Data, "is_writable") == 0 + 1 + 1);
std.debug.assert(@offsetOf(Account.Data, "is_executable") == 0 + 1 + 1 + 1);
std.debug.assert(@offsetOf(Account.Data, "_") == 0 + 1 + 1 + 1 + 1);
std.debug.assert(@offsetOf(Account.Data, "original_data_len") == 0 + 1 + 1 + 1 + 1);
std.debug.assert(@offsetOf(Account.Data, "id") == 0 + 1 + 1 + 1 + 1 + 4);
std.debug.assert(@offsetOf(Account.Data, "owner_id") == 0 + 1 + 1 + 1 + 1 + 4 + 32);
std.debug.assert(@offsetOf(Account.Data, "lamports") == 0 + 1 + 1 + 1 + 1 + 4 + 32 + 32);
std.debug.assert(@offsetOf(Account.Data, "data_len") == 0 + 1 + 1 + 1 + 1 + 4 + 32 + 32 + 8);
std.debug.assert(@sizeOf(Account.Data) == 1 + 1 + 1 + 1 + 4 + 32 + 32 + 8 + 8);
std.debug.assert(@bitSizeOf(Account.Data) == DATA_HEADER * 8);
}
};

Expand Down Expand Up @@ -63,30 +64,46 @@ pub const Account = struct {
return self.ptr.owner_id;
}

pub fn assign(self: Account, new_owner_id: PublicKey) void {
self.ptr.owner_id = new_owner_id;
}

pub fn data(self: Account) []u8 {
const data_ptr = @as([*]u8, @ptrFromInt(@intFromPtr(self.ptr))) + @sizeOf(Account.Data);
const data_ptr = @as([*]u8, @ptrFromInt(@intFromPtr(self.ptr))) + DATA_HEADER;
return data_ptr[0..self.ptr.data_len];
}

pub fn isWritable(self: Account) bool {
return self.ptr.is_writable;
return self.ptr.is_writable == 1;
}

pub fn isExecutable(self: Account) bool {
return self.ptr.is_executable;
return self.ptr.is_executable == 1;
}

pub fn isSigner(self: Account) bool {
return self.ptr.is_signer;
return self.ptr.is_signer == 1;
}

pub fn dataLen(self: Account) usize {
pub fn dataLen(self: Account) u64 {
return self.ptr.data_len;
}

pub fn realloc(self: Account, new_data_len: u64) error.InvalidRealloc!void {
const diff = @subWithOverflow(new_data_len, self.original_data_len);
if (diff[1] == 0 and diff[0] > ACCOUNT_DATA_PADDING) {
return error.InvalidRealloc;
}
self.reallocUnchecked(new_data_len);
}

pub fn reallocUnchecked(self: Account, new_data_len: u64) void {
self.ptr.data_len = new_data_len;
}

pub fn info(self: Account) Account.Info {
const data_ptr = @as([*]u8, @ptrFromInt(@intFromPtr(self.ptr))) + @sizeOf(Account.Data);
const rent_epoch = @as(*u64, @ptrFromInt(std.mem.alignForward(usize, @intFromPtr(self.ptr) + self.ptr.data_len + ACCOUNT_DATA_PADDING, @alignOf(usize))));
const data_ptr = @as([*]u8, @ptrFromInt(@intFromPtr(self.ptr))) + DATA_HEADER;
const rent_epoch = @as(*u64, @ptrFromInt(std.mem.alignForward(u64, @intFromPtr(self.ptr) + self.ptr.data_len + ACCOUNT_DATA_PADDING, @alignOf(u64))));

return .{
.id = &self.ptr.id,
Expand Down
6 changes: 3 additions & 3 deletions src/context.zig
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ pub const Context = struct {
num_accounts: usize,
accounts: [64]Account,
data: []const u8,
program_id: *PublicKey,
program_id: *align(1) PublicKey,

pub fn load(input: [*]u8) !Context {
var ptr: [*]u8 = input;
Expand All @@ -25,7 +25,7 @@ pub const Context = struct {
ptr += @sizeOf(usize);
accounts[i] = accounts[data.duplicate_index];
} else {
ptr += @sizeOf(Account.Data);
ptr += Account.DATA_HEADER;
ptr = @as([*]u8, @ptrFromInt(std.mem.alignForward(usize, @intFromPtr(ptr) + data.data_len + ACCOUNT_DATA_PADDING, @alignOf(usize))));
ptr += @sizeOf(u64);
accounts[i] = .{ .ptr = @as(*Account.Data, @ptrCast(@alignCast(data))) };
Expand All @@ -39,7 +39,7 @@ pub const Context = struct {
const data = ptr[0..data_len];
ptr += data_len;

const program_id = @as(*PublicKey, @ptrCast(ptr));
const program_id = @as(*align(1) PublicKey, @ptrCast(ptr));
ptr += @sizeOf(PublicKey);

return Context{
Expand Down
16 changes: 8 additions & 8 deletions src/public_key.zig
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,17 @@ pub const ProgramDerivedAddress = struct {
bump_seed: [1]u8,
};

pub const PublicKey = extern struct {
pub const PublicKey = packed struct {
pub const length: usize = 32;
pub const base58_length: usize = 44;

pub const max_num_seeds: usize = 16;
pub const max_seed_length: usize = 32;

bytes: [PublicKey.length]u8,
bytes: u256,

pub fn from(bytes: [PublicKey.length]u8) PublicKey {
return .{ .bytes = bytes };
return .{ .bytes = mem.bytesToValue(u256, &bytes) };
}

pub fn comptimeFromBase58(comptime encoded: []const u8) PublicKey {
Expand All @@ -47,11 +47,11 @@ pub const PublicKey = extern struct {
}

pub fn equals(self: PublicKey, other: PublicKey) bool {
return mem.eql(u8, &self.bytes, &other.bytes);
return self.bytes == other.bytes;
}

pub fn isPointOnCurve(self: PublicKey) bool {
const Y = std.crypto.ecc.Curve25519.Fe.fromBytes(self.bytes);
const Y = std.crypto.ecc.Curve25519.Fe.fromBytes(mem.toBytes(self.bytes));
const Z = std.crypto.ecc.Curve25519.Fe.one;
const YY = Y.sq();
const u = YY.sub(Z);
Expand Down Expand Up @@ -125,9 +125,9 @@ pub const PublicKey = extern struct {
inline while (i < seeds.len) : (i += 1) {
hasher.update(seeds[i]);
}
hasher.update(&program_id.bytes);
hasher.update(mem.asBytes(&program_id.bytes));
hasher.update("ProgramDerivedAddress");
hasher.final(&address.bytes);
hasher.final(mem.asBytes(&address.bytes));

if (address.isPointOnCurve()) {
return error.InvalidSeeds;
Expand Down Expand Up @@ -222,7 +222,7 @@ pub const PublicKey = extern struct {
_ = fmt;
_ = options;
var buffer: [base58.bitcoin.getEncodedLengthUpperBound(PublicKey.length)]u8 = undefined;
try writer.print("{s}", .{base58.bitcoin.encode(&buffer, &self.bytes)});
try writer.print("{s}", .{base58.bitcoin.encode(&buffer, mem.asBytes(&self.bytes))});
}
};

Expand Down
4 changes: 2 additions & 2 deletions src/rent.zig
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ pub const Rent = struct {
/// Account storage overhead for calculation of base rent.
pub const account_storage_overhead: u64 = 128;

pub const Data = extern struct {
pub const Data = packed struct {
lamports_per_byte_year: u64 = Rent.default_lamports_per_byte_year,
exemption_threshold: f64 = Rent.default_exemption_threshold,
burn_percent: u8 = Rent.default_burn_percent,
Expand All @@ -43,7 +43,7 @@ pub const Rent = struct {

pub fn getMinimumBalance(self: Rent.Data, data_len: usize) u64 {
const total_data_len: u64 = Rent.account_storage_overhead + data_len;
return @intFromFloat(@as(f64, @floatFromInt(total_data_len * self.lamports_per_byte_year)) * self.exemption_threshold);
return total_data_len * self.lamports_per_byte_year * @as(u64, @intFromFloat(self.exemption_threshold));
}
};

Expand Down

0 comments on commit 4b1f50b

Please sign in to comment.