Skip to content

Commit

Permalink
program: introduce core BPF implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
buffalojoec committed Mar 5, 2024
1 parent ad4436b commit 227a8a0
Show file tree
Hide file tree
Showing 10 changed files with 1,623 additions and 238 deletions.
507 changes: 270 additions & 237 deletions Cargo.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
[workspace]
resolver = "2"
members = ["clients/rust"]
members = ["clients/rust", "program"]
35 changes: 35 additions & 0 deletions program/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
[package]
name = "address-lookup-table"
version = "0.1.0"
edition = "2021"
readme = "./README.md"
license-file = "../LICENSE"
publish = false

[package.metadata.solana]
program-id = "AddressLookupTab1e1111111111111111111111111"

[features]
no-entrypoint = []
test-sbf = []

[dependencies]
bincode = "1.3.3"
bytemuck = "1.14.1"
log = "0.4.20"
serde = { version = "1.0.193", features = ["derive"] }
solana-frozen-abi = "1.18.2"
solana-frozen-abi-macro = "1.18.2"
solana-program = "1.18.2"
spl-program-error = "0.3.1"

[dev-dependencies]
assert_matches = "1.5.0"
solana-program-test = "1.18.2"
solana-sdk = "1.18.2"

[lib]
crate-type = ["cdylib", "lib"]

[build-dependencies]
rustc_version = "0.4"
28 changes: 28 additions & 0 deletions program/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
//! Required for `solana-frozen-abi-macro` to work.
extern crate rustc_version;
use rustc_version::{version_meta, Channel};

fn main() {
// Copied and adapted from
// https://github.com/Kimundi/rustc-version-rs/blob/1d692a965f4e48a8cb72e82cda953107c0d22f47/README.md#example
// Licensed under Apache-2.0 + MIT
match version_meta().unwrap().channel {
Channel::Stable => {
println!("cargo:rustc-cfg=RUSTC_WITHOUT_SPECIALIZATION");
}
Channel::Beta => {
println!("cargo:rustc-cfg=RUSTC_WITHOUT_SPECIALIZATION");
}
Channel::Nightly => {
println!("cargo:rustc-cfg=RUSTC_WITH_SPECIALIZATION");
}
Channel::Dev => {
println!("cargo:rustc-cfg=RUSTC_WITH_SPECIALIZATION");
// See https://github.com/solana-labs/solana/issues/11055
// We may be running the custom `rust-bpf-builder` toolchain,
// which currently needs `#![feature(proc_macro_hygiene)]` to
// be applied.
println!("cargo:rustc-cfg=RUSTC_NEEDS_PROC_MACRO_HYGIENE");
}
}
}
22 changes: 22 additions & 0 deletions program/src/entrypoint.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
//! Program entrypoint
use {
crate::{error::AddressLookupError, processor},
solana_program::{
account_info::AccountInfo, entrypoint::ProgramResult, program_error::PrintProgramError,
pubkey::Pubkey,
},
};

solana_program::entrypoint!(process_instruction);
fn process_instruction(
program_id: &Pubkey,
accounts: &[AccountInfo],
instruction_data: &[u8],
) -> ProgramResult {
if let Err(error) = processor::process(program_id, accounts, instruction_data) {
error.print::<AddressLookupError>();
return Err(error);
}
Ok(())
}
33 changes: 33 additions & 0 deletions program/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//! Program error types
#[cfg(not(target_os = "solana"))]
use solana_program::message::AddressLoaderError;
use spl_program_error::*;

#[spl_program_error]
pub enum AddressLookupError {
/// Attempted to lookup addresses from a table that does not exist
#[error("Attempted to lookup addresses from a table that does not exist")]
LookupTableAccountNotFound,
/// Attempted to lookup addresses from an account owned by the wrong program
#[error("Attempted to lookup addresses from an account owned by the wrong program")]
InvalidAccountOwner,
/// Attempted to lookup addresses from an invalid account
#[error("Attempted to lookup addresses from an invalid account")]
InvalidAccountData,
/// Address lookup contains an invalid index
#[error("Address lookup contains an invalid index")]
InvalidLookupIndex,
}

#[cfg(not(target_os = "solana"))]
impl From<AddressLookupError> for AddressLoaderError {
fn from(err: AddressLookupError) -> Self {
match err {
AddressLookupError::LookupTableAccountNotFound => Self::LookupTableAccountNotFound,
AddressLookupError::InvalidAccountOwner => Self::InvalidAccountOwner,
AddressLookupError::InvalidAccountData => Self::InvalidAccountData,
AddressLookupError::InvalidLookupIndex => Self::InvalidLookupIndex,
}
}
}
193 changes: 193 additions & 0 deletions program/src/instruction.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
//! Program instruction types
use {
serde::{Deserialize, Serialize},
solana_program::{
clock::Slot,
instruction::{AccountMeta, Instruction},
pubkey::Pubkey,
system_program,
},
};

#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
pub enum ProgramInstruction {
/// Create an address lookup table
///
/// # Account references
/// 0. `[WRITE]` Uninitialized address lookup table account
/// 1. `[SIGNER]` Account used to derive and control the new address
/// lookup table.
/// 2. `[SIGNER, WRITE]` Account that will fund the new address lookup
/// table.
/// 3. `[]` System program for CPI.
CreateLookupTable {
/// A recent slot must be used in the derivation path
/// for each initialized table. When closing table accounts,
/// the initialization slot must no longer be "recent" to prevent
/// address tables from being recreated with reordered or
/// otherwise malicious addresses.
recent_slot: Slot,
/// Address tables are always initialized at program-derived
/// addresses using the funding address, recent blockhash, and
/// the user-passed `bump_seed`.
bump_seed: u8,
},

/// Permanently freeze an address lookup table, making it immutable.
///
/// # Account references
/// 0. `[WRITE]` Address lookup table account to freeze
/// 1. `[SIGNER]` Current authority
FreezeLookupTable,

/// Extend an address lookup table with new addresses. Funding account and
/// system program account references are only required if the lookup table
/// account requires additional lamports to cover the rent-exempt balance
/// after being extended.
///
/// # Account references
/// 0. `[WRITE]` Address lookup table account to extend
/// 1. `[SIGNER]` Current authority
/// 2. `[SIGNER, WRITE, OPTIONAL]` Account that will fund the table
/// reallocation
/// 3. `[OPTIONAL]` System program for CPI.
ExtendLookupTable { new_addresses: Vec<Pubkey> },

/// Deactivate an address lookup table, making it unusable and
/// eligible for closure after a short period of time.
///
/// # Account references
/// 0. `[WRITE]` Address lookup table account to deactivate
/// 1. `[SIGNER]` Current authority
DeactivateLookupTable,

/// Close an address lookup table account
///
/// # Account references
/// 0. `[WRITE]` Address lookup table account to close
/// 1. `[SIGNER]` Current authority
/// 2. `[WRITE]` Recipient of closed account lamports
CloseLookupTable,
}

/// Derives the address of an address table account from a wallet address and a
/// recent block's slot.
pub fn derive_lookup_table_address(
authority_address: &Pubkey,
recent_block_slot: Slot,
) -> (Pubkey, u8) {
Pubkey::find_program_address(
&[authority_address.as_ref(), &recent_block_slot.to_le_bytes()],
&crate::id(),
)
}

// [Core BPF]: `create_lookup_table_signed` has been removed, since feature
// "FKAcEvNgSY79RpqsPNUV5gDyumopH4cEHqUxyfm8b8Ap"
// (relax_authority_signer_check_for_lookup_table_creation) has been activated
// on all clusters.

/// Constructs an instruction to create a table account and returns
/// the instruction and the table account's derived address.
pub fn create_lookup_table(
authority_address: Pubkey,
payer_address: Pubkey,
recent_slot: Slot,
) -> (Instruction, Pubkey) {
let (lookup_table_address, bump_seed) =
derive_lookup_table_address(&authority_address, recent_slot);

let instruction = Instruction::new_with_bincode(
crate::id(),
&ProgramInstruction::CreateLookupTable {
recent_slot,
bump_seed,
},
vec![
AccountMeta::new(lookup_table_address, false),
AccountMeta::new_readonly(authority_address, false),
AccountMeta::new(payer_address, true),
AccountMeta::new_readonly(system_program::id(), false),
],
);

(instruction, lookup_table_address)
}

/// Constructs an instruction that freezes an address lookup
/// table so that it can never be closed or extended again. Empty
/// lookup tables cannot be frozen.
pub fn freeze_lookup_table(lookup_table_address: Pubkey, authority_address: Pubkey) -> Instruction {
Instruction::new_with_bincode(
crate::id(),
&ProgramInstruction::FreezeLookupTable,
vec![
AccountMeta::new(lookup_table_address, false),
AccountMeta::new_readonly(authority_address, true),
],
)
}

/// Constructs an instruction which extends an address lookup
/// table account with new addresses.
pub fn extend_lookup_table(
lookup_table_address: Pubkey,
authority_address: Pubkey,
payer_address: Option<Pubkey>,
new_addresses: Vec<Pubkey>,
) -> Instruction {
let mut accounts = vec![
AccountMeta::new(lookup_table_address, false),
AccountMeta::new_readonly(authority_address, true),
];

if let Some(payer_address) = payer_address {
accounts.extend([
AccountMeta::new(payer_address, true),
AccountMeta::new_readonly(system_program::id(), false),
]);
}

Instruction::new_with_bincode(
crate::id(),
&ProgramInstruction::ExtendLookupTable { new_addresses },
accounts,
)
}

/// Constructs an instruction that deactivates an address lookup
/// table so that it cannot be extended again and will be unusable
/// and eligible for closure after a short amount of time.
pub fn deactivate_lookup_table(
lookup_table_address: Pubkey,
authority_address: Pubkey,
) -> Instruction {
Instruction::new_with_bincode(
crate::id(),
&ProgramInstruction::DeactivateLookupTable,
vec![
AccountMeta::new(lookup_table_address, false),
AccountMeta::new_readonly(authority_address, true),
],
)
}

/// Returns an instruction that closes an address lookup table
/// account. The account will be deallocated and the lamports
/// will be drained to the recipient address.
pub fn close_lookup_table(
lookup_table_address: Pubkey,
authority_address: Pubkey,
recipient_address: Pubkey,
) -> Instruction {
Instruction::new_with_bincode(
crate::id(),
&ProgramInstruction::CloseLookupTable,
vec![
AccountMeta::new(lookup_table_address, false),
AccountMeta::new_readonly(authority_address, true),
AccountMeta::new(recipient_address, false),
],
)
}
25 changes: 25 additions & 0 deletions program/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//! Address Lookup Table Program
// [Core BPF]: Required for `solana-frozen-abi-macro` to work.
#![allow(incomplete_features)]
#![cfg_attr(RUSTC_WITH_SPECIALIZATION, feature(specialization))]

#[cfg(not(feature = "no-entrypoint"))]
mod entrypoint;
pub mod error;
pub mod instruction;
pub mod processor;
pub mod state;

// [Core BPF]: TODO: Program-test will not overwrite existing built-ins.
// See https://github.com/solana-labs/solana/pull/35233
// solana_program::declare_id!("AddressLookupTab1e1111111111111111111111111");
solana_program::declare_id!("AaoNx79M6YE3DcXfrRN4nmBcQvQPqdpowi6uEESuJdnm");

/// The definition of address lookup table accounts.
///
/// As used by the `crate::message::v0` message format.
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct AddressLookupTableAccount {
pub key: solana_program::pubkey::Pubkey,
pub addresses: Vec<solana_program::pubkey::Pubkey>,
}
Loading

0 comments on commit 227a8a0

Please sign in to comment.