Skip to content

Commit

Permalink
wip: Add structs with stable layouts for types that are passed to run…
Browse files Browse the repository at this point in the history
…time
  • Loading branch information
brooksprumo committed Feb 8, 2023
1 parent 10dde65 commit edd379b
Show file tree
Hide file tree
Showing 16 changed files with 396 additions and 23 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion program-runtime/src/invoke_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ use {
pubkey::Pubkey,
rent::Rent,
saturating_add_assign,
stable_layout::stable_instruction::StableInstruction,
transaction_context::{
IndexOfAccount, InstructionAccount, TransactionAccount, TransactionContext,
},
Expand Down Expand Up @@ -482,6 +483,7 @@ impl<'a> InvokeContext<'a> {
instruction: Instruction,
signers: &[Pubkey],
) -> Result<(), InstructionError> {
let instruction = StableInstruction::from(instruction);
let (instruction_accounts, program_indices) =
self.prepare_instruction(&instruction, signers)?;
let mut compute_units_consumed = 0;
Expand All @@ -499,7 +501,7 @@ impl<'a> InvokeContext<'a> {
#[allow(clippy::type_complexity)]
pub fn prepare_instruction(
&mut self,
instruction: &Instruction,
instruction: &StableInstruction,
signers: &[Pubkey],
) -> Result<(Vec<InstructionAccount>, Vec<IndexOfAccount>), InstructionError> {
// Finds the index of each account in the instruction by its pubkey.
Expand Down Expand Up @@ -1334,6 +1336,7 @@ mod tests {
},
metas.clone(),
);
let inner_instruction = StableInstruction::from(inner_instruction);
let (inner_instruction_accounts, program_indices) = invoke_context
.prepare_instruction(&inner_instruction, &[])
.unwrap();
Expand Down
5 changes: 3 additions & 2 deletions program-test/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,14 @@ use {
fee_calculator::{FeeCalculator, FeeRateGovernor, DEFAULT_TARGET_LAMPORTS_PER_SIGNATURE},
genesis_config::{ClusterType, GenesisConfig},
hash::Hash,
instruction::{Instruction, InstructionError},
instruction::InstructionError,
native_token::sol_to_lamports,
poh_config::PohConfig,
program_error::{ProgramError, UNSUPPORTED_SYSVAR},
pubkey::Pubkey,
rent::Rent,
signature::{Keypair, Signer},
stable_layout::stable_instruction::StableInstruction,
sysvar::{Sysvar, SysvarId},
},
solana_vote_program::vote_state::{self, VoteState, VoteStateVersions},
Expand Down Expand Up @@ -223,7 +224,7 @@ impl solana_sdk::program_stubs::SyscallStubs for SyscallStubs {

fn sol_invoke_signed(
&self,
instruction: &Instruction,
instruction: &StableInstruction,
account_infos: &[AccountInfo],
signers_seeds: &[&[&[u8]]],
) -> ProgramResult {
Expand Down
3 changes: 3 additions & 0 deletions programs/bpf_loader/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ solana-zk-token-sdk = { path = "../../zk-token-sdk", version = "=1.16.0" }
solana_rbpf = "=0.2.38"
thiserror = "1.0"

[dev-dependencies]
memoffset = "0.8"

[lib]
crate-type = ["lib"]
name = "solana_bpf_loader_program"
Expand Down
28 changes: 18 additions & 10 deletions programs/bpf_loader/src/syscalls/cpi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use {
crate::declare_syscall,
solana_sdk::{
feature_set::enable_bpf_loader_set_authority_checked_ix,
stable_layout::{stable_instruction::StableInstruction, stable_vec::StableVec},
syscalls::{
MAX_CPI_ACCOUNT_INFOS, MAX_CPI_INSTRUCTION_ACCOUNTS, MAX_CPI_INSTRUCTION_DATA_LEN,
},
Expand Down Expand Up @@ -211,7 +212,7 @@ trait SyscallInvokeSigned {
addr: u64,
memory_mapping: &mut MemoryMapping,
invoke_context: &mut InvokeContext,
) -> Result<Instruction, EbpfError>;
) -> Result<StableInstruction, EbpfError>;
fn translate_accounts<'a>(
instruction_accounts: &[InstructionAccount],
program_indices: &[IndexOfAccount],
Expand Down Expand Up @@ -258,8 +259,8 @@ impl SyscallInvokeSigned for SyscallInvokeSignedRust {
addr: u64,
memory_mapping: &mut MemoryMapping,
invoke_context: &mut InvokeContext,
) -> Result<Instruction, EbpfError> {
let ix = translate_type::<Instruction>(
) -> Result<StableInstruction, EbpfError> {
let ix = translate_type::<StableInstruction>(
memory_mapping,
addr,
invoke_context.get_check_aligned(),
Expand All @@ -275,6 +276,7 @@ impl SyscallInvokeSigned for SyscallInvokeSignedRust {
invoke_context.get_check_size(),
)?
.to_vec();
let accounts = StableVec::from(accounts);

let ix_data_len = ix.data.len() as u64;
if invoke_context
Expand All @@ -296,10 +298,12 @@ impl SyscallInvokeSigned for SyscallInvokeSignedRust {
invoke_context.get_check_size(),
)?
.to_vec();
Ok(Instruction {
program_id: ix.program_id,
let data = StableVec::from(data);

Ok(StableInstruction {
accounts,
data,
program_id: ix.program_id,
})
}

Expand Down Expand Up @@ -469,7 +473,7 @@ impl SyscallInvokeSigned for SyscallInvokeSignedC {
addr: u64,
memory_mapping: &mut MemoryMapping,
invoke_context: &mut InvokeContext,
) -> Result<Instruction, EbpfError> {
) -> Result<StableInstruction, EbpfError> {
let ix_c = translate_type::<SolInstruction>(
memory_mapping,
addr,
Expand Down Expand Up @@ -514,6 +518,8 @@ impl SyscallInvokeSigned for SyscallInvokeSignedC {
invoke_context.get_check_size(),
)?
.to_vec();
let data = StableVec::from(data);

let accounts = meta_cs
.iter()
.map(|meta_c| {
Expand All @@ -529,11 +535,12 @@ impl SyscallInvokeSigned for SyscallInvokeSignedC {
})
})
.collect::<Result<Vec<AccountMeta>, EbpfError>>()?;
let accounts = StableVec::from(accounts);

Ok(Instruction {
program_id: *program_id,
Ok(StableInstruction {
accounts,
data,
program_id: *program_id,
})
}

Expand Down Expand Up @@ -1128,6 +1135,7 @@ mod tests {
solana_sdk::{
account::{Account, AccountSharedData},
clock::Epoch,
instruction::Instruction,
rent::Rent,
transaction_context::{TransactionAccount, TransactionContext},
},
Expand Down Expand Up @@ -1220,8 +1228,8 @@ mod tests {
)
.unwrap();
assert_eq!(ins.program_id, program_id);
assert_eq!(ins.accounts, accounts);
assert_eq!(ins.data, data);
assert_eq!(ins.accounts, StableVec::from(accounts));
assert_eq!(ins.data, StableVec::from(data));
}

#[test]
Expand Down
3 changes: 2 additions & 1 deletion programs/bpf_loader/src/syscalls/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ use {
},
hash::{Hasher, HASH_BYTES},
instruction::{
AccountMeta, Instruction, InstructionError, ProcessedSiblingInstruction,
AccountMeta, InstructionError, ProcessedSiblingInstruction,
TRANSACTION_LEVEL_STACK_HEIGHT,
},
keccak, native_loader,
Expand Down Expand Up @@ -1821,6 +1821,7 @@ mod tests {
bpf_loader,
fee_calculator::FeeCalculator,
hash::hashv,
instruction::Instruction,
program::check_type_assumptions,
sysvar::{clock::Clock, epoch_schedule::EpochSchedule, rent::Rent},
transaction_context::TransactionContext,
Expand Down
1 change: 1 addition & 0 deletions sdk/program/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -521,6 +521,7 @@ pub mod serialize_utils;
pub mod short_vec;
pub mod slot_hashes;
pub mod slot_history;
pub mod stable_layout;
pub mod stake;
pub mod stake_history;
pub mod syscalls;
Expand Down
15 changes: 10 additions & 5 deletions sdk/program/src/program.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use crate::{
account_info::AccountInfo, entrypoint::ProgramResult, instruction::Instruction, pubkey::Pubkey,
stable_layout::stable_instruction::StableInstruction,
};

/// Invoke a cross-program instruction.
Expand Down Expand Up @@ -290,11 +291,14 @@ pub fn invoke_signed_unchecked(
account_infos: &[AccountInfo],
signers_seeds: &[&[&[u8]]],
) -> ProgramResult {
// bprumo TODO: it would be nice to not clone `instruction`... Can this fn take a StableInstruction instead?
let instruction = StableInstruction::from(instruction.clone());

#[cfg(target_os = "solana")]
{
let result = unsafe {
crate::syscalls::sol_invoke_signed_rust(
instruction as *const _ as *const u8,
&instruction as *const _ as *const u8,
account_infos as *const _ as *const u8,
account_infos.len() as u64,
signers_seeds as *const _ as *const u8,
Expand All @@ -308,7 +312,7 @@ pub fn invoke_signed_unchecked(
}

#[cfg(not(target_os = "solana"))]
crate::program_stubs::sol_invoke_signed(instruction, account_infos, signers_seeds)
crate::program_stubs::sol_invoke_signed(&instruction, account_infos, signers_seeds)
}

/// Maximum size that can be set using [`set_return_data`].
Expand Down Expand Up @@ -432,17 +436,18 @@ pub fn check_type_assumptions() {
accounts: vec![account_meta1.clone(), account_meta2.clone()],
data: data.clone(),
};
let instruction = StableInstruction::from(instruction);
let instruction_addr = &instruction as *const _ as u64;

// program id
assert_eq!(offset_of!(Instruction, program_id), 48);
assert_eq!(offset_of!(StableInstruction, program_id), 48);
let pubkey_ptr = (instruction_addr + 48) as *const Pubkey;
unsafe {
assert_eq!(*pubkey_ptr, pubkey1);
}

// accounts
assert_eq!(offset_of!(Instruction, accounts), 0);
assert_eq!(offset_of!(StableInstruction, accounts), 0);
let accounts_ptr = (instruction_addr) as *const *const AccountMeta;
let accounts_cap = (instruction_addr + 8) as *const usize;
let accounts_len = (instruction_addr + 16) as *const usize;
Expand All @@ -455,7 +460,7 @@ pub fn check_type_assumptions() {
}

// data
assert_eq!(offset_of!(Instruction, data), 24);
assert_eq!(offset_of!(StableInstruction, data), 24);
let data_ptr = (instruction_addr + 24) as *const *const [u8; 5];
let data_cap = (instruction_addr + 24 + 8) as *const usize;
let data_len = (instruction_addr + 24 + 16) as *const usize;
Expand Down
5 changes: 3 additions & 2 deletions sdk/program/src/program_stubs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use {
crate::{
account_info::AccountInfo, entrypoint::ProgramResult, instruction::Instruction,
program_error::UNSUPPORTED_SYSVAR, pubkey::Pubkey,
stable_layout::stable_instruction::StableInstruction,
},
itertools::Itertools,
std::sync::{Arc, RwLock},
Expand All @@ -31,7 +32,7 @@ pub trait SyscallStubs: Sync + Send {
}
fn sol_invoke_signed(
&self,
_instruction: &Instruction,
_instruction: &StableInstruction,
_account_infos: &[AccountInfo],
_signers_seeds: &[&[&[u8]]],
) -> ProgramResult {
Expand Down Expand Up @@ -117,7 +118,7 @@ pub(crate) fn sol_log_compute_units() {
}

pub(crate) fn sol_invoke_signed(
instruction: &Instruction,
instruction: &StableInstruction,
account_infos: &[AccountInfo],
signers_seeds: &[&[&[u8]]],
) -> ProgramResult {
Expand Down
7 changes: 7 additions & 0 deletions sdk/program/src/stable_layout.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
//! bprumo TODO: doc, or hide doc?
pub mod stable_instruction;
pub mod stable_rc;
pub mod stable_ref_cell;
pub mod stable_slice;
pub mod stable_vec;
77 changes: 77 additions & 0 deletions sdk/program/src/stable_layout/stable_instruction.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
//! Instruction, with a stable memory layout
use {
crate::{
instruction::{AccountMeta, Instruction},
pubkey::Pubkey,
stable_layout::stable_vec::StableVec,
},
std::fmt::Debug,
};

#[derive(Debug)]
#[repr(C)]
pub struct StableInstruction {
pub accounts: StableVec<AccountMeta>,
pub data: StableVec<u8>,
pub program_id: Pubkey,
}

impl From<Instruction> for StableInstruction {
fn from(other: Instruction) -> Self {
Self {
accounts: StableVec::from(other.accounts),
data: StableVec::from(other.data),
program_id: other.program_id,
}
}
}

#[cfg(test)]
mod tests {
use {
super::*,
memoffset::offset_of,
std::mem::{align_of, size_of},
};

#[test]
fn test_memory_layout() {
assert_eq!(offset_of!(StableInstruction, accounts), 0);
assert_eq!(offset_of!(StableInstruction, data), 24);
assert_eq!(offset_of!(StableInstruction, program_id), 48);
assert_eq!(align_of::<StableInstruction>(), 8);
assert_eq!(size_of::<StableInstruction>(), 24 + 24 + 32);

let program_id = Pubkey::new_unique();
let account_meta1 = AccountMeta {
pubkey: Pubkey::new_unique(),
is_signer: true,
is_writable: false,
};
let account_meta2 = AccountMeta {
pubkey: Pubkey::new_unique(),
is_signer: false,
is_writable: true,
};
let accounts = vec![account_meta1, account_meta2];
let data = vec![1, 2, 3, 4, 5];
let instruction = Instruction {
program_id,
accounts: accounts.clone(),
data: data.clone(),
};
let instruction = StableInstruction::from(instruction);

let instruction_addr = &instruction as *const _ as u64;

let accounts_ptr = instruction_addr as *const &[AccountMeta; 2];
assert_eq!(unsafe { *accounts_ptr }, accounts.as_slice());

let data_ptr = (instruction_addr + 24) as *const &[u8; 5];
assert_eq!(unsafe { *data_ptr }, data.as_slice());

let pubkey_ptr = (instruction_addr + 48) as *const Pubkey;
assert_eq!(unsafe { *pubkey_ptr }, program_id);
}
}
Loading

0 comments on commit edd379b

Please sign in to comment.