Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(bpf): instruction handling #609

Open
wants to merge 19 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/runtime/borrowed_account.zig
Original file line number Diff line number Diff line change
@@ -200,6 +200,10 @@ pub const BorrowedAccount = struct {
self.account.owner = pubkey;
}

pub fn isOwnedByCurrentProgram(self: *const BorrowedAccount) bool {
return self.account.owner.equals(&self.context.program_id);
}

/// [agave] https://github.com/anza-xyz/agave/blob/134be7c14066ea00c9791187d6bbc4795dd92f0e/sdk/src/transaction_context.rs#L1001
pub fn setExecutable(
self: *BorrowedAccount,
3 changes: 3 additions & 0 deletions src/runtime/feature_set.zig
Original file line number Diff line number Diff line change
@@ -10,6 +10,9 @@ pub const LIFT_CPI_CALLER_RESTRICTION =
pub const REMOVE_ACCOUNTS_EXECUTABLE_FLAG_CHECKS =
Pubkey.parseBase58String("FfgtauHUWKeXTzjXkua9Px4tNGBFHKZ9WaigM5VbbzFx") catch unreachable;

pub const ENABLE_BPF_LOADER_SET_AUTHORITY_CHECKED_IDX =
Pubkey.parseBase58String("HcW8ZjBezYYgvcbxNJwqv1t484Y2556qJsfNDWvJGZRH") catch unreachable;

/// `FeatureSet` holds the set of currently active and inactive features
///
/// TODO: add features
7 changes: 0 additions & 7 deletions src/runtime/ids.zig
Original file line number Diff line number Diff line change
@@ -19,13 +19,6 @@ pub const SYSVAR_REWARDS_ID =

pub const ADDRESS_LOOKUP_TABLE_PROGRAM_ID =
Pubkey.parseBase58String("AddressLookupTab1e1111111111111111111111111") catch unreachable;
// Deprecated
pub const BPF_LOADER_V1_PROGRAM_ID =
Pubkey.parseBase58String("BPFLoader1111111111111111111111111111111111") catch unreachable;
pub const BPF_LOADER_V2_PROGRAM_ID =
Pubkey.parseBase58String("BPFLoader2111111111111111111111111111111111") catch unreachable;
pub const BPF_LOADER_V3_PROGRAM_ID =
Pubkey.parseBase58String("BPFLoaderUpgradeab1e11111111111111111111111") catch unreachable;
pub const COMPUTE_BUDGET_PROGRAM_ID =
Pubkey.parseBase58String("ComputeBudget111111111111111111111111111111") catch unreachable;
pub const CONFIG_PROGRAM_ID =
6 changes: 6 additions & 0 deletions src/runtime/instruction_context.zig
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const std = @import("std");
const sig = @import("../sig.zig");

const Pubkey = sig.core.Pubkey;
const InstructionError = sig.core.instruction.InstructionError;

const InstructionInfo = sig.runtime.InstructionInfo;
@@ -67,4 +68,9 @@ pub const InstructionContext = struct {

return self.tc.sysvar_cache.get(T) orelse InstructionError.UnsupportedSysvar;
}

pub fn getAccountKeyByIndex(self: *const InstructionContext, index: u16) Pubkey {
const account_meta = self.info.getAccountMetaAtIndex(index) orelse unreachable;
return account_meta.pubkey;
}
};
2,387 changes: 2,387 additions & 0 deletions src/runtime/program/bpf_loader_program/execute.zig

Large diffs are not rendered by default.

36 changes: 36 additions & 0 deletions src/runtime/program/bpf_loader_program/lib.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
const sig = @import("../../../sig.zig");

const Pubkey = sig.core.Pubkey;

pub const v1 = struct {
/// [agave] https://github.com/anza-xyz/agave/blob/c5ed1663a1218e9e088e30c81677bc88059cc62b/sdk/sdk-ids/src/lib.rs#L11
pub const ID =
Pubkey.parseBase58String("BPFLoader1111111111111111111111111111111111") catch unreachable;

/// [agave] https://github.com/anza-xyz/agave/blob/faea52f338df8521864ab7ce97b120b2abb5ce13/programs/bpf_loader/src/lib.rs#L56
pub const COMPUTE_UNITS = 1_140;
};

pub const v2 = struct {
/// [agave] https://github.com/anza-xyz/agave/blob/c5ed1663a1218e9e088e30c81677bc88059cc62b/sdk/sdk-ids/src/lib.rs#L7
pub const ID =
Pubkey.parseBase58String("BPFLoader2111111111111111111111111111111111") catch unreachable;

/// [agave] https://github.com/anza-xyz/agave/blob/faea52f338df8521864ab7ce97b120b2abb5ce13/programs/bpf_loader/src/lib.rs#L55
pub const COMPUTE_UNITS = 570;
};

pub const v3 = struct {
/// [agave] https://github.com/anza-xyz/agave/blob/c5ed1663a1218e9e088e30c81677bc88059cc62b/sdk/sdk-ids/src/lib.rs#L15-L16
pub const ID =
Pubkey.parseBase58String("BPFLoaderUpgradeab1e11111111111111111111111") catch unreachable;

/// [agave] https://github.com/anza-xyz/agave/blob/faea52f338df8521864ab7ce97b120b2abb5ce13/programs/bpf_loader/src/lib.rs#L57
pub const COMPUTE_UNITS = 2_370;

pub const instruction = @import("v3_instruction.zig");
pub const Instruction = instruction.Instruction;
pub const State = @import("v3_state.zig").State;
};

pub const execute = @import("execute.zig").execute;
267 changes: 267 additions & 0 deletions src/runtime/program/bpf_loader_program/v3_instruction.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,267 @@
pub const InitializeBuffer = struct {
pub const AccountIndex = enum(u1) {
/// `[WRITE]` source account to initialize.
account = 0,
/// `[]` Buffer authority, optional, if omitted then the buffer will be immutable.
authority = 1,
};
};

pub const Write = struct {
/// Offset at which to write the given bytes.
offset: u32,
/// Serialized program data
bytes: []const u8,

pub const AccountIndex = enum(u1) {
/// `[WRITE]` Buffer account to write program data to.
account = 0,
/// `[SIGNER]` Buffer authority.
authority = 1,
};
};

pub const DeployWithMaxDataLen = struct {
/// Maximum length that the program can be upgraded to.
max_data_len: usize,

pub const AccountIndex = enum(u3) {
/// `[WRITE, SIGNER]` The payer account that will pay to create the ProgramData account.
payer = 0,
/// `[WRITE]` The uninitialized ProgramData account.
program_data = 1,
/// `[WRITE]` The uninitialized Program account.
program = 2,
/// `[WRITE]` The Buffer account where the program data has been written.
/// The buffer account's authority must match the program's authority.
buffer = 3,
/// `[]` Rent sysvar.
rent = 4,
/// `[]` Clock sysvar.
clock = 5,
/// `[]` System program (`solana_sdk::system_program::id()`).
system_program = 6,
/// `[SIGNER]` The program's authority.
authority = 7,
};
};

pub const Upgrade = struct {
pub const AccountIndex = enum(u7) {
/// `[WRITE]` The ProgramData account.
program_data = 0,
/// `[WRITE]` The Program account.
program = 1,
/// `[WRITE]` The Buffer account where the program data has been written.
/// The buffer account's authority must match the program's authority.
buffer = 2,
/// `[WRITE]` The spill account.
spill = 3,
/// `[]` Rent sysvar.
rent = 4,
/// `[]` Clock sysvar.
clock = 5,
/// `[SIGNER]` The program's authority.
authority = 6,
};
};

pub const SetAuthority = struct {
pub const AccountIndex = enum(u2) {
/// `[WRITE]` The Buffer or ProgramData account to change the authority of.
account = 0,
/// `[SIGNER]` The current authority.
present_authority = 1,
/// `[]` The new authority, optional, if omitted then the program will not be upgradeable.
new_authority = 2,
};
};

pub const SetAuthorityChecked = struct {
pub const AccountIndex = enum(u2) {
/// `[WRITE]` The Buffer or ProgramData account to change the authority of.
account = 0,
/// `[SIGNER]` The current authority.
present_authority = 1,
/// `[SIGNER]` The new authority.
new_authority = 2,
};
};

pub const Close = struct {
pub const AccountIndex = enum(u2) {
/// `[WRITE]` The account to close, if closing a program must be the ProgramData account.
account = 0,
/// `[WRITE]` The account to deposit the closed account's lamports.
recipient = 1,
/// `[SIGNER]` The account's authority, Optional, required for initialized accounts.
authority = 2,
/// `[WRITE]` The associated Program account if the account to close is a ProgramData account.
program = 3,
};
};

pub const ExtendProgram = struct {
/// Number of bytes to extend the program data.
additional_bytes: u32,

pub const AccountIndex = enum(u2) {
/// `[WRITE]` The ProgramData account.
program_data = 0,
/// `[WRITE]` The ProgramData account's associated Program account.
program = 1,
/// `[]` System program (`solana_sdk::system_program::id()`),
/// optional used to transfer lamports from the payer to the ProgramData account.
system_program = 2,
/// `[WRITE, SIGNER]` The payer account, optional, that will pay necessary rent exemption
/// costs for the increased storage size.
payer = 3,
};
};

/// [agave] https://github.com/anza-xyz/agave/blob/master/sdk/program/src/loader_upgradeable_instruction.rs#L7
pub const Instruction = union(enum) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we change the instruction definition to use the typed + index enum approach similar to

pub const IntializeAccount = struct {

/// Initialize a Buffer account.
///
/// A Buffer account is an intermediary that once fully populated is used
/// with the `DeployWithMaxDataLen` instruction to populate the program's
/// ProgramData account.
///
/// The `InitializeBuffer` instruction requires no signers and MUST be
/// included within the same Transaction as the system program's
/// `CreateAccount` instruction that creates the account being initialized.
/// Otherwise another party may initialize the account.
///
/// # Account references
/// 0. `[writable]` source account to initialize.
/// 1. `[]` Buffer authority, optional, if omitted then the buffer will be
/// immutable.
initialize_buffer: InitializeBuffer,

/// Write program data into a Buffer account.
///
/// # Account references
/// 0. `[writable]` Buffer account to write program data to.
/// 1. `[signer]` Buffer authority
write: Write,

/// Deploy an executable program.
///
/// A program consists of a Program and ProgramData account pair.
/// - The Program account's address will serve as the program id for any
/// instructions that execute this program.
/// - The ProgramData account will remain mutable by the loader only and
/// holds the program data and authority information. The ProgramData
/// account's address is derived from the Program account's address and
/// created by the DeployWithMaxDataLen instruction.
///
/// The ProgramData address is derived from the Program account's address as
/// follows:
///
/// ```
/// # use solana_program::pubkey::Pubkey;
/// # use solana_program::bpf_loader_upgradeable;
/// # let program_address = &[];
/// let (program_data_address, _) = Pubkey::find_program_address(
/// &[program_address],
/// &bpf_loader_upgradeable::id()
/// );
/// ```
///
/// The `DeployWithMaxDataLen` instruction does not require the ProgramData
/// account be a signer and therefore MUST be included within the same
/// Transaction as the system program's `CreateAccount` instruction that
/// creates the Program account. Otherwise another party may initialize the
/// account.
///
/// # Account references
/// 0. `[writable, signer]` The payer account that will pay to create the
/// ProgramData account.
/// 1. `[writable]` The uninitialized ProgramData account.
/// 2. `[writable]` The uninitialized Program account.
/// 3. `[writable]` The Buffer account where the program data has been
/// written. The buffer account's authority must match the program's
/// authority
/// 4. `[]` Rent sysvar.
/// 5. `[]` Clock sysvar.
/// 6. `[]` System program (`solana_sdk::system_program::id()`).
/// 7. `[signer]` The program's authority
deploy_with_max_data_len: DeployWithMaxDataLen,

/// Upgrade a program.
///
/// A program can be updated as long as the program's authority has not been
/// set to `None`.
///
/// The Buffer account must contain sufficient lamports to fund the
/// ProgramData account to be rent-exempt, any additional lamports left over
/// will be transferred to the spill account, leaving the Buffer account
/// balance at zero.
///
/// # Account references
/// 0. `[writable]` The ProgramData account.
/// 1. `[writable]` The Program account.
/// 2. `[writable]` The Buffer account where the program data has been
/// written. The buffer account's authority must match the program's
/// authority
/// 3. `[writable]` The spill account.
/// 4. `[]` Rent sysvar.
/// 5. `[]` Clock sysvar.
/// 6. `[signer]` The program's authority.
upgrade: Upgrade,

/// Set a new authority that is allowed to write the buffer or upgrade the
/// program. To permanently make the buffer immutable or disable program
/// updates omit the new authority.
///
/// # Account references
/// 0. `[writable]` The Buffer or ProgramData account to change the
/// authority of.
/// 1. `[signer]` The current authority.
/// 2. `[]` The new authority, optional, if omitted then the program will
/// not be upgradeable.
set_authority: SetAuthority,

/// Closes an account owned by the upgradeable loader of all lamports and
/// withdraws all the lamports
///
/// # Account references
/// 0. `[writable]` The account to close, if closing a program must be the
/// ProgramData account.
/// 1. `[writable]` The account to deposit the closed account's lamports.
/// 2. `[signer]` The account's authority, Optional, required for
/// initialized accounts.
/// 3. `[writable]` The associated Program account if the account to close
/// is a ProgramData account.
close: Close,

/// Extend a program's ProgramData account by the specified number of bytes.
/// Only upgradeable program's can be extended.
///
/// The payer account must contain sufficient lamports to fund the
/// ProgramData account to be rent-exempt. If the ProgramData account
/// balance is already sufficient to cover the rent exemption cost
/// for the extended bytes, the payer account is not required.
///
/// # Account references
/// 0. `[writable]` The ProgramData account.
/// 1. `[writable]` The ProgramData account's associated Program account.
/// 2. `[]` System program (`solana_sdk::system_program::id()`), optional, used to transfer
/// lamports from the payer to the ProgramData account.
/// 3. `[writable, signer]` The payer account, optional, that will pay
/// necessary rent exemption costs for the increased storage size.
extend_program: ExtendProgram,

/// Set a new authority that is allowed to write the buffer or upgrade the
/// program.
///
/// This instruction differs from SetAuthority in that the new authority is a
/// required signer.
///
/// # Account references
/// 0. `[writable]` The Buffer or ProgramData account to change the
/// authority of.
/// 1. `[signer]` The current authority.
/// 2. `[signer]` The new authority.
set_authority_checked: SetAuthorityChecked,
};
54 changes: 54 additions & 0 deletions src/runtime/program/bpf_loader_program/v3_state.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
const sig = @import("../../../sig.zig");

const Pubkey = sig.core.Pubkey;

/// [agave] https://github.com/anza-xyz/agave/blob/5fb000f27e476add032e08a1de9e89310b0eab4b/sdk/program/src/bpf_loader_upgradeable.rs#L29
pub const State = union(enum) {
/// Account is not initialized.
uninitialized,
/// A Buffer account.
buffer: struct {
/// Authority address
authority_address: ?Pubkey,
// The raw program data follows this serialized structure in the
// account's data.
},
/// An Program account.
program: struct {
/// Address of the ProgramData account.
programdata_address: Pubkey,
},
// A ProgramData account.
program_data: struct {
/// Slot that the program was last modified.
slot: u64,
/// Address of the Program's upgrade authority.
upgrade_authority_address: ?Pubkey,
// The raw program data follows this serialized structure in the
// account's data.
},

pub const UNINITIALIZED_SIZE: usize = 4;
pub const BUFFER_METADATA_SIZE: usize = 37;
pub const PROGRAM_SIZE: usize = 36;
pub const PROGRAM_DATA_METADATA_SIZE: usize = 45;

pub fn serializedSize(self: State) !usize {
return switch (self) {
.uninitialized => UNINITIALIZED_SIZE,
.buffer => BUFFER_METADATA_SIZE,
.program => PROGRAM_SIZE,
.program_data => PROGRAM_DATA_METADATA_SIZE,
};
}

/// [agave] https://github.com/anza-xyz/solana-sdk/blob/c07f692e41d757057c8700211a9300cdcd6d33b1/loader-v3-interface/src/state.rs#L57
pub fn sizeOfBuffer(program_len: usize) usize {
return BUFFER_METADATA_SIZE +| program_len;
}

/// [agave] https://github.com/anza-xyz/solana-sdk/blob/c07f692e41d757057c8700211a9300cdcd6d33b1/loader-v3-interface/src/state.rs#L62
pub fn sizeOfProgramData(program_len: usize) usize {
return PROGRAM_DATA_METADATA_SIZE +| program_len;
}
};
7 changes: 5 additions & 2 deletions src/runtime/program/lib.zig
Original file line number Diff line number Diff line change
@@ -4,6 +4,7 @@ const sig = @import("../../sig.zig");
const InstructionError = sig.core.instruction.InstructionError;
const InstructionContext = sig.runtime.InstructionContext;

pub const bpf_loader_program = @import("bpf_loader_program/lib.zig");
pub const precompile_programs = @import("precompile_programs/lib.zig");
pub const system_program = @import("system_program/lib.zig");
pub const testing = @import("testing.zig");
@@ -12,15 +13,17 @@ pub const vote_program = @import("vote/lib.zig");
pub const PROGRAM_ENTRYPOINTS = initProgramEntrypoints();
pub const PRECOMPILE_ENTRYPOINTS = initPrecompileEntrypoints();

const EntrypointFn =
*const fn (
const EntrypointFn = *const fn (
std.mem.Allocator,
*InstructionContext,
) (error{OutOfMemory} || InstructionError)!void;

fn initProgramEntrypoints() std.StaticStringMap(EntrypointFn) {
@setEvalBranchQuota(5000);
return std.StaticStringMap(EntrypointFn).initComptime(&.{
.{ bpf_loader_program.v1.ID.base58String().slice(), bpf_loader_program.execute },
.{ bpf_loader_program.v2.ID.base58String().slice(), bpf_loader_program.execute },
.{ bpf_loader_program.v3.ID.base58String().slice(), bpf_loader_program.execute },
.{ system_program.ID.base58String().slice(), system_program.execute },
.{ vote_program.ID.base58String().slice(), vote_program.execute },
});
21 changes: 21 additions & 0 deletions src/runtime/pubkey_utils.zig
Original file line number Diff line number Diff line change
@@ -143,6 +143,27 @@ test "findProgramAddress" {
}
}

test "createProgramAddress" {
var prng = std.Random.DefaultPrng.init(5083);
for (0..1_000) |_| {
const program_id = Pubkey.initRandom(prng.random());

const derived_key, const bump_seed = findProgramAddress(
&.{ "Lil'", "Bits" },
program_id,
) orelse unreachable;

try std.testing.expectEqual(
derived_key,
createProgramAddress(
&.{ "Lil'", "Bits" },
&.{bump_seed},
program_id,
),
);
}
}

test "bytesAreCurvePoint" {
const bytes_on_curve: []const []const u8 = &.{ &.{
184, 122, 70, 205, 215, 194, 55, 219,
1 change: 1 addition & 0 deletions src/runtime/sysvar_cache.zig
Original file line number Diff line number Diff line change
@@ -23,6 +23,7 @@ pub const SysvarCache = struct {
fees: ?sysvar.Fees = null,
recent_blockhashes: ?sysvar.RecentBlockhashes = null,

/// TODO: [agave] https://github.com/anza-xyz/agave/blob/5fa721b3b27c7ba33e5b0e1c55326241bb403bb1/program-runtime/src/sysvar_cache.rs#L130-L141
pub fn get(self: SysvarCache, comptime T: type) ?T {
return switch (T) {
sysvar.Clock => self.clock,