Feature: Early verification of account modifications in `BorrowedAccount` (#25899)
* Adjusts test cases for stricter requirements. * Removes account reset in deserialization test. * Removes verify related test cases. * Replicates account modification verification logic of PreAccount in BorrowedAccount. * Adds TransactionContext::account_touched_flags. * Adds account modification verification to the BPF ABIv0 and ABIv1 deserialization, CPI syscall and program-test. * Replicates the total sum of all lamports verification of PreAccounts in InstructionContext * Check that the callers instruction balance is maintained during a call / push. * Replicates PreAccount statistics in TransactionContext. * Disable verify() and verify_and_update() if the feature enable_early_verification_of_account_modifications is enabled. * Moves Option<Rent> of enable_early_verification_of_account_modifications into TransactionContext::new(). * Relaxes AccountDataMeter related test cases. * Don't touch the account if nothing changes. * Adds two tests to trigger InstructionError::UnbalancedInstruction. Co-authored-by: Justin Starry <justin@solana.com>
This commit is contained in:
parent
f13b5c832d
commit
038da82b6f
|
@ -46,6 +46,7 @@ use {
|
||||||
signature::{keypair_from_seed, read_keypair_file, Keypair, Signature, Signer},
|
signature::{keypair_from_seed, read_keypair_file, Keypair, Signature, Signer},
|
||||||
system_instruction::{self, SystemError},
|
system_instruction::{self, SystemError},
|
||||||
system_program,
|
system_program,
|
||||||
|
sysvar::rent::Rent,
|
||||||
transaction::{Transaction, TransactionError},
|
transaction::{Transaction, TransactionError},
|
||||||
transaction_context::TransactionContext,
|
transaction_context::TransactionContext,
|
||||||
},
|
},
|
||||||
|
@ -2060,7 +2061,7 @@ fn read_and_verify_elf(program_location: &str) -> Result<Vec<u8>, Box<dyn std::e
|
||||||
let mut program_data = Vec::new();
|
let mut program_data = Vec::new();
|
||||||
file.read_to_end(&mut program_data)
|
file.read_to_end(&mut program_data)
|
||||||
.map_err(|err| format!("Unable to read program file: {}", err))?;
|
.map_err(|err| format!("Unable to read program file: {}", err))?;
|
||||||
let mut transaction_context = TransactionContext::new(Vec::new(), 1, 1);
|
let mut transaction_context = TransactionContext::new(Vec::new(), Some(Rent::default()), 1, 1);
|
||||||
let mut invoke_context = InvokeContext::new_mock(&mut transaction_context, &[]);
|
let mut invoke_context = InvokeContext::new_mock(&mut transaction_context, &[]);
|
||||||
|
|
||||||
// Verify the program
|
// Verify the program
|
||||||
|
|
|
@ -4516,7 +4516,10 @@ pub mod tests {
|
||||||
mint_keypair,
|
mint_keypair,
|
||||||
..
|
..
|
||||||
} = create_genesis_config((1_000_000 + NUM_ACCOUNTS + 1) * LAMPORTS_PER_SOL);
|
} = create_genesis_config((1_000_000 + NUM_ACCOUNTS + 1) * LAMPORTS_PER_SOL);
|
||||||
let bank = Bank::new_for_tests(&genesis_config);
|
let mut bank = Bank::new_for_tests(&genesis_config);
|
||||||
|
bank.deactivate_feature(
|
||||||
|
&feature_set::enable_early_verification_of_account_modifications::id(),
|
||||||
|
);
|
||||||
let bank = Arc::new(bank);
|
let bank = Arc::new(bank);
|
||||||
assert!(bank
|
assert!(bank
|
||||||
.feature_set
|
.feature_set
|
||||||
|
@ -4579,6 +4582,9 @@ pub mod tests {
|
||||||
mock_realloc::process_instruction,
|
mock_realloc::process_instruction,
|
||||||
);
|
);
|
||||||
bank.set_accounts_data_size_initial_for_tests(INITIAL_ACCOUNTS_DATA_SIZE);
|
bank.set_accounts_data_size_initial_for_tests(INITIAL_ACCOUNTS_DATA_SIZE);
|
||||||
|
bank.deactivate_feature(
|
||||||
|
&feature_set::enable_early_verification_of_account_modifications::id(),
|
||||||
|
);
|
||||||
let bank = Arc::new(bank);
|
let bank = Arc::new(bank);
|
||||||
let bank = Arc::new(Bank::new_from_parent(&bank, &Pubkey::new_unique(), 1));
|
let bank = Arc::new(Bank::new_from_parent(&bank, &Pubkey::new_unique(), 1));
|
||||||
assert!(bank
|
assert!(bank
|
||||||
|
|
|
@ -16,7 +16,8 @@ use {
|
||||||
account::{AccountSharedData, ReadableAccount},
|
account::{AccountSharedData, ReadableAccount},
|
||||||
bpf_loader_upgradeable::{self, UpgradeableLoaderState},
|
bpf_loader_upgradeable::{self, UpgradeableLoaderState},
|
||||||
feature_set::{
|
feature_set::{
|
||||||
cap_accounts_data_len, record_instruction_in_transaction_context_push, FeatureSet,
|
cap_accounts_data_len, enable_early_verification_of_account_modifications,
|
||||||
|
record_instruction_in_transaction_context_push, FeatureSet,
|
||||||
},
|
},
|
||||||
hash::Hash,
|
hash::Hash,
|
||||||
instruction::{AccountMeta, Instruction, InstructionError},
|
instruction::{AccountMeta, Instruction, InstructionError},
|
||||||
|
@ -344,29 +345,34 @@ impl<'a> InvokeContext<'a> {
|
||||||
{
|
{
|
||||||
self.current_compute_budget = self.compute_budget;
|
self.current_compute_budget = self.compute_budget;
|
||||||
|
|
||||||
self.pre_accounts = Vec::with_capacity(instruction_accounts.len());
|
if !self
|
||||||
|
.feature_set
|
||||||
for (instruction_account_index, instruction_account) in
|
.is_active(&enable_early_verification_of_account_modifications::id())
|
||||||
instruction_accounts.iter().enumerate()
|
|
||||||
{
|
{
|
||||||
if instruction_account_index != instruction_account.index_in_callee {
|
self.pre_accounts = Vec::with_capacity(instruction_accounts.len());
|
||||||
continue; // Skip duplicate account
|
for (instruction_account_index, instruction_account) in
|
||||||
}
|
instruction_accounts.iter().enumerate()
|
||||||
if instruction_account.index_in_transaction
|
|
||||||
>= self.transaction_context.get_number_of_accounts()
|
|
||||||
{
|
{
|
||||||
return Err(InstructionError::MissingAccount);
|
if instruction_account_index != instruction_account.index_in_callee {
|
||||||
|
continue; // Skip duplicate account
|
||||||
|
}
|
||||||
|
if instruction_account.index_in_transaction
|
||||||
|
>= self.transaction_context.get_number_of_accounts()
|
||||||
|
{
|
||||||
|
return Err(InstructionError::MissingAccount);
|
||||||
|
}
|
||||||
|
let account = self
|
||||||
|
.transaction_context
|
||||||
|
.get_account_at_index(instruction_account.index_in_transaction)?
|
||||||
|
.borrow()
|
||||||
|
.clone();
|
||||||
|
self.pre_accounts.push(PreAccount::new(
|
||||||
|
self.transaction_context.get_key_of_account_at_index(
|
||||||
|
instruction_account.index_in_transaction,
|
||||||
|
)?,
|
||||||
|
account,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
let account = self
|
|
||||||
.transaction_context
|
|
||||||
.get_account_at_index(instruction_account.index_in_transaction)?
|
|
||||||
.borrow()
|
|
||||||
.clone();
|
|
||||||
self.pre_accounts.push(PreAccount::new(
|
|
||||||
self.transaction_context
|
|
||||||
.get_key_of_account_at_index(instruction_account.index_in_transaction)?,
|
|
||||||
account,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let contains = (0..self
|
let contains = (0..self
|
||||||
|
@ -831,26 +837,35 @@ impl<'a> InvokeContext<'a> {
|
||||||
.get_instruction_context_stack_height();
|
.get_instruction_context_stack_height();
|
||||||
let is_top_level_instruction = nesting_level == 0;
|
let is_top_level_instruction = nesting_level == 0;
|
||||||
if !is_top_level_instruction {
|
if !is_top_level_instruction {
|
||||||
// Verify the calling program hasn't misbehaved
|
if !self
|
||||||
let mut verify_caller_time = Measure::start("verify_caller_time");
|
.feature_set
|
||||||
let verify_caller_result = self.verify_and_update(instruction_accounts, true);
|
.is_active(&enable_early_verification_of_account_modifications::id())
|
||||||
verify_caller_time.stop();
|
{
|
||||||
saturating_add_assign!(
|
// Verify the calling program hasn't misbehaved
|
||||||
timings
|
let mut verify_caller_time = Measure::start("verify_caller_time");
|
||||||
.execute_accessories
|
let verify_caller_result = self.verify_and_update(instruction_accounts, true);
|
||||||
.process_instructions
|
verify_caller_time.stop();
|
||||||
.verify_caller_us,
|
saturating_add_assign!(
|
||||||
verify_caller_time.as_us()
|
timings
|
||||||
);
|
.execute_accessories
|
||||||
verify_caller_result?;
|
.process_instructions
|
||||||
|
.verify_caller_us,
|
||||||
|
verify_caller_time.as_us()
|
||||||
|
);
|
||||||
|
verify_caller_result?;
|
||||||
|
}
|
||||||
|
|
||||||
if !self
|
if !self
|
||||||
.feature_set
|
.feature_set
|
||||||
.is_active(&record_instruction_in_transaction_context_push::id())
|
.is_active(&record_instruction_in_transaction_context_push::id())
|
||||||
{
|
{
|
||||||
|
let instruction_accounts_lamport_sum = self
|
||||||
|
.transaction_context
|
||||||
|
.instruction_accounts_lamport_sum(instruction_accounts)?;
|
||||||
self.transaction_context
|
self.transaction_context
|
||||||
.record_instruction(InstructionContext::new(
|
.record_instruction(InstructionContext::new(
|
||||||
nesting_level,
|
nesting_level,
|
||||||
|
instruction_accounts_lamport_sum,
|
||||||
program_indices,
|
program_indices,
|
||||||
instruction_accounts,
|
instruction_accounts,
|
||||||
instruction_data,
|
instruction_data,
|
||||||
|
@ -861,22 +876,29 @@ impl<'a> InvokeContext<'a> {
|
||||||
self.push(instruction_accounts, program_indices, instruction_data)?;
|
self.push(instruction_accounts, program_indices, instruction_data)?;
|
||||||
self.process_executable_chain(compute_units_consumed, timings)
|
self.process_executable_chain(compute_units_consumed, timings)
|
||||||
.and_then(|_| {
|
.and_then(|_| {
|
||||||
// Verify the called program has not misbehaved
|
if self
|
||||||
let mut verify_callee_time = Measure::start("verify_callee_time");
|
.feature_set
|
||||||
let result = if is_top_level_instruction {
|
.is_active(&enable_early_verification_of_account_modifications::id())
|
||||||
self.verify(instruction_accounts, program_indices)
|
{
|
||||||
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
self.verify_and_update(instruction_accounts, false)
|
// Verify the called program has not misbehaved
|
||||||
};
|
let mut verify_callee_time = Measure::start("verify_callee_time");
|
||||||
verify_callee_time.stop();
|
let result = if is_top_level_instruction {
|
||||||
saturating_add_assign!(
|
self.verify(instruction_accounts, program_indices)
|
||||||
timings
|
} else {
|
||||||
.execute_accessories
|
self.verify_and_update(instruction_accounts, false)
|
||||||
.process_instructions
|
};
|
||||||
.verify_callee_us,
|
verify_callee_time.stop();
|
||||||
verify_callee_time.as_us()
|
saturating_add_assign!(
|
||||||
);
|
timings
|
||||||
result
|
.execute_accessories
|
||||||
|
.process_instructions
|
||||||
|
.verify_callee_us,
|
||||||
|
verify_callee_time.as_us()
|
||||||
|
);
|
||||||
|
result
|
||||||
|
}
|
||||||
})
|
})
|
||||||
// MUST pop if and only if `push` succeeded, independent of `result`.
|
// MUST pop if and only if `push` succeeded, independent of `result`.
|
||||||
// Thus, the `.and()` instead of an `.and_then()`.
|
// Thus, the `.and()` instead of an `.and_then()`.
|
||||||
|
@ -1138,6 +1160,7 @@ pub fn with_mock_invoke_context<R, F: FnMut(&mut InvokeContext) -> R>(
|
||||||
prepare_mock_invoke_context(transaction_accounts, instruction_accounts, &program_indices);
|
prepare_mock_invoke_context(transaction_accounts, instruction_accounts, &program_indices);
|
||||||
let mut transaction_context = TransactionContext::new(
|
let mut transaction_context = TransactionContext::new(
|
||||||
preparation.transaction_accounts,
|
preparation.transaction_accounts,
|
||||||
|
Some(Rent::default()),
|
||||||
ComputeBudget::default().max_invoke_depth.saturating_add(1),
|
ComputeBudget::default().max_invoke_depth.saturating_add(1),
|
||||||
1,
|
1,
|
||||||
);
|
);
|
||||||
|
@ -1168,6 +1191,7 @@ pub fn mock_process_instruction(
|
||||||
.push((*loader_id, processor_account));
|
.push((*loader_id, processor_account));
|
||||||
let mut transaction_context = TransactionContext::new(
|
let mut transaction_context = TransactionContext::new(
|
||||||
preparation.transaction_accounts,
|
preparation.transaction_accounts,
|
||||||
|
Some(Rent::default()),
|
||||||
ComputeBudget::default().max_invoke_depth.saturating_add(1),
|
ComputeBudget::default().max_invoke_depth.saturating_add(1),
|
||||||
1,
|
1,
|
||||||
);
|
);
|
||||||
|
@ -1184,10 +1208,9 @@ pub fn mock_process_instruction(
|
||||||
&program_indices,
|
&program_indices,
|
||||||
instruction_data,
|
instruction_data,
|
||||||
)
|
)
|
||||||
.and_then(|_| process_instruction(1, &mut invoke_context))
|
.and_then(|_| process_instruction(1, &mut invoke_context));
|
||||||
.and_then(|_| invoke_context.verify(&preparation.instruction_accounts, &program_indices));
|
let pop_result = invoke_context.pop();
|
||||||
invoke_context.pop().unwrap();
|
assert_eq!(result.and(pop_result), expected_result);
|
||||||
assert_eq!(result, expected_result);
|
|
||||||
let mut transaction_accounts = transaction_context.deconstruct_without_keys().unwrap();
|
let mut transaction_accounts = transaction_context.deconstruct_without_keys().unwrap();
|
||||||
transaction_accounts.pop();
|
transaction_accounts.pop();
|
||||||
transaction_accounts
|
transaction_accounts
|
||||||
|
@ -1199,7 +1222,7 @@ mod tests {
|
||||||
super::*,
|
super::*,
|
||||||
crate::compute_budget,
|
crate::compute_budget,
|
||||||
serde::{Deserialize, Serialize},
|
serde::{Deserialize, Serialize},
|
||||||
solana_sdk::account::{ReadableAccount, WritableAccount},
|
solana_sdk::account::WritableAccount,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
@ -1209,6 +1232,8 @@ mod tests {
|
||||||
ModifyOwned,
|
ModifyOwned,
|
||||||
ModifyNotOwned,
|
ModifyNotOwned,
|
||||||
ModifyReadonly,
|
ModifyReadonly,
|
||||||
|
UnbalancedPush,
|
||||||
|
UnbalancedPop,
|
||||||
ConsumeComputeUnits {
|
ConsumeComputeUnits {
|
||||||
compute_units_to_consume: u64,
|
compute_units_to_consume: u64,
|
||||||
desired_result: Result<(), InstructionError>,
|
desired_result: Result<(), InstructionError>,
|
||||||
|
@ -1256,6 +1281,15 @@ mod tests {
|
||||||
let instruction_context = transaction_context.get_current_instruction_context()?;
|
let instruction_context = transaction_context.get_current_instruction_context()?;
|
||||||
let instruction_data = instruction_context.get_instruction_data();
|
let instruction_data = instruction_context.get_instruction_data();
|
||||||
let program_id = instruction_context.get_last_program_key(transaction_context)?;
|
let program_id = instruction_context.get_last_program_key(transaction_context)?;
|
||||||
|
let instruction_accounts = (0..4)
|
||||||
|
.map(|instruction_account_index| InstructionAccount {
|
||||||
|
index_in_transaction: instruction_account_index,
|
||||||
|
index_in_caller: instruction_account_index,
|
||||||
|
index_in_callee: instruction_account_index,
|
||||||
|
is_signer: false,
|
||||||
|
is_writable: false,
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
program_id,
|
program_id,
|
||||||
instruction_context
|
instruction_context
|
||||||
|
@ -1277,16 +1311,43 @@ mod tests {
|
||||||
MockInstruction::NoopFail => return Err(InstructionError::GenericError),
|
MockInstruction::NoopFail => return Err(InstructionError::GenericError),
|
||||||
MockInstruction::ModifyOwned => instruction_context
|
MockInstruction::ModifyOwned => instruction_context
|
||||||
.try_borrow_instruction_account(transaction_context, 0)?
|
.try_borrow_instruction_account(transaction_context, 0)?
|
||||||
.set_data(&[1])
|
.set_data(&[1])?,
|
||||||
.unwrap(),
|
|
||||||
MockInstruction::ModifyNotOwned => instruction_context
|
MockInstruction::ModifyNotOwned => instruction_context
|
||||||
.try_borrow_instruction_account(transaction_context, 1)?
|
.try_borrow_instruction_account(transaction_context, 1)?
|
||||||
.set_data(&[1])
|
.set_data(&[1])?,
|
||||||
.unwrap(),
|
|
||||||
MockInstruction::ModifyReadonly => instruction_context
|
MockInstruction::ModifyReadonly => instruction_context
|
||||||
.try_borrow_instruction_account(transaction_context, 2)?
|
.try_borrow_instruction_account(transaction_context, 2)?
|
||||||
.set_data(&[1])
|
.set_data(&[1])?,
|
||||||
.unwrap(),
|
MockInstruction::UnbalancedPush => {
|
||||||
|
instruction_context
|
||||||
|
.try_borrow_instruction_account(transaction_context, 0)?
|
||||||
|
.checked_add_lamports(1)?;
|
||||||
|
let program_id = *transaction_context.get_key_of_account_at_index(3)?;
|
||||||
|
let metas = vec![
|
||||||
|
AccountMeta::new_readonly(
|
||||||
|
*transaction_context.get_key_of_account_at_index(0)?,
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
AccountMeta::new_readonly(
|
||||||
|
*transaction_context.get_key_of_account_at_index(1)?,
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
let inner_instruction = Instruction::new_with_bincode(
|
||||||
|
program_id,
|
||||||
|
&MockInstruction::NoopSuccess,
|
||||||
|
metas,
|
||||||
|
);
|
||||||
|
let result = invoke_context.push(&instruction_accounts, &[3], &[]);
|
||||||
|
assert_eq!(result, Err(InstructionError::UnbalancedInstruction));
|
||||||
|
result?;
|
||||||
|
invoke_context
|
||||||
|
.native_invoke(inner_instruction, &[])
|
||||||
|
.and(invoke_context.pop())?;
|
||||||
|
}
|
||||||
|
MockInstruction::UnbalancedPop => instruction_context
|
||||||
|
.try_borrow_instruction_account(transaction_context, 0)?
|
||||||
|
.checked_add_lamports(1)?,
|
||||||
MockInstruction::ConsumeComputeUnits {
|
MockInstruction::ConsumeComputeUnits {
|
||||||
compute_units_to_consume,
|
compute_units_to_consume,
|
||||||
desired_result,
|
desired_result,
|
||||||
|
@ -1294,14 +1355,12 @@ mod tests {
|
||||||
invoke_context
|
invoke_context
|
||||||
.get_compute_meter()
|
.get_compute_meter()
|
||||||
.borrow_mut()
|
.borrow_mut()
|
||||||
.consume(compute_units_to_consume)
|
.consume(compute_units_to_consume)?;
|
||||||
.unwrap();
|
|
||||||
return desired_result;
|
return desired_result;
|
||||||
}
|
}
|
||||||
MockInstruction::Resize { new_len } => instruction_context
|
MockInstruction::Resize { new_len } => instruction_context
|
||||||
.try_borrow_instruction_account(transaction_context, 0)?
|
.try_borrow_instruction_account(transaction_context, 0)?
|
||||||
.set_data(&vec![0; new_len as usize])
|
.set_data(&vec![0; new_len as usize])?,
|
||||||
.unwrap(),
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return Err(InstructionError::InvalidInstructionData);
|
return Err(InstructionError::InvalidInstructionData);
|
||||||
|
@ -1310,7 +1369,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_invoke_context() {
|
fn test_instruction_stack_height() {
|
||||||
const MAX_DEPTH: usize = 10;
|
const MAX_DEPTH: usize = 10;
|
||||||
let mut invoke_stack = vec![];
|
let mut invoke_stack = vec![];
|
||||||
let mut accounts = vec![];
|
let mut accounts = vec![];
|
||||||
|
@ -1342,8 +1401,12 @@ mod tests {
|
||||||
is_writable: false,
|
is_writable: false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
let mut transaction_context =
|
let mut transaction_context = TransactionContext::new(
|
||||||
TransactionContext::new(accounts, ComputeBudget::default().max_invoke_depth, 1);
|
accounts,
|
||||||
|
Some(Rent::default()),
|
||||||
|
ComputeBudget::default().max_invoke_depth,
|
||||||
|
1,
|
||||||
|
);
|
||||||
let mut invoke_context = InvokeContext::new_mock(&mut transaction_context, &[]);
|
let mut invoke_context = InvokeContext::new_mock(&mut transaction_context, &[]);
|
||||||
|
|
||||||
// Check call depth increases and has a limit
|
// Check call depth increases and has a limit
|
||||||
|
@ -1358,107 +1421,6 @@ mod tests {
|
||||||
}
|
}
|
||||||
assert_ne!(depth_reached, 0);
|
assert_ne!(depth_reached, 0);
|
||||||
assert!(depth_reached < MAX_DEPTH);
|
assert!(depth_reached < MAX_DEPTH);
|
||||||
|
|
||||||
// Mock each invocation
|
|
||||||
for owned_index in (1..depth_reached).rev() {
|
|
||||||
let not_owned_index = owned_index - 1;
|
|
||||||
let instruction_accounts = vec![
|
|
||||||
InstructionAccount {
|
|
||||||
index_in_transaction: not_owned_index,
|
|
||||||
index_in_caller: not_owned_index,
|
|
||||||
index_in_callee: 0,
|
|
||||||
is_signer: false,
|
|
||||||
is_writable: true,
|
|
||||||
},
|
|
||||||
InstructionAccount {
|
|
||||||
index_in_transaction: owned_index,
|
|
||||||
index_in_caller: owned_index,
|
|
||||||
index_in_callee: 1,
|
|
||||||
is_signer: false,
|
|
||||||
is_writable: true,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
// modify account owned by the program
|
|
||||||
*invoke_context
|
|
||||||
.transaction_context
|
|
||||||
.get_account_at_index(owned_index)
|
|
||||||
.unwrap()
|
|
||||||
.borrow_mut()
|
|
||||||
.data_as_mut_slice()
|
|
||||||
.get_mut(0)
|
|
||||||
.unwrap() = (MAX_DEPTH + owned_index) as u8;
|
|
||||||
invoke_context
|
|
||||||
.verify_and_update(&instruction_accounts, false)
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(
|
|
||||||
*invoke_context
|
|
||||||
.pre_accounts
|
|
||||||
.get(owned_index)
|
|
||||||
.unwrap()
|
|
||||||
.data()
|
|
||||||
.first()
|
|
||||||
.unwrap(),
|
|
||||||
(MAX_DEPTH + owned_index) as u8
|
|
||||||
);
|
|
||||||
|
|
||||||
// modify account not owned by the program
|
|
||||||
let data = *invoke_context
|
|
||||||
.transaction_context
|
|
||||||
.get_account_at_index(not_owned_index)
|
|
||||||
.unwrap()
|
|
||||||
.borrow_mut()
|
|
||||||
.data()
|
|
||||||
.first()
|
|
||||||
.unwrap();
|
|
||||||
*invoke_context
|
|
||||||
.transaction_context
|
|
||||||
.get_account_at_index(not_owned_index)
|
|
||||||
.unwrap()
|
|
||||||
.borrow_mut()
|
|
||||||
.data_as_mut_slice()
|
|
||||||
.get_mut(0)
|
|
||||||
.unwrap() = (MAX_DEPTH + not_owned_index) as u8;
|
|
||||||
assert_eq!(
|
|
||||||
invoke_context.verify_and_update(&instruction_accounts, false),
|
|
||||||
Err(InstructionError::ExternalAccountDataModified)
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
*invoke_context
|
|
||||||
.pre_accounts
|
|
||||||
.get(not_owned_index)
|
|
||||||
.unwrap()
|
|
||||||
.data()
|
|
||||||
.first()
|
|
||||||
.unwrap(),
|
|
||||||
data
|
|
||||||
);
|
|
||||||
*invoke_context
|
|
||||||
.transaction_context
|
|
||||||
.get_account_at_index(not_owned_index)
|
|
||||||
.unwrap()
|
|
||||||
.borrow_mut()
|
|
||||||
.data_as_mut_slice()
|
|
||||||
.get_mut(0)
|
|
||||||
.unwrap() = data;
|
|
||||||
|
|
||||||
invoke_context.pop().unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_invoke_context_verify() {
|
|
||||||
let accounts = vec![(solana_sdk::pubkey::new_rand(), AccountSharedData::default())];
|
|
||||||
let instruction_accounts = vec![];
|
|
||||||
let program_indices = vec![0];
|
|
||||||
let mut transaction_context = TransactionContext::new(accounts, 1, 1);
|
|
||||||
let mut invoke_context = InvokeContext::new_mock(&mut transaction_context, &[]);
|
|
||||||
invoke_context
|
|
||||||
.push(&instruction_accounts, &program_indices, &[])
|
|
||||||
.unwrap();
|
|
||||||
assert!(invoke_context
|
|
||||||
.verify(&instruction_accounts, &program_indices)
|
|
||||||
.is_ok());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -1496,69 +1458,12 @@ mod tests {
|
||||||
is_writable: instruction_account_index < 2,
|
is_writable: instruction_account_index < 2,
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
let mut transaction_context = TransactionContext::new(accounts, 2, 8);
|
let mut transaction_context =
|
||||||
|
TransactionContext::new(accounts, Some(Rent::default()), 2, 9);
|
||||||
let mut invoke_context =
|
let mut invoke_context =
|
||||||
InvokeContext::new_mock(&mut transaction_context, builtin_programs);
|
InvokeContext::new_mock(&mut transaction_context, builtin_programs);
|
||||||
|
|
||||||
// External modification tests
|
// Account modification tests
|
||||||
{
|
|
||||||
invoke_context
|
|
||||||
.push(&instruction_accounts, &[4], &[])
|
|
||||||
.unwrap();
|
|
||||||
let inner_instruction = Instruction::new_with_bincode(
|
|
||||||
callee_program_id,
|
|
||||||
&MockInstruction::NoopSuccess,
|
|
||||||
metas.clone(),
|
|
||||||
);
|
|
||||||
|
|
||||||
// not owned account
|
|
||||||
*invoke_context
|
|
||||||
.transaction_context
|
|
||||||
.get_account_at_index(1)
|
|
||||||
.unwrap()
|
|
||||||
.borrow_mut()
|
|
||||||
.data_as_mut_slice()
|
|
||||||
.get_mut(0)
|
|
||||||
.unwrap() = 1;
|
|
||||||
assert_eq!(
|
|
||||||
invoke_context.native_invoke(inner_instruction.clone(), &[]),
|
|
||||||
Err(InstructionError::ExternalAccountDataModified)
|
|
||||||
);
|
|
||||||
*invoke_context
|
|
||||||
.transaction_context
|
|
||||||
.get_account_at_index(1)
|
|
||||||
.unwrap()
|
|
||||||
.borrow_mut()
|
|
||||||
.data_as_mut_slice()
|
|
||||||
.get_mut(0)
|
|
||||||
.unwrap() = 0;
|
|
||||||
|
|
||||||
// readonly account
|
|
||||||
*invoke_context
|
|
||||||
.transaction_context
|
|
||||||
.get_account_at_index(2)
|
|
||||||
.unwrap()
|
|
||||||
.borrow_mut()
|
|
||||||
.data_as_mut_slice()
|
|
||||||
.get_mut(0)
|
|
||||||
.unwrap() = 1;
|
|
||||||
assert_eq!(
|
|
||||||
invoke_context.native_invoke(inner_instruction, &[]),
|
|
||||||
Err(InstructionError::ReadonlyDataModified)
|
|
||||||
);
|
|
||||||
*invoke_context
|
|
||||||
.transaction_context
|
|
||||||
.get_account_at_index(2)
|
|
||||||
.unwrap()
|
|
||||||
.borrow_mut()
|
|
||||||
.data_as_mut_slice()
|
|
||||||
.get_mut(0)
|
|
||||||
.unwrap() = 0;
|
|
||||||
|
|
||||||
invoke_context.pop().unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Internal modification tests
|
|
||||||
let cases = vec![
|
let cases = vec![
|
||||||
(MockInstruction::NoopSuccess, Ok(())),
|
(MockInstruction::NoopSuccess, Ok(())),
|
||||||
(
|
(
|
||||||
|
@ -1574,6 +1479,14 @@ mod tests {
|
||||||
MockInstruction::ModifyReadonly,
|
MockInstruction::ModifyReadonly,
|
||||||
Err(InstructionError::ReadonlyDataModified),
|
Err(InstructionError::ReadonlyDataModified),
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
MockInstruction::UnbalancedPush,
|
||||||
|
Err(InstructionError::UnbalancedInstruction),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
MockInstruction::UnbalancedPop,
|
||||||
|
Err(InstructionError::UnbalancedInstruction),
|
||||||
|
),
|
||||||
];
|
];
|
||||||
for case in cases {
|
for case in cases {
|
||||||
invoke_context
|
invoke_context
|
||||||
|
@ -1581,8 +1494,10 @@ mod tests {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
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());
|
||||||
assert_eq!(invoke_context.native_invoke(inner_instruction, &[]), case.1);
|
let result = invoke_context
|
||||||
invoke_context.pop().unwrap();
|
.native_invoke(inner_instruction, &[])
|
||||||
|
.and(invoke_context.pop());
|
||||||
|
assert_eq!(result, case.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compute unit consumption tests
|
// Compute unit consumption tests
|
||||||
|
@ -1628,7 +1543,8 @@ mod tests {
|
||||||
fn test_invoke_context_compute_budget() {
|
fn test_invoke_context_compute_budget() {
|
||||||
let accounts = vec![(solana_sdk::pubkey::new_rand(), AccountSharedData::default())];
|
let accounts = vec![(solana_sdk::pubkey::new_rand(), AccountSharedData::default())];
|
||||||
|
|
||||||
let mut transaction_context = TransactionContext::new(accounts, 1, 3);
|
let mut transaction_context =
|
||||||
|
TransactionContext::new(accounts, Some(Rent::default()), 1, 3);
|
||||||
let mut invoke_context = InvokeContext::new_mock(&mut transaction_context, &[]);
|
let mut invoke_context = InvokeContext::new_mock(&mut transaction_context, &[]);
|
||||||
invoke_context.compute_budget =
|
invoke_context.compute_budget =
|
||||||
ComputeBudget::new(compute_budget::DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT as u64);
|
ComputeBudget::new(compute_budget::DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT as u64);
|
||||||
|
@ -1663,7 +1579,8 @@ mod tests {
|
||||||
process_instruction: mock_process_instruction,
|
process_instruction: mock_process_instruction,
|
||||||
}];
|
}];
|
||||||
|
|
||||||
let mut transaction_context = TransactionContext::new(accounts, 1, 3);
|
let mut transaction_context =
|
||||||
|
TransactionContext::new(accounts, Some(Rent::default()), 1, 3);
|
||||||
let mut invoke_context =
|
let mut invoke_context =
|
||||||
InvokeContext::new_mock(&mut transaction_context, &builtin_programs);
|
InvokeContext::new_mock(&mut transaction_context, &builtin_programs);
|
||||||
|
|
||||||
|
@ -1707,7 +1624,13 @@ mod tests {
|
||||||
);
|
);
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert!(result.is_ok());
|
||||||
assert_eq!(invoke_context.accounts_data_meter.remaining(), 0);
|
assert_eq!(
|
||||||
|
invoke_context
|
||||||
|
.transaction_context
|
||||||
|
.accounts_resize_delta()
|
||||||
|
.unwrap(),
|
||||||
|
user_account_data_len as i64 * 2
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test 2: Resize the account to *the same size*, so not consuming any additional size; this must succeed
|
// Test 2: Resize the account to *the same size*, so not consuming any additional size; this must succeed
|
||||||
|
@ -1725,10 +1648,16 @@ mod tests {
|
||||||
);
|
);
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert!(result.is_ok());
|
||||||
assert_eq!(invoke_context.accounts_data_meter.remaining(), 0);
|
assert_eq!(
|
||||||
|
invoke_context
|
||||||
|
.transaction_context
|
||||||
|
.accounts_resize_delta()
|
||||||
|
.unwrap(),
|
||||||
|
user_account_data_len as i64 * 2
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test 3: Resize the account to exceed the budget; this must fail
|
// Test 3: Resize the account to exceed the budget; this must succeed
|
||||||
{
|
{
|
||||||
let new_len = user_account_data_len + remaining_account_data_len + 1;
|
let new_len = user_account_data_len + remaining_account_data_len + 1;
|
||||||
let instruction_data =
|
let instruction_data =
|
||||||
|
@ -1742,12 +1671,14 @@ mod tests {
|
||||||
&mut ExecuteTimings::default(),
|
&mut ExecuteTimings::default(),
|
||||||
);
|
);
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert!(result.is_ok());
|
||||||
assert!(matches!(
|
assert_eq!(
|
||||||
result,
|
invoke_context
|
||||||
Err(solana_sdk::instruction::InstructionError::MaxAccountsDataSizeExceeded)
|
.transaction_context
|
||||||
));
|
.accounts_resize_delta()
|
||||||
assert_eq!(invoke_context.accounts_data_meter.remaining(), 0);
|
.unwrap(),
|
||||||
|
user_account_data_len as i64 * 2 + 1
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -183,8 +183,14 @@ pub fn builtin_process_instruction(
|
||||||
if borrowed_account.get_lamports() != lamports {
|
if borrowed_account.get_lamports() != lamports {
|
||||||
borrowed_account.set_lamports(lamports)?;
|
borrowed_account.set_lamports(lamports)?;
|
||||||
}
|
}
|
||||||
if borrowed_account.get_data() != data {
|
// The redundant check helps to avoid the expensive data comparison if we can
|
||||||
borrowed_account.set_data(&data)?;
|
match borrowed_account
|
||||||
|
.can_data_be_resized(data.len())
|
||||||
|
.and_then(|_| borrowed_account.can_data_be_changed())
|
||||||
|
{
|
||||||
|
Ok(()) => borrowed_account.set_data(&data)?,
|
||||||
|
Err(err) if borrowed_account.get_data() != data => return Err(err),
|
||||||
|
_ => {}
|
||||||
}
|
}
|
||||||
if borrowed_account.get_owner() != &owner {
|
if borrowed_account.get_owner() != &owner {
|
||||||
borrowed_account.set_owner(owner.as_ref())?;
|
borrowed_account.set_owner(owner.as_ref())?;
|
||||||
|
@ -302,14 +308,23 @@ impl solana_sdk::program_stubs::SyscallStubs for SyscallStubs {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
let account_info_data = account_info.try_borrow_data().unwrap();
|
let account_info_data = account_info.try_borrow_data().unwrap();
|
||||||
if borrowed_account.get_data() != *account_info_data {
|
// The redundant check helps to avoid the expensive data comparison if we can
|
||||||
borrowed_account.set_data(&account_info_data).unwrap();
|
match borrowed_account
|
||||||
|
.can_data_be_resized(account_info_data.len())
|
||||||
|
.and_then(|_| borrowed_account.can_data_be_changed())
|
||||||
|
{
|
||||||
|
Ok(()) => borrowed_account.set_data(&account_info_data).unwrap(),
|
||||||
|
Err(err) if borrowed_account.get_data() != *account_info_data => {
|
||||||
|
panic!("{:?}", err);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
}
|
}
|
||||||
if borrowed_account.is_executable() != account_info.executable {
|
if borrowed_account.is_executable() != account_info.executable {
|
||||||
borrowed_account
|
borrowed_account
|
||||||
.set_executable(account_info.executable)
|
.set_executable(account_info.executable)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
// Change the owner at the end so that we are allowed to change the lamports and data before
|
||||||
if borrowed_account.get_owner() != account_info.owner {
|
if borrowed_account.get_owner() != account_info.owner {
|
||||||
borrowed_account
|
borrowed_account
|
||||||
.set_owner(account_info.owner.as_ref())
|
.set_owner(account_info.owner.as_ref())
|
||||||
|
|
|
@ -305,7 +305,7 @@ fn run_program(name: &str) -> u64 {
|
||||||
tracer = Some(vm.get_tracer().clone());
|
tracer = Some(vm.get_tracer().clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
deserialize_parameters(
|
assert!(match deserialize_parameters(
|
||||||
invoke_context.transaction_context,
|
invoke_context.transaction_context,
|
||||||
invoke_context
|
invoke_context
|
||||||
.transaction_context
|
.transaction_context
|
||||||
|
@ -313,8 +313,21 @@ fn run_program(name: &str) -> u64 {
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
parameter_bytes.as_slice(),
|
parameter_bytes.as_slice(),
|
||||||
&account_lengths,
|
&account_lengths,
|
||||||
)
|
) {
|
||||||
.unwrap();
|
Ok(()) => true,
|
||||||
|
Err(InstructionError::ModifiedProgramId) => true,
|
||||||
|
Err(InstructionError::ExternalAccountLamportSpend) => true,
|
||||||
|
Err(InstructionError::ReadonlyLamportChange) => true,
|
||||||
|
Err(InstructionError::ExecutableLamportChange) => true,
|
||||||
|
Err(InstructionError::ExecutableAccountNotRentExempt) => true,
|
||||||
|
Err(InstructionError::ExecutableModified) => true,
|
||||||
|
Err(InstructionError::AccountDataSizeChanged) => true,
|
||||||
|
Err(InstructionError::InvalidRealloc) => true,
|
||||||
|
Err(InstructionError::ExecutableDataModified) => true,
|
||||||
|
Err(InstructionError::ReadonlyDataModified) => true,
|
||||||
|
Err(InstructionError::ExternalAccountDataModified) => true,
|
||||||
|
_ => false,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
instruction_count
|
instruction_count
|
||||||
})
|
})
|
||||||
|
|
|
@ -9,6 +9,7 @@ use {
|
||||||
solana_sdk::{
|
solana_sdk::{
|
||||||
account::{Account, AccountSharedData},
|
account::{Account, AccountSharedData},
|
||||||
bpf_loader,
|
bpf_loader,
|
||||||
|
sysvar::rent::Rent,
|
||||||
transaction_context::{InstructionAccount, TransactionContext},
|
transaction_context::{InstructionAccount, TransactionContext},
|
||||||
},
|
},
|
||||||
test::Bencher,
|
test::Bencher,
|
||||||
|
@ -101,7 +102,8 @@ fn create_inputs() -> TransactionContext {
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
let mut transaction_context = TransactionContext::new(transaction_accounts, 1, 1);
|
let mut transaction_context =
|
||||||
|
TransactionContext::new(transaction_accounts, Some(Rent::default()), 1, 1);
|
||||||
let instruction_data = vec![1u8, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
|
let instruction_data = vec![1u8, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
|
||||||
transaction_context
|
transaction_context
|
||||||
.push(&[0], &instruction_accounts, &instruction_data, true)
|
.push(&[0], &instruction_accounts, &instruction_data, true)
|
||||||
|
|
|
@ -164,18 +164,28 @@ pub fn deserialize_parameters_unaligned(
|
||||||
start += size_of::<u8>(); // is_signer
|
start += size_of::<u8>(); // is_signer
|
||||||
start += size_of::<u8>(); // is_writable
|
start += size_of::<u8>(); // is_writable
|
||||||
start += size_of::<Pubkey>(); // key
|
start += size_of::<Pubkey>(); // key
|
||||||
let _ = borrowed_account.set_lamports(LittleEndian::read_u64(
|
let lamports = LittleEndian::read_u64(
|
||||||
buffer
|
buffer
|
||||||
.get(start..)
|
.get(start..)
|
||||||
.ok_or(InstructionError::InvalidArgument)?,
|
.ok_or(InstructionError::InvalidArgument)?,
|
||||||
));
|
);
|
||||||
|
if borrowed_account.get_lamports() != lamports {
|
||||||
|
borrowed_account.set_lamports(lamports)?;
|
||||||
|
}
|
||||||
start += size_of::<u64>() // lamports
|
start += size_of::<u64>() // lamports
|
||||||
+ size_of::<u64>(); // data length
|
+ size_of::<u64>(); // data length
|
||||||
let _ = borrowed_account.set_data(
|
let data = buffer
|
||||||
buffer
|
.get(start..start + pre_len)
|
||||||
.get(start..start + pre_len)
|
.ok_or(InstructionError::InvalidArgument)?;
|
||||||
.ok_or(InstructionError::InvalidArgument)?,
|
// The redundant check helps to avoid the expensive data comparison if we can
|
||||||
);
|
match borrowed_account
|
||||||
|
.can_data_be_resized(data.len())
|
||||||
|
.and_then(|_| borrowed_account.can_data_be_changed())
|
||||||
|
{
|
||||||
|
Ok(()) => borrowed_account.set_data(data)?,
|
||||||
|
Err(err) if borrowed_account.get_data() != data => return Err(err),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
start += pre_len // data
|
start += pre_len // data
|
||||||
+ size_of::<Pubkey>() // owner
|
+ size_of::<Pubkey>() // owner
|
||||||
+ size_of::<u8>() // executable
|
+ size_of::<u8>() // executable
|
||||||
|
@ -302,17 +312,18 @@ pub fn deserialize_parameters_aligned(
|
||||||
+ size_of::<u8>() // executable
|
+ size_of::<u8>() // executable
|
||||||
+ size_of::<u32>() // original_data_len
|
+ size_of::<u32>() // original_data_len
|
||||||
+ size_of::<Pubkey>(); // key
|
+ size_of::<Pubkey>(); // key
|
||||||
let _ = borrowed_account.set_owner(
|
let owner = buffer
|
||||||
buffer
|
.get(start..start + size_of::<Pubkey>())
|
||||||
.get(start..start + size_of::<Pubkey>())
|
.ok_or(InstructionError::InvalidArgument)?;
|
||||||
.ok_or(InstructionError::InvalidArgument)?,
|
|
||||||
);
|
|
||||||
start += size_of::<Pubkey>(); // owner
|
start += size_of::<Pubkey>(); // owner
|
||||||
let _ = borrowed_account.set_lamports(LittleEndian::read_u64(
|
let lamports = LittleEndian::read_u64(
|
||||||
buffer
|
buffer
|
||||||
.get(start..)
|
.get(start..)
|
||||||
.ok_or(InstructionError::InvalidArgument)?,
|
.ok_or(InstructionError::InvalidArgument)?,
|
||||||
));
|
);
|
||||||
|
if borrowed_account.get_lamports() != lamports {
|
||||||
|
borrowed_account.set_lamports(lamports)?;
|
||||||
|
}
|
||||||
start += size_of::<u64>(); // lamports
|
start += size_of::<u64>(); // lamports
|
||||||
let post_len = LittleEndian::read_u64(
|
let post_len = LittleEndian::read_u64(
|
||||||
buffer
|
buffer
|
||||||
|
@ -320,21 +331,31 @@ pub fn deserialize_parameters_aligned(
|
||||||
.ok_or(InstructionError::InvalidArgument)?,
|
.ok_or(InstructionError::InvalidArgument)?,
|
||||||
) as usize;
|
) as usize;
|
||||||
start += size_of::<u64>(); // data length
|
start += size_of::<u64>(); // data length
|
||||||
|
|
||||||
if post_len.saturating_sub(*pre_len) > MAX_PERMITTED_DATA_INCREASE
|
if post_len.saturating_sub(*pre_len) > MAX_PERMITTED_DATA_INCREASE
|
||||||
|| post_len > MAX_PERMITTED_DATA_LENGTH as usize
|
|| post_len > MAX_PERMITTED_DATA_LENGTH as usize
|
||||||
{
|
{
|
||||||
return Err(InstructionError::InvalidRealloc);
|
return Err(InstructionError::InvalidRealloc);
|
||||||
}
|
}
|
||||||
let data_end = start + post_len;
|
let data_end = start + post_len;
|
||||||
let _ = borrowed_account.set_data(
|
let data = buffer
|
||||||
buffer
|
.get(start..data_end)
|
||||||
.get(start..data_end)
|
.ok_or(InstructionError::InvalidArgument)?;
|
||||||
.ok_or(InstructionError::InvalidArgument)?,
|
// The redundant check helps to avoid the expensive data comparison if we can
|
||||||
);
|
match borrowed_account
|
||||||
|
.can_data_be_resized(data.len())
|
||||||
|
.and_then(|_| borrowed_account.can_data_be_changed())
|
||||||
|
{
|
||||||
|
Ok(()) => borrowed_account.set_data(data)?,
|
||||||
|
Err(err) if borrowed_account.get_data() != data => return Err(err),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
start += *pre_len + MAX_PERMITTED_DATA_INCREASE; // data
|
start += *pre_len + MAX_PERMITTED_DATA_INCREASE; // data
|
||||||
start += (start as *const u8).align_offset(BPF_ALIGN_OF_U128);
|
start += (start as *const u8).align_offset(BPF_ALIGN_OF_U128);
|
||||||
start += size_of::<u64>(); // rent_epoch
|
start += size_of::<u64>(); // rent_epoch
|
||||||
|
if borrowed_account.get_owner().to_bytes() != owner {
|
||||||
|
// Change the owner at the end so that we are allowed to change the lamports and data before
|
||||||
|
borrowed_account.set_owner(owner)?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -351,6 +372,7 @@ mod tests {
|
||||||
bpf_loader,
|
bpf_loader,
|
||||||
entrypoint::deserialize,
|
entrypoint::deserialize,
|
||||||
instruction::AccountMeta,
|
instruction::AccountMeta,
|
||||||
|
sysvar::rent::Rent,
|
||||||
},
|
},
|
||||||
std::{
|
std::{
|
||||||
cell::RefCell,
|
cell::RefCell,
|
||||||
|
@ -453,8 +475,12 @@ mod tests {
|
||||||
instruction_accounts,
|
instruction_accounts,
|
||||||
&program_indices,
|
&program_indices,
|
||||||
);
|
);
|
||||||
let mut transaction_context =
|
let mut transaction_context = TransactionContext::new(
|
||||||
TransactionContext::new(preparation.transaction_accounts, 1, 1);
|
preparation.transaction_accounts,
|
||||||
|
Some(Rent::default()),
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
);
|
||||||
let mut invoke_context = InvokeContext::new_mock(&mut transaction_context, &[]);
|
let mut invoke_context = InvokeContext::new_mock(&mut transaction_context, &[]);
|
||||||
invoke_context
|
invoke_context
|
||||||
.push(
|
.push(
|
||||||
|
@ -512,16 +538,6 @@ mod tests {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
for index_in_transaction in 1..original_accounts.len() {
|
|
||||||
let mut account = invoke_context
|
|
||||||
.transaction_context
|
|
||||||
.get_account_at_index(index_in_transaction)
|
|
||||||
.unwrap()
|
|
||||||
.borrow_mut();
|
|
||||||
account.set_lamports(0);
|
|
||||||
account.set_data(vec![0; 0]);
|
|
||||||
account.set_owner(Pubkey::default());
|
|
||||||
}
|
|
||||||
deserialize_parameters(
|
deserialize_parameters(
|
||||||
invoke_context.transaction_context,
|
invoke_context.transaction_context,
|
||||||
instruction_context,
|
instruction_context,
|
||||||
|
@ -545,13 +561,12 @@ mod tests {
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.1
|
.1
|
||||||
.set_owner(bpf_loader_deprecated::id());
|
.set_owner(bpf_loader_deprecated::id());
|
||||||
let _ = invoke_context
|
invoke_context
|
||||||
.transaction_context
|
.transaction_context
|
||||||
.get_current_instruction_context()
|
.get_account_at_index(0)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.try_borrow_program_account(invoke_context.transaction_context, 0)
|
.borrow_mut()
|
||||||
.unwrap()
|
.set_owner(bpf_loader_deprecated::id());
|
||||||
.set_owner(bpf_loader_deprecated::id().as_ref());
|
|
||||||
|
|
||||||
let (mut serialized, account_lengths) =
|
let (mut serialized, account_lengths) =
|
||||||
serialize_parameters(invoke_context.transaction_context, instruction_context).unwrap();
|
serialize_parameters(invoke_context.transaction_context, instruction_context).unwrap();
|
||||||
|
@ -578,15 +593,6 @@ mod tests {
|
||||||
assert_eq!(account.rent_epoch(), account_info.rent_epoch);
|
assert_eq!(account.rent_epoch(), account_info.rent_epoch);
|
||||||
}
|
}
|
||||||
|
|
||||||
for index_in_transaction in 1..original_accounts.len() {
|
|
||||||
let mut account = invoke_context
|
|
||||||
.transaction_context
|
|
||||||
.get_account_at_index(index_in_transaction)
|
|
||||||
.unwrap()
|
|
||||||
.borrow_mut();
|
|
||||||
account.set_lamports(0);
|
|
||||||
account.set_data(vec![0; 0]);
|
|
||||||
}
|
|
||||||
deserialize_parameters(
|
deserialize_parameters(
|
||||||
invoke_context.transaction_context,
|
invoke_context.transaction_context,
|
||||||
instruction_context,
|
instruction_context,
|
||||||
|
|
|
@ -17,13 +17,14 @@ use {
|
||||||
vm::{EbpfVm, SyscallObject, SyscallRegistry},
|
vm::{EbpfVm, SyscallObject, SyscallRegistry},
|
||||||
},
|
},
|
||||||
solana_sdk::{
|
solana_sdk::{
|
||||||
account::WritableAccount,
|
account::{ReadableAccount, WritableAccount},
|
||||||
account_info::AccountInfo,
|
account_info::AccountInfo,
|
||||||
blake3, bpf_loader, bpf_loader_deprecated, bpf_loader_upgradeable,
|
blake3, bpf_loader, bpf_loader_deprecated, bpf_loader_upgradeable,
|
||||||
entrypoint::{BPF_ALIGN_OF_U128, MAX_PERMITTED_DATA_INCREASE, SUCCESS},
|
entrypoint::{BPF_ALIGN_OF_U128, MAX_PERMITTED_DATA_INCREASE, SUCCESS},
|
||||||
feature_set::{
|
feature_set::{
|
||||||
blake3_syscall_enabled, check_physical_overlapping, check_slice_translation_size,
|
blake3_syscall_enabled, check_physical_overlapping, check_slice_translation_size,
|
||||||
curve25519_syscall_enabled, disable_fees_sysvar, libsecp256k1_0_5_upgrade_enabled,
|
curve25519_syscall_enabled, disable_fees_sysvar,
|
||||||
|
enable_early_verification_of_account_modifications, libsecp256k1_0_5_upgrade_enabled,
|
||||||
limit_secp256k1_recovery_id, prevent_calling_precompiles_as_programs,
|
limit_secp256k1_recovery_id, prevent_calling_precompiles_as_programs,
|
||||||
syscall_saturated_math,
|
syscall_saturated_math,
|
||||||
},
|
},
|
||||||
|
@ -2663,26 +2664,56 @@ where
|
||||||
invoke_context,
|
invoke_context,
|
||||||
)?;
|
)?;
|
||||||
{
|
{
|
||||||
callee_account
|
if callee_account.get_lamports() != *caller_account.lamports {
|
||||||
.set_lamports(*caller_account.lamports)
|
callee_account
|
||||||
.map_err(SyscallError::InstructionError)?;
|
.set_lamports(*caller_account.lamports)
|
||||||
callee_account
|
.map_err(SyscallError::InstructionError)?;
|
||||||
.set_data(caller_account.data)
|
}
|
||||||
.map_err(SyscallError::InstructionError)?;
|
// The redundant check helps to avoid the expensive data comparison if we can
|
||||||
callee_account
|
match callee_account
|
||||||
.set_executable(caller_account.executable)
|
.can_data_be_resized(caller_account.data.len())
|
||||||
.map_err(SyscallError::InstructionError)?;
|
.and_then(|_| callee_account.can_data_be_changed())
|
||||||
callee_account
|
{
|
||||||
.set_owner(caller_account.owner.as_ref())
|
Ok(()) => callee_account
|
||||||
.map_err(SyscallError::InstructionError)?;
|
.set_data(caller_account.data)
|
||||||
|
.map_err(SyscallError::InstructionError)?,
|
||||||
|
Err(err) if callee_account.get_data() != caller_account.data => {
|
||||||
|
return Err(EbpfError::UserError(BpfError::SyscallError(
|
||||||
|
SyscallError::InstructionError(err),
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
if callee_account.is_executable() != caller_account.executable {
|
||||||
|
callee_account
|
||||||
|
.set_executable(caller_account.executable)
|
||||||
|
.map_err(SyscallError::InstructionError)?;
|
||||||
|
}
|
||||||
|
// Change the owner at the end so that we are allowed to change the lamports and data before
|
||||||
|
if callee_account.get_owner() != caller_account.owner {
|
||||||
|
callee_account
|
||||||
|
.set_owner(caller_account.owner.as_ref())
|
||||||
|
.map_err(SyscallError::InstructionError)?;
|
||||||
|
}
|
||||||
drop(callee_account);
|
drop(callee_account);
|
||||||
let callee_account = invoke_context
|
let callee_account = invoke_context
|
||||||
.transaction_context
|
.transaction_context
|
||||||
.get_account_at_index(instruction_account.index_in_transaction)
|
.get_account_at_index(instruction_account.index_in_transaction)
|
||||||
.map_err(SyscallError::InstructionError)?;
|
.map_err(SyscallError::InstructionError)?;
|
||||||
callee_account
|
if callee_account.borrow().rent_epoch() != caller_account.rent_epoch {
|
||||||
.borrow_mut()
|
if invoke_context
|
||||||
.set_rent_epoch(caller_account.rent_epoch);
|
.feature_set
|
||||||
|
.is_active(&enable_early_verification_of_account_modifications::id())
|
||||||
|
{
|
||||||
|
Err(SyscallError::InstructionError(
|
||||||
|
InstructionError::RentEpochModified,
|
||||||
|
))?;
|
||||||
|
} else {
|
||||||
|
callee_account
|
||||||
|
.borrow_mut()
|
||||||
|
.set_rent_epoch(caller_account.rent_epoch);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
let caller_account = if instruction_account.is_writable {
|
let caller_account = if instruction_account.is_writable {
|
||||||
let orig_data_lens = invoke_context
|
let orig_data_lens = invoke_context
|
||||||
|
@ -3364,7 +3395,8 @@ mod tests {
|
||||||
),
|
),
|
||||||
($program_key, AccountSharedData::new(0, 0, &$loader_key)),
|
($program_key, AccountSharedData::new(0, 0, &$loader_key)),
|
||||||
];
|
];
|
||||||
let mut $transaction_context = TransactionContext::new(transaction_accounts, 1, 1);
|
let mut $transaction_context =
|
||||||
|
TransactionContext::new(transaction_accounts, Some(Rent::default()), 1, 1);
|
||||||
let mut $invoke_context = InvokeContext::new_mock(&mut $transaction_context, &[]);
|
let mut $invoke_context = InvokeContext::new_mock(&mut $transaction_context, &[]);
|
||||||
$invoke_context.push(&[], &[0, 1], &[]).unwrap();
|
$invoke_context.push(&[], &[0, 1], &[]).unwrap();
|
||||||
};
|
};
|
||||||
|
|
|
@ -2783,6 +2783,7 @@ mod tests {
|
||||||
Rent::id(),
|
Rent::id(),
|
||||||
create_account_shared_data_for_test(&Rent::default()),
|
create_account_shared_data_for_test(&Rent::default()),
|
||||||
)],
|
)],
|
||||||
|
Some(Rent::default()),
|
||||||
1,
|
1,
|
||||||
1,
|
1,
|
||||||
)
|
)
|
||||||
|
@ -2894,7 +2895,8 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_things_can_merge() {
|
fn test_things_can_merge() {
|
||||||
let mut transaction_context = TransactionContext::new(Vec::new(), 1, 1);
|
let mut transaction_context =
|
||||||
|
TransactionContext::new(Vec::new(), Some(Rent::default()), 1, 1);
|
||||||
let invoke_context = InvokeContext::new_mock(&mut transaction_context, &[]);
|
let invoke_context = InvokeContext::new_mock(&mut transaction_context, &[]);
|
||||||
let good_stake = Stake {
|
let good_stake = Stake {
|
||||||
credits_observed: 4242,
|
credits_observed: 4242,
|
||||||
|
@ -2993,7 +2995,8 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_metas_can_merge() {
|
fn test_metas_can_merge() {
|
||||||
let mut transaction_context = TransactionContext::new(Vec::new(), 1, 1);
|
let mut transaction_context =
|
||||||
|
TransactionContext::new(Vec::new(), Some(Rent::default()), 1, 1);
|
||||||
let invoke_context = InvokeContext::new_mock(&mut transaction_context, &[]);
|
let invoke_context = InvokeContext::new_mock(&mut transaction_context, &[]);
|
||||||
// Identical Metas can merge
|
// Identical Metas can merge
|
||||||
assert!(MergeKind::metas_can_merge(
|
assert!(MergeKind::metas_can_merge(
|
||||||
|
@ -3140,7 +3143,8 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_merge_kind_get_if_mergeable() {
|
fn test_merge_kind_get_if_mergeable() {
|
||||||
let mut transaction_context = TransactionContext::new(Vec::new(), 1, 1);
|
let mut transaction_context =
|
||||||
|
TransactionContext::new(Vec::new(), Some(Rent::default()), 1, 1);
|
||||||
let invoke_context = InvokeContext::new_mock(&mut transaction_context, &[]);
|
let invoke_context = InvokeContext::new_mock(&mut transaction_context, &[]);
|
||||||
let authority_pubkey = Pubkey::new_unique();
|
let authority_pubkey = Pubkey::new_unique();
|
||||||
let initial_lamports = 4242424242;
|
let initial_lamports = 4242424242;
|
||||||
|
@ -3379,7 +3383,8 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_merge_kind_merge() {
|
fn test_merge_kind_merge() {
|
||||||
let mut transaction_context = TransactionContext::new(Vec::new(), 1, 1);
|
let mut transaction_context =
|
||||||
|
TransactionContext::new(Vec::new(), Some(Rent::default()), 1, 1);
|
||||||
let invoke_context = InvokeContext::new_mock(&mut transaction_context, &[]);
|
let invoke_context = InvokeContext::new_mock(&mut transaction_context, &[]);
|
||||||
let clock = Clock::default();
|
let clock = Clock::default();
|
||||||
let lamports = 424242;
|
let lamports = 424242;
|
||||||
|
|
|
@ -107,7 +107,12 @@ fn bench_process_vote_instruction(
|
||||||
instruction_data: Vec<u8>,
|
instruction_data: Vec<u8>,
|
||||||
) {
|
) {
|
||||||
bencher.iter(|| {
|
bencher.iter(|| {
|
||||||
let mut transaction_context = TransactionContext::new(transaction_accounts.clone(), 1, 1);
|
let mut transaction_context = TransactionContext::new(
|
||||||
|
transaction_accounts.clone(),
|
||||||
|
Some(sysvar::rent::Rent::default()),
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
);
|
||||||
let mut invoke_context = InvokeContext::new_mock(&mut transaction_context, &[]);
|
let mut invoke_context = InvokeContext::new_mock(&mut transaction_context, &[]);
|
||||||
invoke_context
|
invoke_context
|
||||||
.push(&instruction_accounts, &[0], &instruction_data)
|
.push(&instruction_accounts, &[0], &instruction_data)
|
||||||
|
|
|
@ -16,7 +16,7 @@ use {
|
||||||
},
|
},
|
||||||
solana_sdk::{
|
solana_sdk::{
|
||||||
account::AccountSharedData, bpf_loader, instruction::AccountMeta, pubkey::Pubkey,
|
account::AccountSharedData, bpf_loader, instruction::AccountMeta, pubkey::Pubkey,
|
||||||
transaction_context::TransactionContext,
|
sysvar::rent::Rent, transaction_context::TransactionContext,
|
||||||
},
|
},
|
||||||
std::{
|
std::{
|
||||||
fmt::{Debug, Formatter},
|
fmt::{Debug, Formatter},
|
||||||
|
@ -216,7 +216,12 @@ native machine code before execting it in the virtual machine.",
|
||||||
let program_indices = [0, 1];
|
let program_indices = [0, 1];
|
||||||
let preparation =
|
let preparation =
|
||||||
prepare_mock_invoke_context(transaction_accounts, instruction_accounts, &program_indices);
|
prepare_mock_invoke_context(transaction_accounts, instruction_accounts, &program_indices);
|
||||||
let mut transaction_context = TransactionContext::new(preparation.transaction_accounts, 1, 1);
|
let mut transaction_context = TransactionContext::new(
|
||||||
|
preparation.transaction_accounts,
|
||||||
|
Some(Rent::default()),
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
);
|
||||||
let mut invoke_context = InvokeContext::new_mock(&mut transaction_context, &[]);
|
let mut invoke_context = InvokeContext::new_mock(&mut transaction_context, &[]);
|
||||||
invoke_context
|
invoke_context
|
||||||
.push(
|
.push(
|
||||||
|
|
|
@ -109,7 +109,7 @@ use {
|
||||||
feature,
|
feature,
|
||||||
feature_set::{
|
feature_set::{
|
||||||
self, add_set_compute_unit_price_ix, default_units_per_instruction,
|
self, add_set_compute_unit_price_ix, default_units_per_instruction,
|
||||||
disable_fee_calculator, FeatureSet,
|
disable_fee_calculator, enable_early_verification_of_account_modifications, FeatureSet,
|
||||||
},
|
},
|
||||||
fee::FeeStructure,
|
fee::FeeStructure,
|
||||||
fee_calculator::{FeeCalculator, FeeRateGovernor},
|
fee_calculator::{FeeCalculator, FeeRateGovernor},
|
||||||
|
@ -4274,6 +4274,14 @@ impl Bank {
|
||||||
let transaction_accounts = std::mem::take(&mut loaded_transaction.accounts);
|
let transaction_accounts = std::mem::take(&mut loaded_transaction.accounts);
|
||||||
let mut transaction_context = TransactionContext::new(
|
let mut transaction_context = TransactionContext::new(
|
||||||
transaction_accounts,
|
transaction_accounts,
|
||||||
|
if self
|
||||||
|
.feature_set
|
||||||
|
.is_active(&enable_early_verification_of_account_modifications::id())
|
||||||
|
{
|
||||||
|
Some(self.rent_collector.rent)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
},
|
||||||
compute_budget.max_invoke_depth.saturating_add(1),
|
compute_budget.max_invoke_depth.saturating_add(1),
|
||||||
tx.message().instructions().len(),
|
tx.message().instructions().len(),
|
||||||
);
|
);
|
||||||
|
@ -4352,7 +4360,7 @@ impl Bank {
|
||||||
}
|
}
|
||||||
err
|
err
|
||||||
});
|
});
|
||||||
let accounts_data_len_delta = status
|
let mut accounts_data_len_delta = status
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map_or(0, |info| info.accounts_data_len_delta);
|
.map_or(0, |info| info.accounts_data_len_delta);
|
||||||
let status = status.map(|_| ());
|
let status = status.map(|_| ());
|
||||||
|
@ -4368,9 +4376,31 @@ impl Bank {
|
||||||
accounts,
|
accounts,
|
||||||
instruction_trace,
|
instruction_trace,
|
||||||
mut return_data,
|
mut return_data,
|
||||||
..
|
changed_account_count,
|
||||||
|
total_size_of_all_accounts,
|
||||||
|
total_size_of_touched_accounts,
|
||||||
|
accounts_resize_delta,
|
||||||
} = transaction_context.into();
|
} = transaction_context.into();
|
||||||
loaded_transaction.accounts = accounts;
|
loaded_transaction.accounts = accounts;
|
||||||
|
if self
|
||||||
|
.feature_set
|
||||||
|
.is_active(&enable_early_verification_of_account_modifications::id())
|
||||||
|
{
|
||||||
|
saturating_add_assign!(
|
||||||
|
timings.details.total_account_count,
|
||||||
|
loaded_transaction.accounts.len() as u64
|
||||||
|
);
|
||||||
|
saturating_add_assign!(timings.details.changed_account_count, changed_account_count);
|
||||||
|
saturating_add_assign!(
|
||||||
|
timings.details.total_data_size,
|
||||||
|
total_size_of_all_accounts as usize
|
||||||
|
);
|
||||||
|
saturating_add_assign!(
|
||||||
|
timings.details.data_size_changed,
|
||||||
|
total_size_of_touched_accounts as usize
|
||||||
|
);
|
||||||
|
accounts_data_len_delta = status.as_ref().map_or(0, |_| accounts_resize_delta);
|
||||||
|
}
|
||||||
|
|
||||||
let inner_instructions = if enable_cpi_recording {
|
let inner_instructions = if enable_cpi_recording {
|
||||||
Some(inner_instructions_list_from_instruction_trace(
|
Some(inner_instructions_list_from_instruction_trace(
|
||||||
|
@ -18668,6 +18698,7 @@ pub(crate) mod tests {
|
||||||
});
|
});
|
||||||
let transaction_context = TransactionContext::new(
|
let transaction_context = TransactionContext::new(
|
||||||
loaded_txs[0].0.as_ref().unwrap().accounts.clone(),
|
loaded_txs[0].0.as_ref().unwrap().accounts.clone(),
|
||||||
|
Some(Rent::default()),
|
||||||
compute_budget.max_invoke_depth.saturating_add(1),
|
compute_budget.max_invoke_depth.saturating_add(1),
|
||||||
number_of_instructions_at_transaction_level,
|
number_of_instructions_at_transaction_level,
|
||||||
);
|
);
|
||||||
|
@ -18819,15 +18850,15 @@ pub(crate) mod tests {
|
||||||
fn test_inner_instructions_list_from_instruction_trace() {
|
fn test_inner_instructions_list_from_instruction_trace() {
|
||||||
let instruction_trace = vec![
|
let instruction_trace = vec![
|
||||||
vec![
|
vec![
|
||||||
InstructionContext::new(0, &[], &[], &[1]),
|
InstructionContext::new(0, 0, &[], &[], &[1]),
|
||||||
InstructionContext::new(1, &[], &[], &[2]),
|
InstructionContext::new(1, 0, &[], &[], &[2]),
|
||||||
],
|
],
|
||||||
vec![],
|
vec![],
|
||||||
vec![
|
vec![
|
||||||
InstructionContext::new(0, &[], &[], &[3]),
|
InstructionContext::new(0, 0, &[], &[], &[3]),
|
||||||
InstructionContext::new(1, &[], &[], &[4]),
|
InstructionContext::new(1, 0, &[], &[], &[4]),
|
||||||
InstructionContext::new(2, &[], &[], &[5]),
|
InstructionContext::new(2, 0, &[], &[], &[5]),
|
||||||
InstructionContext::new(1, &[], &[], &[6]),
|
InstructionContext::new(1, 0, &[], &[], &[6]),
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
@ -282,7 +282,8 @@ mod tests {
|
||||||
create_loadable_account_for_test("mock_system_program"),
|
create_loadable_account_for_test("mock_system_program"),
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
let mut transaction_context = TransactionContext::new(accounts, 1, 3);
|
let mut transaction_context =
|
||||||
|
TransactionContext::new(accounts, Some(Rent::default()), 1, 3);
|
||||||
let program_indices = vec![vec![2]];
|
let program_indices = vec![vec![2]];
|
||||||
let executors = Rc::new(RefCell::new(Executors::default()));
|
let executors = Rc::new(RefCell::new(Executors::default()));
|
||||||
let account_keys = transaction_context.get_keys_of_accounts().to_vec();
|
let account_keys = transaction_context.get_keys_of_accounts().to_vec();
|
||||||
|
@ -502,7 +503,8 @@ mod tests {
|
||||||
create_loadable_account_for_test("mock_system_program"),
|
create_loadable_account_for_test("mock_system_program"),
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
let mut transaction_context = TransactionContext::new(accounts, 1, 3);
|
let mut transaction_context =
|
||||||
|
TransactionContext::new(accounts, Some(Rent::default()), 1, 3);
|
||||||
let program_indices = vec![vec![2]];
|
let program_indices = vec![vec![2]];
|
||||||
let executors = Rc::new(RefCell::new(Executors::default()));
|
let executors = Rc::new(RefCell::new(Executors::default()));
|
||||||
let account_metas = vec![
|
let account_metas = vec![
|
||||||
|
@ -661,7 +663,8 @@ mod tests {
|
||||||
(secp256k1_program::id(), secp256k1_account),
|
(secp256k1_program::id(), secp256k1_account),
|
||||||
(mock_program_id, mock_program_account),
|
(mock_program_id, mock_program_account),
|
||||||
];
|
];
|
||||||
let mut transaction_context = TransactionContext::new(accounts, 1, 2);
|
let mut transaction_context =
|
||||||
|
TransactionContext::new(accounts, Some(Rent::default()), 1, 2);
|
||||||
|
|
||||||
let message = SanitizedMessage::Legacy(Message::new(
|
let message = SanitizedMessage::Legacy(Message::new(
|
||||||
&[
|
&[
|
||||||
|
|
|
@ -328,7 +328,8 @@ mod test {
|
||||||
is_writable: true,
|
is_writable: true,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
let mut transaction_context = TransactionContext::new(accounts, 1, 2);
|
let mut transaction_context =
|
||||||
|
TransactionContext::new(accounts, Some(Rent::default()), 1, 2);
|
||||||
let mut $invoke_context = InvokeContext::new_mock(&mut transaction_context, &[]);
|
let mut $invoke_context = InvokeContext::new_mock(&mut transaction_context, &[]);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -786,7 +786,8 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_address_create_with_seed_mismatch() {
|
fn test_address_create_with_seed_mismatch() {
|
||||||
let mut transaction_context = TransactionContext::new(Vec::new(), 1, 1);
|
let mut transaction_context =
|
||||||
|
TransactionContext::new(Vec::new(), Some(Rent::default()), 1, 1);
|
||||||
let invoke_context = InvokeContext::new_mock(&mut transaction_context, &[]);
|
let invoke_context = InvokeContext::new_mock(&mut transaction_context, &[]);
|
||||||
let from = Pubkey::new_unique();
|
let from = Pubkey::new_unique();
|
||||||
let seed = "dull boy";
|
let seed = "dull boy";
|
||||||
|
|
|
@ -456,6 +456,10 @@ pub mod enable_bpf_loader_extend_program_data_ix {
|
||||||
solana_sdk::declare_id!("8Zs9W7D9MpSEtUWSQdGniZk2cNmV22y6FLJwCx53asme");
|
solana_sdk::declare_id!("8Zs9W7D9MpSEtUWSQdGniZk2cNmV22y6FLJwCx53asme");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub mod enable_early_verification_of_account_modifications {
|
||||||
|
solana_sdk::declare_id!("7Vced912WrRnfjaiKRiNBcbuFw7RrnLv3E3z95Y4GTNc");
|
||||||
|
}
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
/// Map of feature identifiers to user-visible description
|
/// Map of feature identifiers to user-visible description
|
||||||
pub static ref FEATURE_NAMES: HashMap<Pubkey, &'static str> = [
|
pub static ref FEATURE_NAMES: HashMap<Pubkey, &'static str> = [
|
||||||
|
@ -564,6 +568,7 @@ lazy_static! {
|
||||||
(cap_accounts_data_size_per_block::id(), "cap the accounts data size per block #25517"),
|
(cap_accounts_data_size_per_block::id(), "cap the accounts data size per block #25517"),
|
||||||
(preserve_rent_epoch_for_rent_exempt_accounts::id(), "preserve rent epoch for rent exempt accounts #26479"),
|
(preserve_rent_epoch_for_rent_exempt_accounts::id(), "preserve rent epoch for rent exempt accounts #26479"),
|
||||||
(enable_bpf_loader_extend_program_data_ix::id(), "enable bpf upgradeable loader ExtendProgramData instruction #25234"),
|
(enable_bpf_loader_extend_program_data_ix::id(), "enable bpf upgradeable loader ExtendProgramData instruction #25234"),
|
||||||
|
(enable_early_verification_of_account_modifications::id(), "enable early verification of account modifications #25899"),
|
||||||
/*************** ADD NEW FEATURES HERE ***************/
|
/*************** ADD NEW FEATURES HERE ***************/
|
||||||
]
|
]
|
||||||
.iter()
|
.iter()
|
||||||
|
|
|
@ -5,6 +5,8 @@ use {
|
||||||
account::{AccountSharedData, ReadableAccount, WritableAccount},
|
account::{AccountSharedData, ReadableAccount, WritableAccount},
|
||||||
instruction::InstructionError,
|
instruction::InstructionError,
|
||||||
pubkey::Pubkey,
|
pubkey::Pubkey,
|
||||||
|
rent::Rent,
|
||||||
|
system_instruction::MAX_PERMITTED_DATA_LENGTH,
|
||||||
},
|
},
|
||||||
std::{
|
std::{
|
||||||
cell::{RefCell, RefMut},
|
cell::{RefCell, RefMut},
|
||||||
|
@ -43,18 +45,21 @@ pub struct InstructionAccount {
|
||||||
pub struct TransactionContext {
|
pub struct TransactionContext {
|
||||||
account_keys: Pin<Box<[Pubkey]>>,
|
account_keys: Pin<Box<[Pubkey]>>,
|
||||||
accounts: Pin<Box<[RefCell<AccountSharedData>]>>,
|
accounts: Pin<Box<[RefCell<AccountSharedData>]>>,
|
||||||
|
account_touched_flags: RefCell<Pin<Box<[bool]>>>,
|
||||||
instruction_context_capacity: usize,
|
instruction_context_capacity: usize,
|
||||||
instruction_stack: Vec<usize>,
|
instruction_stack: Vec<usize>,
|
||||||
number_of_instructions_at_transaction_level: usize,
|
number_of_instructions_at_transaction_level: usize,
|
||||||
instruction_trace: InstructionTrace,
|
instruction_trace: InstructionTrace,
|
||||||
return_data: TransactionReturnData,
|
return_data: TransactionReturnData,
|
||||||
total_resize_delta: RefCell<i64>,
|
accounts_resize_delta: RefCell<i64>,
|
||||||
|
rent: Option<Rent>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TransactionContext {
|
impl TransactionContext {
|
||||||
/// Constructs a new TransactionContext
|
/// Constructs a new TransactionContext
|
||||||
pub fn new(
|
pub fn new(
|
||||||
transaction_accounts: Vec<TransactionAccount>,
|
transaction_accounts: Vec<TransactionAccount>,
|
||||||
|
rent: Option<Rent>,
|
||||||
instruction_context_capacity: usize,
|
instruction_context_capacity: usize,
|
||||||
number_of_instructions_at_transaction_level: usize,
|
number_of_instructions_at_transaction_level: usize,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
|
@ -63,15 +68,18 @@ impl TransactionContext {
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(key, account)| (key, RefCell::new(account)))
|
.map(|(key, account)| (key, RefCell::new(account)))
|
||||||
.unzip();
|
.unzip();
|
||||||
|
let account_touched_flags = vec![false; accounts.len()];
|
||||||
Self {
|
Self {
|
||||||
account_keys: Pin::new(account_keys.into_boxed_slice()),
|
account_keys: Pin::new(account_keys.into_boxed_slice()),
|
||||||
accounts: Pin::new(accounts.into_boxed_slice()),
|
accounts: Pin::new(accounts.into_boxed_slice()),
|
||||||
|
account_touched_flags: RefCell::new(Pin::new(account_touched_flags.into_boxed_slice())),
|
||||||
instruction_context_capacity,
|
instruction_context_capacity,
|
||||||
instruction_stack: Vec::with_capacity(instruction_context_capacity),
|
instruction_stack: Vec::with_capacity(instruction_context_capacity),
|
||||||
number_of_instructions_at_transaction_level,
|
number_of_instructions_at_transaction_level,
|
||||||
instruction_trace: Vec::with_capacity(number_of_instructions_at_transaction_level),
|
instruction_trace: Vec::with_capacity(number_of_instructions_at_transaction_level),
|
||||||
return_data: TransactionReturnData::default(),
|
return_data: TransactionReturnData::default(),
|
||||||
total_resize_delta: RefCell::new(0),
|
accounts_resize_delta: RefCell::new(0),
|
||||||
|
rent,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -86,6 +94,11 @@ impl TransactionContext {
|
||||||
.collect())
|
.collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns true if `enable_early_verification_of_account_modifications` is active
|
||||||
|
pub fn is_early_verification_of_account_modifications_enabled(&self) -> bool {
|
||||||
|
self.rent.is_some()
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the total number of accounts loaded in this Transaction
|
/// Returns the total number of accounts loaded in this Transaction
|
||||||
pub fn get_number_of_accounts(&self) -> usize {
|
pub fn get_number_of_accounts(&self) -> usize {
|
||||||
self.accounts.len()
|
self.accounts.len()
|
||||||
|
@ -180,31 +193,51 @@ impl TransactionContext {
|
||||||
instruction_data: &[u8],
|
instruction_data: &[u8],
|
||||||
record_instruction_in_transaction_context_push: bool,
|
record_instruction_in_transaction_context_push: bool,
|
||||||
) -> Result<(), InstructionError> {
|
) -> Result<(), InstructionError> {
|
||||||
|
let callee_instruction_accounts_lamport_sum =
|
||||||
|
self.instruction_accounts_lamport_sum(instruction_accounts)?;
|
||||||
let index_in_trace = if self.instruction_stack.is_empty() {
|
let index_in_trace = if self.instruction_stack.is_empty() {
|
||||||
debug_assert!(
|
debug_assert!(
|
||||||
self.instruction_trace.len() < self.number_of_instructions_at_transaction_level
|
self.instruction_trace.len() < self.number_of_instructions_at_transaction_level
|
||||||
);
|
);
|
||||||
let instruction_context = InstructionContext {
|
let instruction_context = InstructionContext {
|
||||||
nesting_level: self.instruction_stack.len(),
|
nesting_level: self.instruction_stack.len(),
|
||||||
|
instruction_accounts_lamport_sum: callee_instruction_accounts_lamport_sum,
|
||||||
program_accounts: program_accounts.to_vec(),
|
program_accounts: program_accounts.to_vec(),
|
||||||
instruction_accounts: instruction_accounts.to_vec(),
|
instruction_accounts: instruction_accounts.to_vec(),
|
||||||
instruction_data: instruction_data.to_vec(),
|
instruction_data: instruction_data.to_vec(),
|
||||||
};
|
};
|
||||||
self.instruction_trace.push(vec![instruction_context]);
|
self.instruction_trace.push(vec![instruction_context]);
|
||||||
self.instruction_trace.len().saturating_sub(1)
|
self.instruction_trace.len().saturating_sub(1)
|
||||||
} else if let Some(instruction_trace) = self.instruction_trace.last_mut() {
|
|
||||||
if record_instruction_in_transaction_context_push {
|
|
||||||
let instruction_context = InstructionContext {
|
|
||||||
nesting_level: self.instruction_stack.len(),
|
|
||||||
program_accounts: program_accounts.to_vec(),
|
|
||||||
instruction_accounts: instruction_accounts.to_vec(),
|
|
||||||
instruction_data: instruction_data.to_vec(),
|
|
||||||
};
|
|
||||||
instruction_trace.push(instruction_context);
|
|
||||||
}
|
|
||||||
instruction_trace.len().saturating_sub(1)
|
|
||||||
} else {
|
} else {
|
||||||
return Err(InstructionError::CallDepth);
|
if self.is_early_verification_of_account_modifications_enabled() {
|
||||||
|
let caller_instruction_context = self.get_current_instruction_context()?;
|
||||||
|
let original_caller_instruction_accounts_lamport_sum =
|
||||||
|
caller_instruction_context.instruction_accounts_lamport_sum;
|
||||||
|
let current_caller_instruction_accounts_lamport_sum = self
|
||||||
|
.instruction_accounts_lamport_sum(
|
||||||
|
&caller_instruction_context.instruction_accounts,
|
||||||
|
)?;
|
||||||
|
if original_caller_instruction_accounts_lamport_sum
|
||||||
|
!= current_caller_instruction_accounts_lamport_sum
|
||||||
|
{
|
||||||
|
return Err(InstructionError::UnbalancedInstruction);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(instruction_trace) = self.instruction_trace.last_mut() {
|
||||||
|
if record_instruction_in_transaction_context_push {
|
||||||
|
let instruction_context = InstructionContext {
|
||||||
|
nesting_level: self.instruction_stack.len(),
|
||||||
|
instruction_accounts_lamport_sum: callee_instruction_accounts_lamport_sum,
|
||||||
|
program_accounts: program_accounts.to_vec(),
|
||||||
|
instruction_accounts: instruction_accounts.to_vec(),
|
||||||
|
instruction_data: instruction_data.to_vec(),
|
||||||
|
};
|
||||||
|
instruction_trace.push(instruction_context);
|
||||||
|
}
|
||||||
|
instruction_trace.len().saturating_sub(1)
|
||||||
|
} else {
|
||||||
|
return Err(InstructionError::CallDepth);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
if self.instruction_stack.len() >= self.instruction_context_capacity {
|
if self.instruction_stack.len() >= self.instruction_context_capacity {
|
||||||
return Err(InstructionError::CallDepth);
|
return Err(InstructionError::CallDepth);
|
||||||
|
@ -218,8 +251,34 @@ impl TransactionContext {
|
||||||
if self.instruction_stack.is_empty() {
|
if self.instruction_stack.is_empty() {
|
||||||
return Err(InstructionError::CallDepth);
|
return Err(InstructionError::CallDepth);
|
||||||
}
|
}
|
||||||
|
// Verify (before we pop) that the total sum of all lamports in this instruction did not change
|
||||||
|
let detected_an_unbalanced_instruction = if self
|
||||||
|
.is_early_verification_of_account_modifications_enabled()
|
||||||
|
{
|
||||||
|
self.get_current_instruction_context()
|
||||||
|
.and_then(|instruction_context| {
|
||||||
|
// Verify all executable accounts have no outstanding refs
|
||||||
|
for account_index in instruction_context.program_accounts.iter() {
|
||||||
|
self.get_account_at_index(*account_index)?
|
||||||
|
.try_borrow_mut()
|
||||||
|
.map_err(|_| InstructionError::AccountBorrowOutstanding)?;
|
||||||
|
}
|
||||||
|
self.instruction_accounts_lamport_sum(&instruction_context.instruction_accounts)
|
||||||
|
.map(|instruction_accounts_lamport_sum| {
|
||||||
|
instruction_context.instruction_accounts_lamport_sum
|
||||||
|
!= instruction_accounts_lamport_sum
|
||||||
|
})
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Ok(false)
|
||||||
|
};
|
||||||
|
// Always pop, even if we `detected_an_unbalanced_instruction`
|
||||||
self.instruction_stack.pop();
|
self.instruction_stack.pop();
|
||||||
Ok(())
|
if detected_an_unbalanced_instruction? {
|
||||||
|
Err(InstructionError::UnbalancedInstruction)
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the return data of the current InstructionContext or any above
|
/// Gets the return data of the current InstructionContext or any above
|
||||||
|
@ -251,6 +310,40 @@ impl TransactionContext {
|
||||||
pub fn get_instruction_trace(&self) -> &InstructionTrace {
|
pub fn get_instruction_trace(&self) -> &InstructionTrace {
|
||||||
&self.instruction_trace
|
&self.instruction_trace
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Calculates the sum of all lamports within an instruction
|
||||||
|
pub fn instruction_accounts_lamport_sum(
|
||||||
|
&self,
|
||||||
|
instruction_accounts: &[InstructionAccount],
|
||||||
|
) -> Result<u128, InstructionError> {
|
||||||
|
if !self.is_early_verification_of_account_modifications_enabled() {
|
||||||
|
return Ok(0);
|
||||||
|
}
|
||||||
|
let mut instruction_accounts_lamport_sum: u128 = 0;
|
||||||
|
for (instruction_account_index, instruction_account) in
|
||||||
|
instruction_accounts.iter().enumerate()
|
||||||
|
{
|
||||||
|
if instruction_account_index != instruction_account.index_in_callee {
|
||||||
|
continue; // Skip duplicate account
|
||||||
|
}
|
||||||
|
instruction_accounts_lamport_sum = (self
|
||||||
|
.get_account_at_index(instruction_account.index_in_transaction)?
|
||||||
|
.try_borrow()
|
||||||
|
.map_err(|_| InstructionError::AccountBorrowOutstanding)?
|
||||||
|
.lamports() as u128)
|
||||||
|
.checked_add(instruction_accounts_lamport_sum)
|
||||||
|
.ok_or(InstructionError::ArithmeticOverflow)?;
|
||||||
|
}
|
||||||
|
Ok(instruction_accounts_lamport_sum)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the accounts resize delta
|
||||||
|
pub fn accounts_resize_delta(&self) -> Result<i64, InstructionError> {
|
||||||
|
self.accounts_resize_delta
|
||||||
|
.try_borrow()
|
||||||
|
.map_err(|_| InstructionError::GenericError)
|
||||||
|
.map(|value_ref| *value_ref)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return data at the end of a transaction
|
/// Return data at the end of a transaction
|
||||||
|
@ -269,6 +362,7 @@ pub type InstructionTrace = Vec<Vec<InstructionContext>>;
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct InstructionContext {
|
pub struct InstructionContext {
|
||||||
nesting_level: usize,
|
nesting_level: usize,
|
||||||
|
instruction_accounts_lamport_sum: u128,
|
||||||
program_accounts: Vec<usize>,
|
program_accounts: Vec<usize>,
|
||||||
instruction_accounts: Vec<InstructionAccount>,
|
instruction_accounts: Vec<InstructionAccount>,
|
||||||
instruction_data: Vec<u8>,
|
instruction_data: Vec<u8>,
|
||||||
|
@ -278,12 +372,14 @@ impl InstructionContext {
|
||||||
/// New
|
/// New
|
||||||
pub fn new(
|
pub fn new(
|
||||||
nesting_level: usize,
|
nesting_level: usize,
|
||||||
|
instruction_accounts_lamport_sum: u128,
|
||||||
program_accounts: &[usize],
|
program_accounts: &[usize],
|
||||||
instruction_accounts: &[InstructionAccount],
|
instruction_accounts: &[InstructionAccount],
|
||||||
instruction_data: &[u8],
|
instruction_data: &[u8],
|
||||||
) -> Self {
|
) -> Self {
|
||||||
InstructionContext {
|
InstructionContext {
|
||||||
nesting_level,
|
nesting_level,
|
||||||
|
instruction_accounts_lamport_sum,
|
||||||
program_accounts: program_accounts.to_vec(),
|
program_accounts: program_accounts.to_vec(),
|
||||||
instruction_accounts: instruction_accounts.to_vec(),
|
instruction_accounts: instruction_accounts.to_vec(),
|
||||||
instruction_data: instruction_data.to_vec(),
|
instruction_data: instruction_data.to_vec(),
|
||||||
|
@ -540,6 +636,32 @@ impl<'a> BorrowedAccount<'a> {
|
||||||
|
|
||||||
/// Assignes the owner of this account (transaction wide)
|
/// Assignes the owner of this account (transaction wide)
|
||||||
pub fn set_owner(&mut self, pubkey: &[u8]) -> Result<(), InstructionError> {
|
pub fn set_owner(&mut self, pubkey: &[u8]) -> Result<(), InstructionError> {
|
||||||
|
if self
|
||||||
|
.transaction_context
|
||||||
|
.is_early_verification_of_account_modifications_enabled()
|
||||||
|
{
|
||||||
|
// Only the owner can assign a new owner
|
||||||
|
if !self.is_owned_by_current_program() {
|
||||||
|
return Err(InstructionError::ModifiedProgramId);
|
||||||
|
}
|
||||||
|
// and only if the account is writable
|
||||||
|
if !self.is_writable() {
|
||||||
|
return Err(InstructionError::ModifiedProgramId);
|
||||||
|
}
|
||||||
|
// and only if the account is not executable
|
||||||
|
if self.is_executable() {
|
||||||
|
return Err(InstructionError::ModifiedProgramId);
|
||||||
|
}
|
||||||
|
// and only if the data is zero-initialized or empty
|
||||||
|
if !is_zeroed(self.get_data()) {
|
||||||
|
return Err(InstructionError::ModifiedProgramId);
|
||||||
|
}
|
||||||
|
// don't touch the account if the owner does not change
|
||||||
|
if self.get_owner().to_bytes() == pubkey {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
self.touch()?;
|
||||||
|
}
|
||||||
self.account.copy_into_owner_from_slice(pubkey);
|
self.account.copy_into_owner_from_slice(pubkey);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -551,6 +673,28 @@ impl<'a> BorrowedAccount<'a> {
|
||||||
|
|
||||||
/// Overwrites the number of lamports of this account (transaction wide)
|
/// Overwrites the number of lamports of this account (transaction wide)
|
||||||
pub fn set_lamports(&mut self, lamports: u64) -> Result<(), InstructionError> {
|
pub fn set_lamports(&mut self, lamports: u64) -> Result<(), InstructionError> {
|
||||||
|
if self
|
||||||
|
.transaction_context
|
||||||
|
.is_early_verification_of_account_modifications_enabled()
|
||||||
|
{
|
||||||
|
// An account not owned by the program cannot have its balance decrease
|
||||||
|
if !self.is_owned_by_current_program() && lamports < self.get_lamports() {
|
||||||
|
return Err(InstructionError::ExternalAccountLamportSpend);
|
||||||
|
}
|
||||||
|
// The balance of read-only may not change
|
||||||
|
if !self.is_writable() {
|
||||||
|
return Err(InstructionError::ReadonlyLamportChange);
|
||||||
|
}
|
||||||
|
// The balance of executable accounts may not change
|
||||||
|
if self.is_executable() {
|
||||||
|
return Err(InstructionError::ExecutableLamportChange);
|
||||||
|
}
|
||||||
|
// don't touch the account if the lamports do not change
|
||||||
|
if self.get_lamports() == lamports {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
self.touch()?;
|
||||||
|
}
|
||||||
self.account.set_lamports(lamports);
|
self.account.set_lamports(lamports);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -580,16 +724,25 @@ impl<'a> BorrowedAccount<'a> {
|
||||||
|
|
||||||
/// Returns a writable slice of the account data (transaction wide)
|
/// Returns a writable slice of the account data (transaction wide)
|
||||||
pub fn get_data_mut(&mut self) -> Result<&mut [u8], InstructionError> {
|
pub fn get_data_mut(&mut self) -> Result<&mut [u8], InstructionError> {
|
||||||
|
self.can_data_be_changed()?;
|
||||||
|
self.touch()?;
|
||||||
Ok(self.account.data_as_mut_slice())
|
Ok(self.account.data_as_mut_slice())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Overwrites the account data and size (transaction wide)
|
/// Overwrites the account data and size (transaction wide)
|
||||||
pub fn set_data(&mut self, data: &[u8]) -> Result<(), InstructionError> {
|
pub fn set_data(&mut self, data: &[u8]) -> Result<(), InstructionError> {
|
||||||
|
self.can_data_be_resized(data.len())?;
|
||||||
|
self.can_data_be_changed()?;
|
||||||
|
self.touch()?;
|
||||||
if data.len() == self.account.data().len() {
|
if data.len() == self.account.data().len() {
|
||||||
self.account.data_as_mut_slice().copy_from_slice(data);
|
self.account.data_as_mut_slice().copy_from_slice(data);
|
||||||
} else {
|
} else {
|
||||||
let mut total_resize_delta = self.transaction_context.total_resize_delta.borrow_mut();
|
let mut accounts_resize_delta = self
|
||||||
*total_resize_delta = total_resize_delta
|
.transaction_context
|
||||||
|
.accounts_resize_delta
|
||||||
|
.try_borrow_mut()
|
||||||
|
.map_err(|_| InstructionError::GenericError)?;
|
||||||
|
*accounts_resize_delta = accounts_resize_delta
|
||||||
.saturating_add((data.len() as i64).saturating_sub(self.get_data().len() as i64));
|
.saturating_add((data.len() as i64).saturating_sub(self.get_data().len() as i64));
|
||||||
self.account.set_data_from_slice(data);
|
self.account.set_data_from_slice(data);
|
||||||
}
|
}
|
||||||
|
@ -600,8 +753,19 @@ impl<'a> BorrowedAccount<'a> {
|
||||||
///
|
///
|
||||||
/// Fills it with zeros at the end if is extended or truncates at the end otherwise.
|
/// Fills it with zeros at the end if is extended or truncates at the end otherwise.
|
||||||
pub fn set_data_length(&mut self, new_length: usize) -> Result<(), InstructionError> {
|
pub fn set_data_length(&mut self, new_length: usize) -> Result<(), InstructionError> {
|
||||||
let mut total_resize_delta = self.transaction_context.total_resize_delta.borrow_mut();
|
self.can_data_be_resized(new_length)?;
|
||||||
*total_resize_delta = total_resize_delta
|
self.can_data_be_changed()?;
|
||||||
|
// don't touch the account if the length does not change
|
||||||
|
if self.get_data().len() == new_length {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
self.touch()?;
|
||||||
|
let mut accounts_resize_delta = self
|
||||||
|
.transaction_context
|
||||||
|
.accounts_resize_delta
|
||||||
|
.try_borrow_mut()
|
||||||
|
.map_err(|_| InstructionError::GenericError)?;
|
||||||
|
*accounts_resize_delta = accounts_resize_delta
|
||||||
.saturating_add((new_length as i64).saturating_sub(self.get_data().len() as i64));
|
.saturating_add((new_length as i64).saturating_sub(self.get_data().len() as i64));
|
||||||
self.account.data_mut().resize(new_length, 0);
|
self.account.data_mut().resize(new_length, 0);
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -616,7 +780,7 @@ impl<'a> BorrowedAccount<'a> {
|
||||||
|
|
||||||
/// Serializes a state into the account data
|
/// Serializes a state into the account data
|
||||||
pub fn set_state<T: serde::Serialize>(&mut self, state: &T) -> Result<(), InstructionError> {
|
pub fn set_state<T: serde::Serialize>(&mut self, state: &T) -> Result<(), InstructionError> {
|
||||||
let data = self.account.data_as_mut_slice();
|
let data = self.get_data_mut()?;
|
||||||
let serialized_size =
|
let serialized_size =
|
||||||
bincode::serialized_size(state).map_err(|_| InstructionError::GenericError)?;
|
bincode::serialized_size(state).map_err(|_| InstructionError::GenericError)?;
|
||||||
if serialized_size > data.len() as u64 {
|
if serialized_size > data.len() as u64 {
|
||||||
|
@ -633,6 +797,29 @@ impl<'a> BorrowedAccount<'a> {
|
||||||
|
|
||||||
/// Configures whether this account is executable (transaction wide)
|
/// Configures whether this account is executable (transaction wide)
|
||||||
pub fn set_executable(&mut self, is_executable: bool) -> Result<(), InstructionError> {
|
pub fn set_executable(&mut self, is_executable: bool) -> Result<(), InstructionError> {
|
||||||
|
if let Some(rent) = self.transaction_context.rent {
|
||||||
|
// To become executable an account must be rent exempt
|
||||||
|
if !rent.is_exempt(self.get_lamports(), self.get_data().len()) {
|
||||||
|
return Err(InstructionError::ExecutableAccountNotRentExempt);
|
||||||
|
}
|
||||||
|
// Only the owner can set the executable flag
|
||||||
|
if !self.is_owned_by_current_program() {
|
||||||
|
return Err(InstructionError::ExecutableModified);
|
||||||
|
}
|
||||||
|
// and only if the account is writable
|
||||||
|
if !self.is_writable() {
|
||||||
|
return Err(InstructionError::ExecutableModified);
|
||||||
|
}
|
||||||
|
// one can not clear the executable flag
|
||||||
|
if self.is_executable() && !is_executable {
|
||||||
|
return Err(InstructionError::ExecutableModified);
|
||||||
|
}
|
||||||
|
// don't touch the account if the executable flag does not change
|
||||||
|
if self.is_executable() == is_executable {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
self.touch()?;
|
||||||
|
}
|
||||||
self.account.set_executable(is_executable);
|
self.account.set_executable(is_executable);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -667,6 +854,72 @@ impl<'a> BorrowedAccount<'a> {
|
||||||
)
|
)
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns true if the owner of this account is the current `InstructionContext`s last program (instruction wide)
|
||||||
|
pub fn is_owned_by_current_program(&self) -> bool {
|
||||||
|
self.instruction_context
|
||||||
|
.get_last_program_key(self.transaction_context)
|
||||||
|
.map(|key| key == self.get_owner())
|
||||||
|
.unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns an error if the account data can not be mutated by the current program
|
||||||
|
pub fn can_data_be_changed(&self) -> Result<(), InstructionError> {
|
||||||
|
if !self
|
||||||
|
.transaction_context
|
||||||
|
.is_early_verification_of_account_modifications_enabled()
|
||||||
|
{
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
// Only non-executable accounts data can be changed
|
||||||
|
if self.is_executable() {
|
||||||
|
return Err(InstructionError::ExecutableDataModified);
|
||||||
|
}
|
||||||
|
// and only if the account is writable
|
||||||
|
if !self.is_writable() {
|
||||||
|
return Err(InstructionError::ReadonlyDataModified);
|
||||||
|
}
|
||||||
|
// and only if we are the owner
|
||||||
|
if !self.is_owned_by_current_program() {
|
||||||
|
return Err(InstructionError::ExternalAccountDataModified);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns an error if the account data can not be resized to the given length
|
||||||
|
pub fn can_data_be_resized(&self, new_length: usize) -> Result<(), InstructionError> {
|
||||||
|
if !self
|
||||||
|
.transaction_context
|
||||||
|
.is_early_verification_of_account_modifications_enabled()
|
||||||
|
{
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
// Only the owner can change the length of the data
|
||||||
|
if new_length != self.get_data().len() && !self.is_owned_by_current_program() {
|
||||||
|
return Err(InstructionError::AccountDataSizeChanged);
|
||||||
|
}
|
||||||
|
// The new length can not exceed the maximum permitted length
|
||||||
|
if new_length > MAX_PERMITTED_DATA_LENGTH as usize {
|
||||||
|
return Err(InstructionError::InvalidRealloc);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn touch(&self) -> Result<(), InstructionError> {
|
||||||
|
if self
|
||||||
|
.transaction_context
|
||||||
|
.is_early_verification_of_account_modifications_enabled()
|
||||||
|
{
|
||||||
|
*self
|
||||||
|
.transaction_context
|
||||||
|
.account_touched_flags
|
||||||
|
.try_borrow_mut()
|
||||||
|
.map_err(|_| InstructionError::GenericError)?
|
||||||
|
.get_mut(self.index_in_transaction)
|
||||||
|
.ok_or(InstructionError::NotEnoughAccountKeys)? = true;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Everything that needs to be recorded from a TransactionContext after execution
|
/// Everything that needs to be recorded from a TransactionContext after execution
|
||||||
|
@ -674,12 +927,38 @@ pub struct ExecutionRecord {
|
||||||
pub accounts: Vec<TransactionAccount>,
|
pub accounts: Vec<TransactionAccount>,
|
||||||
pub instruction_trace: InstructionTrace,
|
pub instruction_trace: InstructionTrace,
|
||||||
pub return_data: TransactionReturnData,
|
pub return_data: TransactionReturnData,
|
||||||
pub total_resize_delta: i64,
|
pub changed_account_count: u64,
|
||||||
|
pub total_size_of_all_accounts: u64,
|
||||||
|
pub total_size_of_touched_accounts: u64,
|
||||||
|
pub accounts_resize_delta: i64,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Used by the bank in the runtime to write back the processed accounts and recorded instructions
|
/// Used by the bank in the runtime to write back the processed accounts and recorded instructions
|
||||||
impl From<TransactionContext> for ExecutionRecord {
|
impl From<TransactionContext> for ExecutionRecord {
|
||||||
fn from(context: TransactionContext) -> Self {
|
fn from(context: TransactionContext) -> Self {
|
||||||
|
let mut changed_account_count = 0u64;
|
||||||
|
let mut total_size_of_all_accounts = 0u64;
|
||||||
|
let mut total_size_of_touched_accounts = 0u64;
|
||||||
|
let account_touched_flags = context
|
||||||
|
.account_touched_flags
|
||||||
|
.try_borrow()
|
||||||
|
.expect("borrowing transaction_context.account_touched_flags failed");
|
||||||
|
for (index_in_transaction, was_touched) in account_touched_flags.iter().enumerate() {
|
||||||
|
let account_data_size = context
|
||||||
|
.get_account_at_index(index_in_transaction)
|
||||||
|
.expect("index_in_transaction out of bounds")
|
||||||
|
.try_borrow()
|
||||||
|
.expect("borrowing a transaction_context.account failed")
|
||||||
|
.data()
|
||||||
|
.len() as u64;
|
||||||
|
total_size_of_all_accounts =
|
||||||
|
total_size_of_all_accounts.saturating_add(account_data_size);
|
||||||
|
if *was_touched {
|
||||||
|
changed_account_count = changed_account_count.saturating_add(1);
|
||||||
|
total_size_of_touched_accounts =
|
||||||
|
total_size_of_touched_accounts.saturating_add(account_data_size);
|
||||||
|
}
|
||||||
|
}
|
||||||
Self {
|
Self {
|
||||||
accounts: Vec::from(Pin::into_inner(context.account_keys))
|
accounts: Vec::from(Pin::into_inner(context.account_keys))
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
@ -691,7 +970,22 @@ impl From<TransactionContext> for ExecutionRecord {
|
||||||
.collect(),
|
.collect(),
|
||||||
instruction_trace: context.instruction_trace,
|
instruction_trace: context.instruction_trace,
|
||||||
return_data: context.return_data,
|
return_data: context.return_data,
|
||||||
total_resize_delta: RefCell::into_inner(context.total_resize_delta),
|
changed_account_count,
|
||||||
|
total_size_of_all_accounts,
|
||||||
|
total_size_of_touched_accounts,
|
||||||
|
accounts_resize_delta: RefCell::into_inner(context.accounts_resize_delta),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_zeroed(buf: &[u8]) -> bool {
|
||||||
|
const ZEROS_LEN: usize = 1024;
|
||||||
|
const ZEROS: [u8; ZEROS_LEN] = [0; ZEROS_LEN];
|
||||||
|
let mut chunks = buf.chunks_exact(ZEROS_LEN);
|
||||||
|
|
||||||
|
#[allow(clippy::indexing_slicing)]
|
||||||
|
{
|
||||||
|
chunks.all(|chunk| chunk == &ZEROS[..])
|
||||||
|
&& chunks.remainder() == &ZEROS[..chunks.remainder().len()]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue