Skip to content

Commit 4b1f50b

Browse files
authored
BREAKING: Use packed structs (#7)
#### 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.
1 parent ec2d90a commit 4b1f50b

File tree

4 files changed

+45
-28
lines changed

4 files changed

+45
-28
lines changed

src/account.zig

Lines changed: 32 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,29 +4,30 @@ const PublicKey = @import("public_key.zig").PublicKey;
44
pub const ACCOUNT_DATA_PADDING = 10 * 1024;
55

66
pub const Account = struct {
7+
pub const DATA_HEADER = 88;
78
/// A Solana account sliced from what is provided as inputs to the BPF virtual machine.
8-
pub const Data = extern struct {
9+
pub const Data = packed struct {
910
duplicate_index: u8,
10-
is_signer: bool,
11-
is_writable: bool,
12-
is_executable: bool,
13-
_: [4]u8,
11+
is_signer: u8,
12+
is_writable: u8,
13+
is_executable: u8,
14+
original_data_len: u32,
1415
id: PublicKey,
1516
owner_id: PublicKey,
1617
lamports: u64,
17-
data_len: usize,
18+
data_len: u64,
1819

1920
comptime {
2021
std.debug.assert(@offsetOf(Account.Data, "duplicate_index") == 0);
2122
std.debug.assert(@offsetOf(Account.Data, "is_signer") == 0 + 1);
2223
std.debug.assert(@offsetOf(Account.Data, "is_writable") == 0 + 1 + 1);
2324
std.debug.assert(@offsetOf(Account.Data, "is_executable") == 0 + 1 + 1 + 1);
24-
std.debug.assert(@offsetOf(Account.Data, "_") == 0 + 1 + 1 + 1 + 1);
25+
std.debug.assert(@offsetOf(Account.Data, "original_data_len") == 0 + 1 + 1 + 1 + 1);
2526
std.debug.assert(@offsetOf(Account.Data, "id") == 0 + 1 + 1 + 1 + 1 + 4);
2627
std.debug.assert(@offsetOf(Account.Data, "owner_id") == 0 + 1 + 1 + 1 + 1 + 4 + 32);
2728
std.debug.assert(@offsetOf(Account.Data, "lamports") == 0 + 1 + 1 + 1 + 1 + 4 + 32 + 32);
2829
std.debug.assert(@offsetOf(Account.Data, "data_len") == 0 + 1 + 1 + 1 + 1 + 4 + 32 + 32 + 8);
29-
std.debug.assert(@sizeOf(Account.Data) == 1 + 1 + 1 + 1 + 4 + 32 + 32 + 8 + 8);
30+
std.debug.assert(@bitSizeOf(Account.Data) == DATA_HEADER * 8);
3031
}
3132
};
3233

@@ -63,30 +64,46 @@ pub const Account = struct {
6364
return self.ptr.owner_id;
6465
}
6566

67+
pub fn assign(self: Account, new_owner_id: PublicKey) void {
68+
self.ptr.owner_id = new_owner_id;
69+
}
70+
6671
pub fn data(self: Account) []u8 {
67-
const data_ptr = @as([*]u8, @ptrFromInt(@intFromPtr(self.ptr))) + @sizeOf(Account.Data);
72+
const data_ptr = @as([*]u8, @ptrFromInt(@intFromPtr(self.ptr))) + DATA_HEADER;
6873
return data_ptr[0..self.ptr.data_len];
6974
}
7075

7176
pub fn isWritable(self: Account) bool {
72-
return self.ptr.is_writable;
77+
return self.ptr.is_writable == 1;
7378
}
7479

7580
pub fn isExecutable(self: Account) bool {
76-
return self.ptr.is_executable;
81+
return self.ptr.is_executable == 1;
7782
}
7883

7984
pub fn isSigner(self: Account) bool {
80-
return self.ptr.is_signer;
85+
return self.ptr.is_signer == 1;
8186
}
8287

83-
pub fn dataLen(self: Account) usize {
88+
pub fn dataLen(self: Account) u64 {
8489
return self.ptr.data_len;
8590
}
8691

92+
pub fn realloc(self: Account, new_data_len: u64) error.InvalidRealloc!void {
93+
const diff = @subWithOverflow(new_data_len, self.original_data_len);
94+
if (diff[1] == 0 and diff[0] > ACCOUNT_DATA_PADDING) {
95+
return error.InvalidRealloc;
96+
}
97+
self.reallocUnchecked(new_data_len);
98+
}
99+
100+
pub fn reallocUnchecked(self: Account, new_data_len: u64) void {
101+
self.ptr.data_len = new_data_len;
102+
}
103+
87104
pub fn info(self: Account) Account.Info {
88-
const data_ptr = @as([*]u8, @ptrFromInt(@intFromPtr(self.ptr))) + @sizeOf(Account.Data);
89-
const rent_epoch = @as(*u64, @ptrFromInt(std.mem.alignForward(usize, @intFromPtr(self.ptr) + self.ptr.data_len + ACCOUNT_DATA_PADDING, @alignOf(usize))));
105+
const data_ptr = @as([*]u8, @ptrFromInt(@intFromPtr(self.ptr))) + DATA_HEADER;
106+
const rent_epoch = @as(*u64, @ptrFromInt(std.mem.alignForward(u64, @intFromPtr(self.ptr) + self.ptr.data_len + ACCOUNT_DATA_PADDING, @alignOf(u64))));
90107

91108
return .{
92109
.id = &self.ptr.id,

src/context.zig

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ pub const Context = struct {
99
num_accounts: usize,
1010
accounts: [64]Account,
1111
data: []const u8,
12-
program_id: *PublicKey,
12+
program_id: *align(1) PublicKey,
1313

1414
pub fn load(input: [*]u8) !Context {
1515
var ptr: [*]u8 = input;
@@ -25,7 +25,7 @@ pub const Context = struct {
2525
ptr += @sizeOf(usize);
2626
accounts[i] = accounts[data.duplicate_index];
2727
} else {
28-
ptr += @sizeOf(Account.Data);
28+
ptr += Account.DATA_HEADER;
2929
ptr = @as([*]u8, @ptrFromInt(std.mem.alignForward(usize, @intFromPtr(ptr) + data.data_len + ACCOUNT_DATA_PADDING, @alignOf(usize))));
3030
ptr += @sizeOf(u64);
3131
accounts[i] = .{ .ptr = @as(*Account.Data, @ptrCast(@alignCast(data))) };
@@ -39,7 +39,7 @@ pub const Context = struct {
3939
const data = ptr[0..data_len];
4040
ptr += data_len;
4141

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

4545
return Context{

src/public_key.zig

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,17 @@ pub const ProgramDerivedAddress = struct {
1313
bump_seed: [1]u8,
1414
};
1515

16-
pub const PublicKey = extern struct {
16+
pub const PublicKey = packed struct {
1717
pub const length: usize = 32;
1818
pub const base58_length: usize = 44;
1919

2020
pub const max_num_seeds: usize = 16;
2121
pub const max_seed_length: usize = 32;
2222

23-
bytes: [PublicKey.length]u8,
23+
bytes: u256,
2424

2525
pub fn from(bytes: [PublicKey.length]u8) PublicKey {
26-
return .{ .bytes = bytes };
26+
return .{ .bytes = mem.bytesToValue(u256, &bytes) };
2727
}
2828

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

4949
pub fn equals(self: PublicKey, other: PublicKey) bool {
50-
return mem.eql(u8, &self.bytes, &other.bytes);
50+
return self.bytes == other.bytes;
5151
}
5252

5353
pub fn isPointOnCurve(self: PublicKey) bool {
54-
const Y = std.crypto.ecc.Curve25519.Fe.fromBytes(self.bytes);
54+
const Y = std.crypto.ecc.Curve25519.Fe.fromBytes(mem.toBytes(self.bytes));
5555
const Z = std.crypto.ecc.Curve25519.Fe.one;
5656
const YY = Y.sq();
5757
const u = YY.sub(Z);
@@ -125,9 +125,9 @@ pub const PublicKey = extern struct {
125125
inline while (i < seeds.len) : (i += 1) {
126126
hasher.update(seeds[i]);
127127
}
128-
hasher.update(&program_id.bytes);
128+
hasher.update(mem.asBytes(&program_id.bytes));
129129
hasher.update("ProgramDerivedAddress");
130-
hasher.final(&address.bytes);
130+
hasher.final(mem.asBytes(&address.bytes));
131131

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

src/rent.zig

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ pub const Rent = struct {
2121
/// Account storage overhead for calculation of base rent.
2222
pub const account_storage_overhead: u64 = 128;
2323

24-
pub const Data = extern struct {
24+
pub const Data = packed struct {
2525
lamports_per_byte_year: u64 = Rent.default_lamports_per_byte_year,
2626
exemption_threshold: f64 = Rent.default_exemption_threshold,
2727
burn_percent: u8 = Rent.default_burn_percent,
@@ -43,7 +43,7 @@ pub const Rent = struct {
4343

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

0 commit comments

Comments
 (0)