~bpf.sol | 0x04: Sealevel Syscalls

This page lists the syscalls available in the Sealevel VM.

Syscalls allow programs running in the Solana virtual machine to interact with outside resources. They can be invoked using the call instruction with the immediate value set to the symbol hash.

Syscall Table

Hash Symbol
b6fc1a11 abort
686093bb sol_panic_
207559bd sol_log_
5c2a3178 sol_log_64_
52ba5096 sol_log_compute_units_
7ef088ca sol_log_pubkey
9377323c sol_create_program_address
48504a38 sol_try_find_program_address
11f49d86 sol_sha256
d7793abb sol_keccak256
17e40350 sol_secp256k1_recover
174c5122 sol_blake3
aa2607ca sol_curve_validate_point
dd1c41a6 sol_curve_group_op
d56b5fe9 sol_get_clock_sysvar
23a29a61 sol_get_epoch_schedule_sysvar
3b97b73c sol_get_fees_sysvar
bf7188f6 sol_get_rent_sysvar
717cc4a3 sol_memcpy_
434371f8 sol_memmove_
5fdcde31 sol_memcmp_
3770fb22 sol_memset_
a22b9c85 sol_invoke_signed_c
d7449092 sol_invoke_signed_rust
83f00e8f sol_alloc_free_
a226d3eb sol_set_return_data
5d2245e4 sol_get_return_data
7317b434 sol_log_data
adb8efc8 sol_get_processed_sibling_instruction
85532d94 sol_get_stack_height

Calling Convention

The convention for invoking syscalls follows the SBFv2 ABI for function calls.

Input params are provided in registers r1, r2, r3, r4, r5.
Register r0 is always set, either to the return value, or zero for void syscalls.
Other registers are preserved. Syscalls do not allocate a call frame.

If the caller provides invalid inputs that cause a runtime error (e.g. calling sol_memcpy_ on null pointer), the VM raises the error at the call site.

Syscall Reference

abort

See abort(3). Aborts the VM with SyscallError::Abort and never returns.

#include <stdlib.h>
noreturn void abort();

sol_panic_

Aborts the VM with SyscallError::Panic and never returns.

#include <sol/assert.h>
noreturn void sol_panic_(/* r1 */ const char *file_str,
                         /* r2 */ uint64_t file_str_len,
                         /* r3 */ uint64_t line,
                         /* r4 */ uint64_t column);

sol_log_

Writes a log entry to the Sealevel logging facility.

#include <sol/log.h>
void sol_log_(/* r1 */ const char *message,
              /* r2 */ uint64_t len);

sol_log_64_

Writes a log entry containing five 64-bit integers to the Sealevel logging facility. The log message uses format Program log: %x %x %x %x %x.

#include <sol/log.h>
void sol_log_64_(uint64_t r1,
                 uint64_t r2,
                 uint64_t r3,
                 uint64_t r4,
                 uint64_t r5);

sol_log_compute_units_

Writes the current compute unit consumption to the Sealevel logging facility.
The log message uses format Program consumption: %d units remaining

#include <sol/log.h>
void sol_log_compute_units_();

sol_log_pubkey

Writes a log entry with a Base58-encoded Solana public key.
The log message uses format Program log: %s

#include <sol/pubkey.h>
void sol_log_pubkey(/* r1 */ const SubPubkey *);

sol_create_program_address

Calculates a program-derived address (PDA) from the given program ID and seed list.

If the resulting address is a valid PDA (i.e. does not lie on Curve25519), returns 0 and writes the address to the pointer in r4. Otherwise returns 1 without writing to memory.

The caller is expected to provide a seeds input such that the PDA derivation succeeds. This makes sol_create_program_address mostly useful for verifying PDA derivations. If you want to find new PDAs, consider using sol_try_find_program_address instead.

See solana_program::pubkey::Pubkey::create_program_address.

#include <sol/pubkey.h>
typedef struct {
  const uint8_t *addr;
  uint64_t len;
} SolSignerSeed;
uint64_t sol_create_program_address(
        /* r1 */ const SolSignerSeed *seeds,
        /* r2 */ int seeds_len,
        /* r3 */ const SolPubkey *program_id,
        /* r4 */ SolPubkey *program_address);

sol_try_find_program_address

Calculates a program-derived address (PDA) and bump seed from the given program ID and seed list. Since not all inputs to sol_create_program_address result in successful derivations, the bump seed is used a one-byte-sized nonce that is appended to the seed list to search the input space for the next valid derivation. The search starts with bump seed 0 and ends at 255, choosing the first value that results in a valid PDA. Each iteration writes the derived address to the pointer in r4 and the bump seed byte to the pointer in r5.

Returns 0 on success. In the unlikely case that the search fails, 1 is returned. In the highly unlikely failure case, bump seed 255 along with an (invalid) PDA gets written out to memory.

See solana_program::pubkey::Pubkey::try_find_program_address.

#include <sol/pubkey.h>
typedef struct {
  const uint8_t *addr;
  uint64_t len;
} SolSignerSeed;
uint64_t sol_try_find_programs_address(
        /* r1 */ const SolSignerSeed *seeds,
        /* r2 */ int seeds_len,
        /* r3 */ const SolPubkey *program_id,
        /* r4 */ SolPubkey *program_address,
        /* r5 */ uint8_t *bump_seed);

sol_sha256

Calculates the SHA-256 hash for the given byte inputs. The byte slices pointed to by r1 each get hashed as if they were a contiguous array. r2 specifies the number of byte slices. It is permitted to hash overlapping byte slices.

The default allowed max allowed slices count per operation is 20000. The compute unit costs are made up from two components:

  • sha256_base_cost (default 10) once per syscall invocation.
  • Per slice, sha256_byte_cost times the number of two-byte pairs per slice or minimally mem_op_base_cost for very short slices.
#include <sol/sha.h>
typedef struct {
  const uint8_t *addr;
  uint64_t len;
} SolBytes;
void sol_sha256(/* r1 */ const SolBytes *bytes,
                /* r2 */ int bytes_len,
                /* r3 */ uint8_t *result);

sol_keccak256

Calculates the Keccak-256 hash for the given byte inputs. Keccak-256 is similar to NIST SHA3-256 but preserves the original padding from the NIST competition submission, not the FIPS 202 variant. This version is compatible with the Ethereum protocols.

The function signature and compute unit costs are identical to sol_sha256.

#include <sol/keccak.h>
typedef struct {
  const uint8_t *addr;
  uint64_t len;
} SolBytes;
void sol_keccak256(/* r1 */ const SolBytes *bytes,
                   /* r2 */ int bytes_len,
                   /* r3 */ uint8_t *result);

sol_secp256k1_recover

Recovers a secp256k1 public key from a signed message. Writes the uncompressed public key (64 bytes) to the pointer in r4. Compatible with Bitcoin and Ethereum.

Return codes:

  • 0x00: Success
  • 0x01: Invalid hash provided
  • 0x02: Invalid signature provided
#include <sol/secp256k1.h>
uint64_t sol_secp256k1_recover(/* r1 */ const uint8_t *hash,
                               /* r2 */ uint64_t recovery_id,
                               /* r3 */ const uint8_t *signature,
                               /* r4 */ uint8_t *result);

sol_blake3

Calculates the BLAKE3 hash for the given byte inputs.

The function signature and compute unit costs are identical to sol_sha256.

#include <sol/blake3.h>
typedef struct {
  const uint8_t *addr;
  uint64_t len;
} SolBytes;
void sol_blake3(/* r1 */ const SolBytes *bytes,
                /* r2 */ int bytes_len,
                /* r3 */ uint8_t *result);

sol_curve_validate_point

Validates an elliptic curve point. Returns 0 if the point is valid, otherwise 1.

Supported elliptic curves (specified in r1):

  • 0x00: CURVE25519_EDWARDS
  • 0x01: CURVE25519_RISTRETTO
uint64_t sol_curve_validate_point(
        /* r1 */ int curve_id,
        /* r2 */ const void *point);

sol_curve_group_op

Provides elliptic curve group operations. Returns 0 on success, otherwise 1.

See sol_curve_validate_point for supported curves.

Supported group operations (specified in r2):

  • 0x00: Addition
  • 0x01: Subtraction
  • 0x02: Multiplication

r3 and r4 point to the left and right input curve points. If the operation was successful, the resulting point gets written to the pointer in r5.

uint64_t sol_curve_group_op(
        /* r1 */ int curve_id,
        /* r2 */ int group_op,
        /* r3 */ const void *left_point,
        /* r4 */ const void *right_point,
        /* r5 */ void *result_point);

sol_get_clock_sysvar

Writes the clock sysvar to the pointer in r1.

typedef struct {
  uint64_t slot;
  int64_t epoch_start_timestamp;
  uint64_t epoch;
  uint64_t leader_schedule_epoch;
  int64_t unix_timestamp;
} SysvarClock;
void sol_get_clock_sysvar(/* r1 */ SysvarClock *);

sol_get_epoch_schedule_sysvar

Writes the epoch schedule sysvar to the pointer in r1.

typedef struct {
  uint64_t slots_per_epoch;
  uint64_t leader_schedule_slot_offset;
  bool warmup; /* one byte */
  uint64_t first_normal_epoch;
  uint64_t first_normal_slot;
} SysvarEpochSchedule;
void sol_get_epoch_schedule_sysvar(/* r1 */ SysvarEpochSchedule *);

sol_get_fees_sysvar

Writes the fees sysvar to the pointer in r1.

typedef struct {
  uint64_t lamports_per_signature;
} SysvarFees;
void sol_get_fees_sysvar(/* r1 */ SysvarFees *);

sol_get_rent_sysvar

Writes the rent sysvar to the pointer in r1.

typedef struct {
  uint64_t lamports_per_byte_year;
  double exemption_threshold;
  uint8_t burn_percent;
} SysvarRent;
void sol_get_rent_sysvar(/* r1 */ SysvarRent *);

sol_memcpy_

See memcpy(3).

Throws the SyscallError::CopyOverlapping runtime error if dest and src overlap.

void sol_memcpy_(/* r1 */ void *restrict dest,
                 /* r2 */ const void *restrict src,
                 /* r3 */ size_t n);

sol_memmove_

See memmove(3).

void sol_memmove_(/* r1 */ void *dest,
                  /* r2 */ const void *src,
                  /* r3 */ size_t n);

sol_memcmp_

See memcmp(3).

int sol_memcmp_(/* r1 */ const void *s1,
                /* r2 */ const void *s2,
                /* r3 */ size_t n);

sol_memset_

See memset(3).

void *sol_memset_(/* r1 */ void *s,
                  /* r2 */ int c,
                  /* r3 */ size_t n);

sol_invoke_signed_c

Executes a cross-program invocation (CPI) given a Sealevel instruction, account infos and a list of seed list to derive signers. Each seed list determines one PDA to set as a signer.

Input r1 points to the instruction that will execute the CPI.
Input r2 points to the account infos array, r3 specifies its length.
Input r4 points to the array of seed lists, r5 specifies its length.

Returns with r0 set to zero.

#include <sol/cpi.h>

typedef struct {
  SolPubkey *pubkey;
  bool is_writable;
  bool is_signer;
} SolAccountMeta;

typedef struct {
  SolPubkey *program_id;
  SolAccountMeta *accounts;
  uint64_t account_len;
  uint8_t *data;
  uint64_t data_len;
} SolInstruction;

typedef struct {
  SolPubkey *key;
  uint64_t *lamports;
  uint64_t data_len;
  uint8_t *data;
  SolPubkey *owner;
  uint64_t rent_epoch;
  bool is_signer;
  bool is_writable;
  bool executable;
} SolAccountInfo;

typedef struct {
  const uint8_t *seed;
  uint64_t seed_len;
} SolSignerSeed;

typedef struct {
  const SolSignerSeed *seeds;
  uint64_t num_seeds;
} SolSignerSeeds;

void sol_invoke_signed_c(
        /* r1 */ const SolInstruction *instruction,
        /* r2 */ const SolAccountInfo *account_infos,
        /* r3 */ int num_account_infos,
        /* r4 */ const SolSignerSeeds *signers_seeds,
        /* r5 */ int num_signers_seeds);

sol_invoke_signed_rust

Identical effects to sol_invoke_signed_c but using the Rust ABI for input parameters. This is the most complex syscall and scheduled to be removed soon. Please refer to the PR below for the reference of this syscall.

🤝
Thanks to Robert Chen at OtterSec for helping me out with CPI entrypoints. They are by far the most complex syscalls.
We also fixed some portability/soundness issues with the host implementation of this syscall: solana-labs/solana#26531

sol_alloc_free_

Provides memory allocation primitives backed by a bump allocator.

If r2 is a null pointer, allocates a chunk of memory of at least the size in r1. Returns a valid pointer on success or a null pointer on failure.

If r2 is a valid pointer, does nothing.

void *sol_alloc_free_(/* r1 */ uint64_t size,
                      /* r2 */ void *free_addr);

sol_set_return_data

Sets the return data of the current instruction. This data can be retrieved with sol_get_return_data in the parent instruction's context.

Copies the return data from the pointer in r1 with data len given in r2 into the return data buffer.

#include <sol/return_data.h>
void sol_set_return_data(/* r1 */ const void *addr,
                         /* r2 */ uint64_t len);

sol_get_return_data

Retrieves the return data of the CPI that has last returned back to the current context.

Copies return data into memory at r1 with max data len given in r2. Writes the program ID that set the return data into r3. Returns the number of bytes read, or 0 if no return data was set.

#include <sol/return_data.h>
uint64_t sol_get_return_data(/* r1 */ void *return_addr,
                             /* r2 */ uint64_t max_len,
                             /* r3 */ SolPubkey *program_id);

sol_log_data

Writes a log entry containing Base64-encoded to the Sealevel logging facility. The log message uses format Program log: %s. The byte slices pointed to by r1 each get encoded as if they were a contiguous array.

The compute unit costs are made up from three components:

  • syscall_base_cost (default 100) once per syscall invocation.
  • syscall_base_cost (default 100) times the number of slices.
  • One cost unit per byte read in total.
#include <sol/log.h>
void sol_log_data(/* r1 */ SolBytes *slices,
                  /* r2 */ uint64_t num_slices);

sol_get_processed_sibling_instruction

Copies data of a processed sibling Sealevel instruction to memory. For transaction-level instructions, the list of sibling instructions are the programs that have been invoked previously in the same transaction. Otherwise, it is the list of CPIs that the parent instruction has executed.

  • r1 specifies the instruction index in the list.
  • r2 points to a descriptor of the inputs of the target instruction. As an input param, it specifies the expected instruction data length and number of accounts. After syscall execution, the fields are set to the actual values.
  • r3 is filled in with the program ID invoked by the sibling instruction.
  • If the specified data length in r2 matches the actual invocation, copies the instruction bytes to r4 and the accounts meta list to r5.
typedef struct {
  uint64_t data_len;
  uint64_t num_accounts;
} SolProcessedSiblingInstruction;

typedef struct {
  SolPubkey pubkey;
  bool is_signer; /* one byte */
  bool is_writable; /* one byte */
} SolAccountMeta;

bool sol_get_processed_sibling_instruction(
        /* r1 */ int index,
        /* r2 */ SolProcessedSiblingInstruction *meta,
        /* r3 */ SolPubkey *program_id,
        /* r4 */ void *data,
        /* r5 */ SolAccountMeta *accounts);

sol_get_stack_height

Returns the height of the Sealevel invocation stack, which is 1 at transaction level and increases for every cross-program invocation. Note that this is unrelated to the SBF call stack.

uint64_t sol_get_stack_height();

Subscribe to bpf.wtf

Don’t miss out on the latest issues. Sign up now to get access to the library of members-only issues.
jamie@example.com
Subscribe