Adds stable layout types to pass to the runtime (#30192)
This commit is contained in:
parent
fa22389b22
commit
0c36e4c82d
|
@ -4954,6 +4954,7 @@ dependencies = [
|
||||||
"byteorder",
|
"byteorder",
|
||||||
"libsecp256k1",
|
"libsecp256k1",
|
||||||
"log",
|
"log",
|
||||||
|
"memoffset 0.8.0",
|
||||||
"rand 0.7.3",
|
"rand 0.7.3",
|
||||||
"solana-measure",
|
"solana-measure",
|
||||||
"solana-program-runtime",
|
"solana-program-runtime",
|
||||||
|
|
|
@ -17,11 +17,12 @@ use {
|
||||||
bpf_loader_upgradeable::{self, UpgradeableLoaderState},
|
bpf_loader_upgradeable::{self, UpgradeableLoaderState},
|
||||||
feature_set::{enable_early_verification_of_account_modifications, FeatureSet},
|
feature_set::{enable_early_verification_of_account_modifications, FeatureSet},
|
||||||
hash::Hash,
|
hash::Hash,
|
||||||
instruction::{AccountMeta, Instruction, InstructionError},
|
instruction::{AccountMeta, InstructionError},
|
||||||
native_loader,
|
native_loader,
|
||||||
pubkey::Pubkey,
|
pubkey::Pubkey,
|
||||||
rent::Rent,
|
rent::Rent,
|
||||||
saturating_add_assign,
|
saturating_add_assign,
|
||||||
|
stable_layout::stable_instruction::StableInstruction,
|
||||||
transaction_context::{
|
transaction_context::{
|
||||||
IndexOfAccount, InstructionAccount, TransactionAccount, TransactionContext,
|
IndexOfAccount, InstructionAccount, TransactionAccount, TransactionContext,
|
||||||
},
|
},
|
||||||
|
@ -489,7 +490,7 @@ impl<'a> InvokeContext<'a> {
|
||||||
/// Entrypoint for a cross-program invocation from a builtin program
|
/// Entrypoint for a cross-program invocation from a builtin program
|
||||||
pub fn native_invoke(
|
pub fn native_invoke(
|
||||||
&mut self,
|
&mut self,
|
||||||
instruction: Instruction,
|
instruction: StableInstruction,
|
||||||
signers: &[Pubkey],
|
signers: &[Pubkey],
|
||||||
) -> Result<(), InstructionError> {
|
) -> Result<(), InstructionError> {
|
||||||
let (instruction_accounts, program_indices) =
|
let (instruction_accounts, program_indices) =
|
||||||
|
@ -509,7 +510,7 @@ impl<'a> InvokeContext<'a> {
|
||||||
#[allow(clippy::type_complexity)]
|
#[allow(clippy::type_complexity)]
|
||||||
pub fn prepare_instruction(
|
pub fn prepare_instruction(
|
||||||
&mut self,
|
&mut self,
|
||||||
instruction: &Instruction,
|
instruction: &StableInstruction,
|
||||||
signers: &[Pubkey],
|
signers: &[Pubkey],
|
||||||
) -> Result<(Vec<InstructionAccount>, Vec<IndexOfAccount>), InstructionError> {
|
) -> Result<(Vec<InstructionAccount>, Vec<IndexOfAccount>), InstructionError> {
|
||||||
// Finds the index of each account in the instruction by its pubkey.
|
// Finds the index of each account in the instruction by its pubkey.
|
||||||
|
@ -1036,7 +1037,7 @@ mod tests {
|
||||||
super::*,
|
super::*,
|
||||||
crate::compute_budget,
|
crate::compute_budget,
|
||||||
serde::{Deserialize, Serialize},
|
serde::{Deserialize, Serialize},
|
||||||
solana_sdk::account::WritableAccount,
|
solana_sdk::{account::WritableAccount, instruction::Instruction},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
@ -1161,7 +1162,7 @@ mod tests {
|
||||||
assert_eq!(result, Err(InstructionError::UnbalancedInstruction));
|
assert_eq!(result, Err(InstructionError::UnbalancedInstruction));
|
||||||
result?;
|
result?;
|
||||||
invoke_context
|
invoke_context
|
||||||
.native_invoke(inner_instruction, &[])
|
.native_invoke(inner_instruction.into(), &[])
|
||||||
.and(invoke_context.pop())?;
|
.and(invoke_context.pop())?;
|
||||||
}
|
}
|
||||||
MockInstruction::UnbalancedPop => instruction_context
|
MockInstruction::UnbalancedPop => instruction_context
|
||||||
|
@ -1336,7 +1337,7 @@ mod tests {
|
||||||
let inner_instruction =
|
let inner_instruction =
|
||||||
Instruction::new_with_bincode(callee_program_id, &case.0, metas.clone());
|
Instruction::new_with_bincode(callee_program_id, &case.0, metas.clone());
|
||||||
let result = invoke_context
|
let result = invoke_context
|
||||||
.native_invoke(inner_instruction, &[])
|
.native_invoke(inner_instruction.into(), &[])
|
||||||
.and(invoke_context.pop());
|
.and(invoke_context.pop());
|
||||||
assert_eq!(result, case.1);
|
assert_eq!(result, case.1);
|
||||||
}
|
}
|
||||||
|
@ -1359,6 +1360,7 @@ mod tests {
|
||||||
},
|
},
|
||||||
metas.clone(),
|
metas.clone(),
|
||||||
);
|
);
|
||||||
|
let inner_instruction = StableInstruction::from(inner_instruction);
|
||||||
let (inner_instruction_accounts, program_indices) = invoke_context
|
let (inner_instruction_accounts, program_indices) = invoke_context
|
||||||
.prepare_instruction(&inner_instruction, &[])
|
.prepare_instruction(&inner_instruction, &[])
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
|
@ -40,6 +40,7 @@ use {
|
||||||
pubkey::Pubkey,
|
pubkey::Pubkey,
|
||||||
rent::Rent,
|
rent::Rent,
|
||||||
signature::{Keypair, Signer},
|
signature::{Keypair, Signer},
|
||||||
|
stable_layout::stable_instruction::StableInstruction,
|
||||||
sysvar::{Sysvar, SysvarId},
|
sysvar::{Sysvar, SysvarId},
|
||||||
},
|
},
|
||||||
solana_vote_program::vote_state::{self, VoteState, VoteStateVersions},
|
solana_vote_program::vote_state::{self, VoteState, VoteStateVersions},
|
||||||
|
@ -227,6 +228,7 @@ impl solana_sdk::program_stubs::SyscallStubs for SyscallStubs {
|
||||||
account_infos: &[AccountInfo],
|
account_infos: &[AccountInfo],
|
||||||
signers_seeds: &[&[&[u8]]],
|
signers_seeds: &[&[&[u8]]],
|
||||||
) -> ProgramResult {
|
) -> ProgramResult {
|
||||||
|
let instruction = StableInstruction::from(instruction.clone());
|
||||||
let invoke_context = get_invoke_context();
|
let invoke_context = get_invoke_context();
|
||||||
let log_collector = invoke_context.get_log_collector();
|
let log_collector = invoke_context.get_log_collector();
|
||||||
let transaction_context = &invoke_context.transaction_context;
|
let transaction_context = &invoke_context.transaction_context;
|
||||||
|
@ -249,7 +251,7 @@ impl solana_sdk::program_stubs::SyscallStubs for SyscallStubs {
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let (instruction_accounts, program_indices) = invoke_context
|
let (instruction_accounts, program_indices) = invoke_context
|
||||||
.prepare_instruction(instruction, &signers)
|
.prepare_instruction(&instruction, &signers)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// Copy caller's account_info modifications into invoke_context accounts
|
// Copy caller's account_info modifications into invoke_context accounts
|
||||||
|
|
|
@ -144,18 +144,18 @@ impl Processor {
|
||||||
|
|
||||||
if required_lamports > 0 {
|
if required_lamports > 0 {
|
||||||
invoke_context.native_invoke(
|
invoke_context.native_invoke(
|
||||||
system_instruction::transfer(&payer_key, &table_key, required_lamports),
|
system_instruction::transfer(&payer_key, &table_key, required_lamports).into(),
|
||||||
&[payer_key],
|
&[payer_key],
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
invoke_context.native_invoke(
|
invoke_context.native_invoke(
|
||||||
system_instruction::allocate(&table_key, table_account_data_len as u64),
|
system_instruction::allocate(&table_key, table_account_data_len as u64).into(),
|
||||||
&[table_key],
|
&[table_key],
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
invoke_context.native_invoke(
|
invoke_context.native_invoke(
|
||||||
system_instruction::assign(&table_key, &crate::id()),
|
system_instruction::assign(&table_key, &crate::id()).into(),
|
||||||
&[table_key],
|
&[table_key],
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
@ -332,7 +332,7 @@ impl Processor {
|
||||||
drop(payer_account);
|
drop(payer_account);
|
||||||
|
|
||||||
invoke_context.native_invoke(
|
invoke_context.native_invoke(
|
||||||
system_instruction::transfer(&payer_key, &table_key, required_lamports),
|
system_instruction::transfer(&payer_key, &table_key, required_lamports).into(),
|
||||||
&[payer_key],
|
&[payer_key],
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,9 @@ solana-zk-token-sdk = { path = "../../zk-token-sdk", version = "=1.16.0" }
|
||||||
solana_rbpf = "=0.2.38"
|
solana_rbpf = "=0.2.38"
|
||||||
thiserror = "1.0"
|
thiserror = "1.0"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
memoffset = "0.8"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
crate-type = ["lib"]
|
crate-type = ["lib"]
|
||||||
name = "solana_bpf_loader_program"
|
name = "solana_bpf_loader_program"
|
||||||
|
|
|
@ -736,7 +736,7 @@ fn process_loader_upgradeable_instruction(
|
||||||
.iter()
|
.iter()
|
||||||
.map(|seeds| Pubkey::create_program_address(seeds, caller_program_id))
|
.map(|seeds| Pubkey::create_program_address(seeds, caller_program_id))
|
||||||
.collect::<Result<Vec<Pubkey>, solana_sdk::pubkey::PubkeyError>>()?;
|
.collect::<Result<Vec<Pubkey>, solana_sdk::pubkey::PubkeyError>>()?;
|
||||||
invoke_context.native_invoke(instruction, signers.as_slice())?;
|
invoke_context.native_invoke(instruction.into(), signers.as_slice())?;
|
||||||
|
|
||||||
// Load and verify the program bits
|
// Load and verify the program bits
|
||||||
let transaction_context = &invoke_context.transaction_context;
|
let transaction_context = &invoke_context.transaction_context;
|
||||||
|
@ -1360,7 +1360,8 @@ fn process_loader_upgradeable_instruction(
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
invoke_context.native_invoke(
|
invoke_context.native_invoke(
|
||||||
system_instruction::transfer(&payer_key, &programdata_key, required_payment),
|
system_instruction::transfer(&payer_key, &programdata_key, required_payment)
|
||||||
|
.into(),
|
||||||
&[],
|
&[],
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ use {
|
||||||
crate::declare_syscall,
|
crate::declare_syscall,
|
||||||
solana_sdk::{
|
solana_sdk::{
|
||||||
feature_set::enable_bpf_loader_set_authority_checked_ix,
|
feature_set::enable_bpf_loader_set_authority_checked_ix,
|
||||||
|
stable_layout::stable_instruction::StableInstruction,
|
||||||
syscalls::{
|
syscalls::{
|
||||||
MAX_CPI_ACCOUNT_INFOS, MAX_CPI_INSTRUCTION_ACCOUNTS, MAX_CPI_INSTRUCTION_DATA_LEN,
|
MAX_CPI_ACCOUNT_INFOS, MAX_CPI_INSTRUCTION_ACCOUNTS, MAX_CPI_INSTRUCTION_DATA_LEN,
|
||||||
},
|
},
|
||||||
|
@ -211,7 +212,7 @@ trait SyscallInvokeSigned {
|
||||||
addr: u64,
|
addr: u64,
|
||||||
memory_mapping: &mut MemoryMapping,
|
memory_mapping: &mut MemoryMapping,
|
||||||
invoke_context: &mut InvokeContext,
|
invoke_context: &mut InvokeContext,
|
||||||
) -> Result<Instruction, EbpfError>;
|
) -> Result<StableInstruction, EbpfError>;
|
||||||
fn translate_accounts<'a>(
|
fn translate_accounts<'a>(
|
||||||
instruction_accounts: &[InstructionAccount],
|
instruction_accounts: &[InstructionAccount],
|
||||||
program_indices: &[IndexOfAccount],
|
program_indices: &[IndexOfAccount],
|
||||||
|
@ -258,8 +259,8 @@ impl SyscallInvokeSigned for SyscallInvokeSignedRust {
|
||||||
addr: u64,
|
addr: u64,
|
||||||
memory_mapping: &mut MemoryMapping,
|
memory_mapping: &mut MemoryMapping,
|
||||||
invoke_context: &mut InvokeContext,
|
invoke_context: &mut InvokeContext,
|
||||||
) -> Result<Instruction, EbpfError> {
|
) -> Result<StableInstruction, EbpfError> {
|
||||||
let ix = translate_type::<Instruction>(
|
let ix = translate_type::<StableInstruction>(
|
||||||
memory_mapping,
|
memory_mapping,
|
||||||
addr,
|
addr,
|
||||||
invoke_context.get_check_aligned(),
|
invoke_context.get_check_aligned(),
|
||||||
|
@ -296,10 +297,11 @@ impl SyscallInvokeSigned for SyscallInvokeSignedRust {
|
||||||
invoke_context.get_check_size(),
|
invoke_context.get_check_size(),
|
||||||
)?
|
)?
|
||||||
.to_vec();
|
.to_vec();
|
||||||
Ok(Instruction {
|
|
||||||
|
Ok(StableInstruction {
|
||||||
|
accounts: accounts.into(),
|
||||||
|
data: data.into(),
|
||||||
program_id: ix.program_id,
|
program_id: ix.program_id,
|
||||||
accounts,
|
|
||||||
data,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -469,7 +471,7 @@ impl SyscallInvokeSigned for SyscallInvokeSignedC {
|
||||||
addr: u64,
|
addr: u64,
|
||||||
memory_mapping: &mut MemoryMapping,
|
memory_mapping: &mut MemoryMapping,
|
||||||
invoke_context: &mut InvokeContext,
|
invoke_context: &mut InvokeContext,
|
||||||
) -> Result<Instruction, EbpfError> {
|
) -> Result<StableInstruction, EbpfError> {
|
||||||
let ix_c = translate_type::<SolInstruction>(
|
let ix_c = translate_type::<SolInstruction>(
|
||||||
memory_mapping,
|
memory_mapping,
|
||||||
addr,
|
addr,
|
||||||
|
@ -530,10 +532,10 @@ impl SyscallInvokeSigned for SyscallInvokeSignedC {
|
||||||
})
|
})
|
||||||
.collect::<Result<Vec<AccountMeta>, EbpfError>>()?;
|
.collect::<Result<Vec<AccountMeta>, EbpfError>>()?;
|
||||||
|
|
||||||
Ok(Instruction {
|
Ok(StableInstruction {
|
||||||
|
accounts: accounts.into(),
|
||||||
|
data: data.into(),
|
||||||
program_id: *program_id,
|
program_id: *program_id,
|
||||||
accounts,
|
|
||||||
data,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1128,6 +1130,7 @@ mod tests {
|
||||||
solana_sdk::{
|
solana_sdk::{
|
||||||
account::{Account, AccountSharedData},
|
account::{Account, AccountSharedData},
|
||||||
clock::Epoch,
|
clock::Epoch,
|
||||||
|
instruction::Instruction,
|
||||||
rent::Rent,
|
rent::Rent,
|
||||||
transaction_context::{TransactionAccount, TransactionContext},
|
transaction_context::{TransactionAccount, TransactionContext},
|
||||||
},
|
},
|
||||||
|
@ -1693,12 +1696,12 @@ mod tests {
|
||||||
fn into_region(self, vm_addr: u64) -> (Vec<u8>, MemoryRegion) {
|
fn into_region(self, vm_addr: u64) -> (Vec<u8>, MemoryRegion) {
|
||||||
let accounts_len = mem::size_of::<AccountMeta>() * self.accounts.len();
|
let accounts_len = mem::size_of::<AccountMeta>() * self.accounts.len();
|
||||||
|
|
||||||
let size = mem::size_of::<Instruction>() + accounts_len + self.data.len();
|
let size = mem::size_of::<StableInstruction>() + accounts_len + self.data.len();
|
||||||
|
|
||||||
let mut data = vec![0; size];
|
let mut data = vec![0; size];
|
||||||
|
|
||||||
let vm_addr = vm_addr as usize;
|
let vm_addr = vm_addr as usize;
|
||||||
let accounts_addr = vm_addr + mem::size_of::<Instruction>();
|
let accounts_addr = vm_addr + mem::size_of::<StableInstruction>();
|
||||||
let data_addr = accounts_addr + accounts_len;
|
let data_addr = accounts_addr + accounts_len;
|
||||||
|
|
||||||
let ins = Instruction {
|
let ins = Instruction {
|
||||||
|
@ -1714,6 +1717,7 @@ mod tests {
|
||||||
Vec::from_raw_parts(data_addr as *mut _, self.data.len(), self.data.len())
|
Vec::from_raw_parts(data_addr as *mut _, self.data.len(), self.data.len())
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
let ins = StableInstruction::from(ins);
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
ptr::write_unaligned(data.as_mut_ptr().cast(), ins);
|
ptr::write_unaligned(data.as_mut_ptr().cast(), ins);
|
||||||
|
|
|
@ -44,7 +44,7 @@ use {
|
||||||
},
|
},
|
||||||
hash::{Hasher, HASH_BYTES},
|
hash::{Hasher, HASH_BYTES},
|
||||||
instruction::{
|
instruction::{
|
||||||
AccountMeta, Instruction, InstructionError, ProcessedSiblingInstruction,
|
AccountMeta, InstructionError, ProcessedSiblingInstruction,
|
||||||
TRANSACTION_LEVEL_STACK_HEIGHT,
|
TRANSACTION_LEVEL_STACK_HEIGHT,
|
||||||
},
|
},
|
||||||
keccak, native_loader,
|
keccak, native_loader,
|
||||||
|
@ -1821,7 +1821,9 @@ mod tests {
|
||||||
bpf_loader,
|
bpf_loader,
|
||||||
fee_calculator::FeeCalculator,
|
fee_calculator::FeeCalculator,
|
||||||
hash::hashv,
|
hash::hashv,
|
||||||
|
instruction::Instruction,
|
||||||
program::check_type_assumptions,
|
program::check_type_assumptions,
|
||||||
|
stable_layout::stable_instruction::StableInstruction,
|
||||||
sysvar::{clock::Clock, epoch_schedule::EpochSchedule, rent::Rent},
|
sysvar::{clock::Clock, epoch_schedule::EpochSchedule, rent::Rent},
|
||||||
transaction_context::TransactionContext,
|
transaction_context::TransactionContext,
|
||||||
},
|
},
|
||||||
|
@ -1936,17 +1938,18 @@ mod tests {
|
||||||
&"foobar",
|
&"foobar",
|
||||||
vec![AccountMeta::new(solana_sdk::pubkey::new_rand(), false)],
|
vec![AccountMeta::new(solana_sdk::pubkey::new_rand(), false)],
|
||||||
);
|
);
|
||||||
|
let instruction = StableInstruction::from(instruction);
|
||||||
let addr = &instruction as *const _ as u64;
|
let addr = &instruction as *const _ as u64;
|
||||||
let mut memory_region = MemoryRegion {
|
let mut memory_region = MemoryRegion {
|
||||||
host_addr: addr,
|
host_addr: addr,
|
||||||
vm_addr: 0x100000000,
|
vm_addr: 0x100000000,
|
||||||
len: std::mem::size_of::<Instruction>() as u64,
|
len: std::mem::size_of::<StableInstruction>() as u64,
|
||||||
vm_gap_shift: 63,
|
vm_gap_shift: 63,
|
||||||
is_writable: false,
|
is_writable: false,
|
||||||
};
|
};
|
||||||
let mut memory_mapping = MemoryMapping::new(vec![memory_region.clone()], &config).unwrap();
|
let mut memory_mapping = MemoryMapping::new(vec![memory_region.clone()], &config).unwrap();
|
||||||
let translated_instruction =
|
let translated_instruction =
|
||||||
translate_type::<Instruction>(&memory_mapping, 0x100000000, true).unwrap();
|
translate_type::<StableInstruction>(&memory_mapping, 0x100000000, true).unwrap();
|
||||||
assert_eq!(instruction, *translated_instruction);
|
assert_eq!(instruction, *translated_instruction);
|
||||||
memory_region.len = 1;
|
memory_region.len = 1;
|
||||||
let memory_region_index = memory_mapping
|
let memory_region_index = memory_mapping
|
||||||
|
@ -1957,7 +1960,7 @@ mod tests {
|
||||||
memory_mapping
|
memory_mapping
|
||||||
.replace_region(memory_region_index, memory_region)
|
.replace_region(memory_region_index, memory_region)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(translate_type::<Instruction>(&memory_mapping, 0x100000000, true).is_err());
|
assert!(translate_type::<StableInstruction>(&memory_mapping, 0x100000000, true).is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
@ -521,6 +521,7 @@ pub mod serialize_utils;
|
||||||
pub mod short_vec;
|
pub mod short_vec;
|
||||||
pub mod slot_hashes;
|
pub mod slot_hashes;
|
||||||
pub mod slot_history;
|
pub mod slot_history;
|
||||||
|
pub mod stable_layout;
|
||||||
pub mod stake;
|
pub mod stake;
|
||||||
pub mod stake_history;
|
pub mod stake_history;
|
||||||
pub mod syscalls;
|
pub mod syscalls;
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
account_info::AccountInfo, entrypoint::ProgramResult, instruction::Instruction, pubkey::Pubkey,
|
account_info::AccountInfo, entrypoint::ProgramResult, instruction::Instruction, pubkey::Pubkey,
|
||||||
|
stable_layout::stable_instruction::StableInstruction,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Invoke a cross-program instruction.
|
/// Invoke a cross-program instruction.
|
||||||
|
@ -292,9 +293,10 @@ pub fn invoke_signed_unchecked(
|
||||||
) -> ProgramResult {
|
) -> ProgramResult {
|
||||||
#[cfg(target_os = "solana")]
|
#[cfg(target_os = "solana")]
|
||||||
{
|
{
|
||||||
|
let instruction = StableInstruction::from(instruction.clone());
|
||||||
let result = unsafe {
|
let result = unsafe {
|
||||||
crate::syscalls::sol_invoke_signed_rust(
|
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 as *const _ as *const u8,
|
||||||
account_infos.len() as u64,
|
account_infos.len() as u64,
|
||||||
signers_seeds as *const _ as *const u8,
|
signers_seeds as *const _ as *const u8,
|
||||||
|
@ -432,17 +434,18 @@ pub fn check_type_assumptions() {
|
||||||
accounts: vec![account_meta1.clone(), account_meta2.clone()],
|
accounts: vec![account_meta1.clone(), account_meta2.clone()],
|
||||||
data: data.clone(),
|
data: data.clone(),
|
||||||
};
|
};
|
||||||
|
let instruction = StableInstruction::from(instruction);
|
||||||
let instruction_addr = &instruction as *const _ as u64;
|
let instruction_addr = &instruction as *const _ as u64;
|
||||||
|
|
||||||
// program id
|
// 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;
|
let pubkey_ptr = (instruction_addr + 48) as *const Pubkey;
|
||||||
unsafe {
|
unsafe {
|
||||||
assert_eq!(*pubkey_ptr, pubkey1);
|
assert_eq!(*pubkey_ptr, pubkey1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// accounts
|
// 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_ptr = (instruction_addr) as *const *const AccountMeta;
|
||||||
let accounts_cap = (instruction_addr + 8) as *const usize;
|
let accounts_cap = (instruction_addr + 8) as *const usize;
|
||||||
let accounts_len = (instruction_addr + 16) as *const usize;
|
let accounts_len = (instruction_addr + 16) as *const usize;
|
||||||
|
@ -455,7 +458,7 @@ pub fn check_type_assumptions() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// data
|
// 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_ptr = (instruction_addr + 24) as *const *const [u8; 5];
|
||||||
let data_cap = (instruction_addr + 24 + 8) as *const usize;
|
let data_cap = (instruction_addr + 24 + 8) as *const usize;
|
||||||
let data_len = (instruction_addr + 24 + 16) as *const usize;
|
let data_len = (instruction_addr + 24 + 16) as *const usize;
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
#![doc(hidden)]
|
||||||
|
//! Types with stable memory layouts
|
||||||
|
//!
|
||||||
|
//! Internal use only; here be dragons!
|
||||||
|
|
||||||
|
pub mod stable_instruction;
|
||||||
|
pub mod stable_rc;
|
||||||
|
pub mod stable_ref_cell;
|
||||||
|
pub mod stable_slice;
|
||||||
|
pub mod stable_vec;
|
|
@ -0,0 +1,96 @@
|
||||||
|
//! `Instruction`, with a stable memory layout
|
||||||
|
|
||||||
|
use {
|
||||||
|
crate::{
|
||||||
|
instruction::{AccountMeta, Instruction},
|
||||||
|
pubkey::Pubkey,
|
||||||
|
stable_layout::stable_vec::StableVec,
|
||||||
|
},
|
||||||
|
std::fmt::Debug,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// `Instruction`, with a stable memory layout
|
||||||
|
///
|
||||||
|
/// This is used within the runtime to ensure memory mapping and memory accesses are valid. We
|
||||||
|
/// rely on known addresses and offsets within the runtime, and since `Instruction`'s layout is
|
||||||
|
/// allowed to change, we must provide a way to lock down the memory layout. `StableInstruction`
|
||||||
|
/// reimplements the bare minimum of `Instruction`'s API sufficient only for the runtime's needs.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// Creating a `StableInstruction` from an `Instruction`
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use solana_program::{instruction::Instruction, pubkey::Pubkey, stable_layout::stable_instruction::StableInstruction};
|
||||||
|
/// # let program_id = Pubkey::default();
|
||||||
|
/// # let accounts = Vec::default();
|
||||||
|
/// # let data = Vec::default();
|
||||||
|
/// let instruction = Instruction { program_id, accounts, data };
|
||||||
|
/// let instruction = StableInstruction::from(instruction);
|
||||||
|
/// ```
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
#[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: other.accounts.into(),
|
||||||
|
data: other.data.into(),
|
||||||
|
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 StableVec<AccountMeta>;
|
||||||
|
assert_eq!(unsafe { &*accounts_ptr }, &accounts);
|
||||||
|
|
||||||
|
let data_ptr = (instruction_addr + 24) as *const StableVec<u8>;
|
||||||
|
assert_eq!(unsafe { &*data_ptr }, &data);
|
||||||
|
|
||||||
|
let pubkey_ptr = (instruction_addr + 48) as *const Pubkey;
|
||||||
|
assert_eq!(unsafe { *pubkey_ptr }, program_id);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
//! Ensure Rc has a stable memory layout
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::{
|
||||||
|
mem::{align_of, size_of},
|
||||||
|
rc::Rc,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_memory_layout() {
|
||||||
|
assert_eq!(align_of::<Rc<i32>>(), 8);
|
||||||
|
assert_eq!(size_of::<Rc<i32>>(), 8);
|
||||||
|
|
||||||
|
let value = 42;
|
||||||
|
let rc = Rc::new(value);
|
||||||
|
let _rc2 = Rc::clone(&rc); // used to increment strong count
|
||||||
|
|
||||||
|
let addr_rc = &rc as *const _ as usize;
|
||||||
|
let addr_ptr = addr_rc;
|
||||||
|
let addr_rcbox = unsafe { *(addr_ptr as *const *const i32) } as usize;
|
||||||
|
let addr_strong = addr_rcbox;
|
||||||
|
let addr_weak = addr_rcbox + 8;
|
||||||
|
let addr_value = addr_rcbox + 16;
|
||||||
|
assert_eq!(unsafe { *(addr_strong as *const usize) }, 2);
|
||||||
|
assert_eq!(unsafe { *(addr_weak as *const usize) }, 1);
|
||||||
|
assert_eq!(unsafe { *(addr_value as *const i32) }, 42);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
//! Ensure RefCell has a stable layout
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::{
|
||||||
|
cell::RefCell,
|
||||||
|
mem::{align_of, size_of},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_memory_layout() {
|
||||||
|
assert_eq!(align_of::<RefCell<i32>>(), 8);
|
||||||
|
assert_eq!(size_of::<RefCell<i32>>(), 8 + 4 + /* padding */4);
|
||||||
|
|
||||||
|
let value = 42;
|
||||||
|
let refcell = RefCell::new(value);
|
||||||
|
let _borrow = refcell.borrow(); // used to increment borrow count
|
||||||
|
|
||||||
|
let addr_refcell = &refcell as *const _ as usize;
|
||||||
|
let addr_borrow = addr_refcell;
|
||||||
|
let addr_value = addr_refcell + 8;
|
||||||
|
assert_eq!(unsafe { *(addr_borrow as *const isize) }, 1);
|
||||||
|
assert_eq!(unsafe { *(addr_value as *const i32) }, 42);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
//! Ensure slice has a stable memory layout
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::mem::{align_of, size_of};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_memory_layout() {
|
||||||
|
assert_eq!(align_of::<&[i32]>(), 8);
|
||||||
|
assert_eq!(size_of::<&[i32]>(), /*ptr*/ 8 + /*len*/8);
|
||||||
|
|
||||||
|
let array = [11, 22, 33, 44, 55];
|
||||||
|
let slice = array.as_slice();
|
||||||
|
|
||||||
|
let addr_slice = &slice as *const _ as usize;
|
||||||
|
let addr_ptr = addr_slice;
|
||||||
|
let addr_len = addr_slice + 8;
|
||||||
|
assert_eq!(unsafe { *(addr_len as *const usize) }, 5);
|
||||||
|
|
||||||
|
let ptr_data = addr_ptr as *const *const i32;
|
||||||
|
assert_eq!(unsafe { *((*ptr_data).offset(0)) }, 11);
|
||||||
|
assert_eq!(unsafe { *((*ptr_data).offset(1)) }, 22);
|
||||||
|
assert_eq!(unsafe { *((*ptr_data).offset(2)) }, 33);
|
||||||
|
assert_eq!(unsafe { *((*ptr_data).offset(3)) }, 44);
|
||||||
|
assert_eq!(unsafe { *((*ptr_data).offset(4)) }, 55);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,181 @@
|
||||||
|
//! `Vec`, with a stable memory layout
|
||||||
|
|
||||||
|
use std::{marker::PhantomData, mem::ManuallyDrop, ptr::NonNull};
|
||||||
|
|
||||||
|
/// `Vec`, with a stable memory layout
|
||||||
|
///
|
||||||
|
/// This container is used within the runtime to ensure memory mapping and memory accesses are
|
||||||
|
/// valid. We rely on known addresses and offsets within the runtime, and since `Vec`'s layout
|
||||||
|
/// is allowed to change, we must provide a way to lock down the memory layout. `StableVec`
|
||||||
|
/// reimplements the bare minimum of `Vec`'s API sufficient only for the runtime's needs.
|
||||||
|
///
|
||||||
|
/// To ensure memory allocation and deallocation is handled correctly, it is only possible to
|
||||||
|
/// create a new `StableVec` from an existing `Vec`. This way we ensure all Rust invariants are
|
||||||
|
/// upheld.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// Creating a `StableVec` from a `Vec`
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use solana_program::stable_layout::stable_vec::StableVec;
|
||||||
|
/// let vec = vec!["meow", "woof", "moo"];
|
||||||
|
/// let vec = StableVec::from(vec);
|
||||||
|
/// ```
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct StableVec<T> {
|
||||||
|
pub ptr: NonNull<T>,
|
||||||
|
pub cap: usize,
|
||||||
|
pub len: usize,
|
||||||
|
_marker: PhantomData<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> StableVec<T> {
|
||||||
|
#[inline]
|
||||||
|
pub fn as_ptr(&self) -> *const T {
|
||||||
|
// We shadow the slice method of the same name to avoid going through
|
||||||
|
// `deref`, which creates an intermediate reference.
|
||||||
|
self.ptr.as_ptr()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn as_mut_ptr(&mut self) -> *mut T {
|
||||||
|
// We shadow the slice method of the same name to avoid going through
|
||||||
|
// `deref_mut`, which creates an intermediate reference.
|
||||||
|
self.ptr.as_ptr()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> AsRef<[T]> for StableVec<T> {
|
||||||
|
fn as_ref(&self) -> &[T] {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> AsMut<[T]> for StableVec<T> {
|
||||||
|
fn as_mut(&mut self) -> &mut [T] {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> std::ops::Deref for StableVec<T> {
|
||||||
|
type Target = [T];
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn deref(&self) -> &[T] {
|
||||||
|
unsafe { core::slice::from_raw_parts(self.as_ptr(), self.len) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> std::ops::DerefMut for StableVec<T> {
|
||||||
|
#[inline]
|
||||||
|
fn deref_mut(&mut self) -> &mut [T] {
|
||||||
|
unsafe { core::slice::from_raw_parts_mut(self.as_mut_ptr(), self.len) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: std::fmt::Debug> std::fmt::Debug for StableVec<T> {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
std::fmt::Debug::fmt(&**self, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! impl_partial_eq {
|
||||||
|
([$($vars:tt)*] $lhs:ty, $rhs:ty) => {
|
||||||
|
impl<T, U, $($vars)*> PartialEq<$rhs> for $lhs
|
||||||
|
where
|
||||||
|
T: PartialEq<U>,
|
||||||
|
{
|
||||||
|
#[inline]
|
||||||
|
fn eq(&self, other: &$rhs) -> bool { self[..] == other[..] }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl_partial_eq! { [] StableVec<T>, StableVec<U> }
|
||||||
|
impl_partial_eq! { [] StableVec<T>, Vec<U> }
|
||||||
|
impl_partial_eq! { [] Vec<T>, StableVec<U> }
|
||||||
|
impl_partial_eq! { [] StableVec<T>, &[U] }
|
||||||
|
impl_partial_eq! { [] StableVec<T>, &mut [U] }
|
||||||
|
impl_partial_eq! { [] &[T], StableVec<U> }
|
||||||
|
impl_partial_eq! { [] &mut [T], StableVec<U> }
|
||||||
|
impl_partial_eq! { [] StableVec<T>, [U] }
|
||||||
|
impl_partial_eq! { [] [T], StableVec<U> }
|
||||||
|
impl_partial_eq! { [const N: usize] StableVec<T>, [U; N] }
|
||||||
|
impl_partial_eq! { [const N: usize] StableVec<T>, &[U; N] }
|
||||||
|
|
||||||
|
impl<T> From<Vec<T>> for StableVec<T> {
|
||||||
|
fn from(other: Vec<T>) -> Self {
|
||||||
|
// NOTE: This impl is basically copied from `Vec::into_raw_parts()`. Once that fn is
|
||||||
|
// stabilized, use it here.
|
||||||
|
//
|
||||||
|
// We are going to pilfer `other`'s guts, and we don't want it to be dropped when it goes
|
||||||
|
// out of scope.
|
||||||
|
let mut other = ManuallyDrop::new(other);
|
||||||
|
Self {
|
||||||
|
// SAFETY: We have a valid Vec, so its ptr is non-null.
|
||||||
|
ptr: unsafe { NonNull::new_unchecked(other.as_mut_ptr()) },
|
||||||
|
cap: other.capacity(),
|
||||||
|
len: other.len(),
|
||||||
|
_marker: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> From<StableVec<T>> for Vec<T> {
|
||||||
|
fn from(other: StableVec<T>) -> Self {
|
||||||
|
// We are going to pilfer `other`'s guts, and we don't want it to be dropped when it goes
|
||||||
|
// out of scope.
|
||||||
|
let other = ManuallyDrop::new(other);
|
||||||
|
// SAFETY: We have a valid StableVec, which we can only get from a Vec. Therefore it is
|
||||||
|
// safe to convert back to Vec.
|
||||||
|
unsafe { Vec::from_raw_parts(other.ptr.as_ptr(), other.len, other.cap) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Drop for StableVec<T> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
// We only allow creating a StableVec through creating a Vec. To ensure we are dropped
|
||||||
|
// correctly, convert ourselves back to a Vec and let Vec's drop handling take over.
|
||||||
|
//
|
||||||
|
// SAFETY: We have a valid StableVec, which we can only get from a Vec. Therefore it is
|
||||||
|
// safe to convert back to Vec.
|
||||||
|
let _vec = unsafe { Vec::from_raw_parts(self.ptr.as_ptr(), self.len, self.cap) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use {
|
||||||
|
super::*,
|
||||||
|
memoffset::offset_of,
|
||||||
|
std::mem::{align_of, size_of},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_memory_layout() {
|
||||||
|
assert_eq!(offset_of!(StableVec<i32>, ptr), 0);
|
||||||
|
assert_eq!(offset_of!(StableVec<i32>, cap), 8);
|
||||||
|
assert_eq!(offset_of!(StableVec<i32>, len), 16);
|
||||||
|
assert_eq!(align_of::<StableVec<i32>>(), 8);
|
||||||
|
assert_eq!(size_of::<StableVec<i32>>(), 8 + 8 + 8);
|
||||||
|
|
||||||
|
// create a vec with different values for cap and len
|
||||||
|
let vec = {
|
||||||
|
let mut vec = Vec::with_capacity(3);
|
||||||
|
vec.push(11);
|
||||||
|
vec.push(22);
|
||||||
|
vec
|
||||||
|
};
|
||||||
|
let vec = StableVec::from(vec);
|
||||||
|
|
||||||
|
let addr_vec = &vec as *const _ as usize;
|
||||||
|
let addr_ptr = addr_vec;
|
||||||
|
let addr_cap = addr_vec + 8;
|
||||||
|
let addr_len = addr_vec + 16;
|
||||||
|
assert_eq!(unsafe { *(addr_cap as *const usize) }, 3);
|
||||||
|
assert_eq!(unsafe { *(addr_len as *const usize) }, 2);
|
||||||
|
|
||||||
|
let ptr_data = addr_ptr as *const &[i32; 2];
|
||||||
|
assert_eq!(unsafe { *ptr_data }, &[11, 22]);
|
||||||
|
}
|
||||||
|
}
|
|
@ -50,8 +50,8 @@ pub use solana_program::{
|
||||||
instruction, keccak, lamports, loader_instruction, loader_upgradeable_instruction, message,
|
instruction, keccak, lamports, loader_instruction, loader_upgradeable_instruction, message,
|
||||||
msg, native_token, nonce, program, program_error, program_memory, program_option, program_pack,
|
msg, native_token, nonce, program, program_error, program_memory, program_option, program_pack,
|
||||||
rent, sanitize, sdk_ids, secp256k1_program, secp256k1_recover, serde_varint, serialize_utils,
|
rent, sanitize, sdk_ids, secp256k1_program, secp256k1_recover, serde_varint, serialize_utils,
|
||||||
short_vec, slot_hashes, slot_history, stake, stake_history, syscalls, system_instruction,
|
short_vec, slot_hashes, slot_history, stable_layout, stake, stake_history, syscalls,
|
||||||
system_program, sysvar, unchecked_div_by_const, vote, wasm_bindgen,
|
system_instruction, system_program, sysvar, unchecked_div_by_const, vote, wasm_bindgen,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub mod account;
|
pub mod account;
|
||||||
|
|
Loading…
Reference in New Issue