Remove credit-only account handling (#6726)
* Renaming - credit-only/credit-debit to read-only/read-write - debitable to writable * Remove credit handling, making credit-only accounts read-only * Update programs to remove deprecated credit-only account designation * Use readonly and writable instead of underscored types
This commit is contained in:
@ -1,9 +1,7 @@
|
||||
use crate::native_loader;
|
||||
use crate::system_instruction_processor;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use solana_sdk::account::{
|
||||
create_keyed_credit_only_accounts, Account, KeyedAccount, LamportCredit,
|
||||
};
|
||||
use solana_sdk::account::{create_keyed_readonly_accounts, Account, KeyedAccount};
|
||||
use solana_sdk::instruction::{CompiledInstruction, InstructionError};
|
||||
use solana_sdk::instruction_processor_utils;
|
||||
use solana_sdk::loader_instruction::LoaderInstruction;
|
||||
@ -59,7 +57,7 @@ fn get_subset_unchecked_mut<'a, T>(
|
||||
}
|
||||
|
||||
pub fn verify_instruction(
|
||||
is_debitable: bool,
|
||||
is_writable: bool,
|
||||
program_id: &Pubkey,
|
||||
pre: &Account,
|
||||
post: &Account,
|
||||
@ -67,10 +65,10 @@ pub fn verify_instruction(
|
||||
// Verify the transaction
|
||||
|
||||
// Only the owner of the account may change owner and
|
||||
// only if the account is credit-debit and
|
||||
// only if the account is writable and
|
||||
// only if the data is zero-initialized or empty
|
||||
if pre.owner != post.owner
|
||||
&& (!is_debitable // line coverage used to get branch coverage
|
||||
&& (!is_writable // line coverage used to get branch coverage
|
||||
|| *program_id != pre.owner // line coverage used to get branch coverage
|
||||
|| !is_zeroed(&post.data))
|
||||
{
|
||||
@ -84,11 +82,11 @@ pub fn verify_instruction(
|
||||
return Err(InstructionError::ExternalAccountLamportSpend);
|
||||
}
|
||||
|
||||
// The balance of credit-only accounts may only increase.
|
||||
if !is_debitable // line coverage used to get branch coverage
|
||||
&& pre.lamports > post.lamports
|
||||
// The balance of read-only accounts may not change.
|
||||
if !is_writable // line coverage used to get branch coverage
|
||||
&& pre.lamports != post.lamports
|
||||
{
|
||||
return Err(InstructionError::CreditOnlyLamportSpend);
|
||||
return Err(InstructionError::ReadonlyLamportChange);
|
||||
}
|
||||
|
||||
// Only the system program can change the size of the data
|
||||
@ -126,16 +124,16 @@ pub fn verify_instruction(
|
||||
return Err(InstructionError::ExternalAccountDataModified);
|
||||
}
|
||||
|
||||
// Credit-only account data may not change.
|
||||
if !is_debitable // line coverage used to get branch coverage
|
||||
// Read-only account data may not change.
|
||||
if !is_writable // line coverage used to get branch coverage
|
||||
&& data_changed()
|
||||
{
|
||||
return Err(InstructionError::CreditOnlyDataModified);
|
||||
return Err(InstructionError::ReadonlyDataModified);
|
||||
}
|
||||
|
||||
// executable is one-way (false->true) and only the account owner may set it.
|
||||
if pre.executable != post.executable
|
||||
&& (!is_debitable // line coverage used to get branch coverage
|
||||
&& (!is_writable // line coverage used to get branch coverage
|
||||
|| pre.executable // line coverage used to get branch coverage
|
||||
|| *program_id != pre.owner)
|
||||
{
|
||||
@ -227,26 +225,26 @@ impl MessageProcessor {
|
||||
&mut loader_ix_data,
|
||||
);
|
||||
|
||||
let mut keyed_accounts = create_keyed_credit_only_accounts(executable_accounts);
|
||||
let mut keyed_accounts = create_keyed_readonly_accounts(executable_accounts);
|
||||
let mut keyed_accounts2: Vec<_> = instruction
|
||||
.accounts
|
||||
.iter()
|
||||
.map(|&index| {
|
||||
let index = index as usize;
|
||||
let key = &message.account_keys[index];
|
||||
let is_debitable = message.is_debitable(index);
|
||||
let is_writable = message.is_writable(index);
|
||||
(
|
||||
key,
|
||||
index < message.header.num_required_signatures as usize,
|
||||
is_debitable,
|
||||
is_writable,
|
||||
)
|
||||
})
|
||||
.zip(program_accounts.iter_mut())
|
||||
.map(|((key, is_signer, is_debitable), account)| {
|
||||
if is_debitable {
|
||||
.map(|((key, is_signer, is_writable), account)| {
|
||||
if is_writable {
|
||||
KeyedAccount::new(key, is_signer, account)
|
||||
} else {
|
||||
KeyedAccount::new_credit_only(key, is_signer, account)
|
||||
KeyedAccount::new_readonly(key, is_signer, account)
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
@ -282,7 +280,6 @@ impl MessageProcessor {
|
||||
instruction: &CompiledInstruction,
|
||||
executable_accounts: &mut [(Pubkey, Account)],
|
||||
program_accounts: &mut [&mut Account],
|
||||
credits: &mut [&mut LamportCredit],
|
||||
) -> Result<(), InstructionError> {
|
||||
let program_id = instruction.program_id(&message.account_keys);
|
||||
assert_eq!(instruction.accounts.len(), program_accounts.len());
|
||||
@ -300,21 +297,17 @@ impl MessageProcessor {
|
||||
|
||||
self.process_instruction(message, instruction, executable_accounts, program_accounts)?;
|
||||
// Verify the instruction
|
||||
for (pre_account, (i, post_account, is_debitable)) in
|
||||
for (pre_account, (post_account, is_writable)) in
|
||||
pre_accounts
|
||||
.iter()
|
||||
.zip(program_accounts.iter().enumerate().map(|(i, account)| {
|
||||
(
|
||||
i,
|
||||
account,
|
||||
message.is_debitable(instruction.accounts[i] as usize),
|
||||
message.is_writable(instruction.accounts[i] as usize),
|
||||
)
|
||||
}))
|
||||
{
|
||||
verify_instruction(is_debitable, &program_id, pre_account, post_account)?;
|
||||
if !is_debitable {
|
||||
*credits[i] += post_account.lamports - pre_account.lamports;
|
||||
}
|
||||
verify_instruction(is_writable, &program_id, pre_account, post_account)?;
|
||||
}
|
||||
// The total sum of all the lamports in all the accounts cannot change.
|
||||
let post_total: u128 = program_accounts
|
||||
@ -336,7 +329,6 @@ impl MessageProcessor {
|
||||
message: &Message,
|
||||
loaders: &mut [Vec<(Pubkey, Account)>],
|
||||
accounts: &mut [Account],
|
||||
credits: &mut [LamportCredit],
|
||||
) -> Result<(), TransactionError> {
|
||||
for (instruction_index, instruction) in message.instructions.iter().enumerate() {
|
||||
let executable_index = message
|
||||
@ -348,14 +340,11 @@ impl MessageProcessor {
|
||||
// TODO: `get_subset_unchecked_mut` panics on an index out of bounds if an executable
|
||||
// account is also included as a regular account for an instruction, because the
|
||||
// executable account is not passed in as part of the accounts slice
|
||||
let mut instruction_credits = get_subset_unchecked_mut(credits, &instruction.accounts)
|
||||
.map_err(|err| TransactionError::InstructionError(instruction_index as u8, err))?;
|
||||
self.execute_instruction(
|
||||
message,
|
||||
instruction,
|
||||
executable_accounts,
|
||||
&mut program_accounts,
|
||||
&mut instruction_credits,
|
||||
)
|
||||
.map_err(|err| TransactionError::InstructionError(instruction_index as u8, err))?;
|
||||
}
|
||||
@ -440,10 +429,10 @@ mod tests {
|
||||
ix: &Pubkey,
|
||||
pre: &Pubkey,
|
||||
post: &Pubkey,
|
||||
is_debitable: bool,
|
||||
is_writable: bool,
|
||||
) -> Result<(), InstructionError> {
|
||||
verify_instruction(
|
||||
is_debitable,
|
||||
is_writable,
|
||||
&ix,
|
||||
&Account::new(0, 0, pre),
|
||||
&Account::new(0, 0, post),
|
||||
@ -465,9 +454,14 @@ mod tests {
|
||||
"system program should be able to change the account owner"
|
||||
);
|
||||
assert_eq!(
|
||||
change_owner(&system_program_id, &system_program_id, &alice_program_id, false),
|
||||
change_owner(
|
||||
&system_program_id,
|
||||
&system_program_id,
|
||||
&alice_program_id,
|
||||
false
|
||||
),
|
||||
Err(InstructionError::ModifiedProgramId),
|
||||
"system program should not be able to change the account owner of a credit only account"
|
||||
"system program should not be able to change the account owner of a read-only account"
|
||||
);
|
||||
assert_eq!(
|
||||
change_owner(
|
||||
@ -517,7 +511,7 @@ mod tests {
|
||||
fn test_verify_instruction_change_executable() {
|
||||
let owner = Pubkey::new_rand();
|
||||
let change_executable = |program_id: &Pubkey,
|
||||
is_debitable: bool,
|
||||
is_writable: bool,
|
||||
pre_executable: bool,
|
||||
post_executable: bool|
|
||||
-> Result<(), InstructionError> {
|
||||
@ -532,7 +526,7 @@ mod tests {
|
||||
executable: post_executable,
|
||||
..Account::default()
|
||||
};
|
||||
verify_instruction(is_debitable, &program_id, &pre, &post)
|
||||
verify_instruction(is_writable, &program_id, &pre, &post)
|
||||
};
|
||||
|
||||
let mallory_program_id = Pubkey::new_rand();
|
||||
@ -551,7 +545,7 @@ mod tests {
|
||||
assert_eq!(
|
||||
change_executable(&owner, false, false, true),
|
||||
Err(InstructionError::ExecutableModified),
|
||||
"system program can't modify executable of credit-only accounts"
|
||||
"system program can't modify executable of read-only accounts"
|
||||
);
|
||||
assert_eq!(
|
||||
change_executable(&owner, true, true, false),
|
||||
@ -596,10 +590,10 @@ mod tests {
|
||||
let alice_program_id = Pubkey::new_rand();
|
||||
|
||||
let change_data =
|
||||
|program_id: &Pubkey, is_debitable: bool| -> Result<(), InstructionError> {
|
||||
|program_id: &Pubkey, is_writable: bool| -> Result<(), InstructionError> {
|
||||
let pre = Account::new_data(0, &[0], &alice_program_id).unwrap();
|
||||
let post = Account::new_data(0, &[42], &alice_program_id).unwrap();
|
||||
verify_instruction(is_debitable, &program_id, &pre, &post)
|
||||
verify_instruction(is_writable, &program_id, &pre, &post)
|
||||
};
|
||||
|
||||
let mallory_program_id = Pubkey::new_rand();
|
||||
@ -617,7 +611,7 @@ mod tests {
|
||||
|
||||
assert_eq!(
|
||||
change_data(&alice_program_id, false),
|
||||
Err(InstructionError::CreditOnlyDataModified),
|
||||
Err(InstructionError::ReadonlyDataModified),
|
||||
"alice isn't allowed to touch a CO account"
|
||||
);
|
||||
}
|
||||
@ -670,7 +664,7 @@ mod tests {
|
||||
);
|
||||
assert_eq!(
|
||||
verify_instruction(false, &alice_program_id, &pre, &post,),
|
||||
Err(InstructionError::CreditOnlyLamportSpend),
|
||||
Err(InstructionError::ReadonlyLamportChange),
|
||||
"debit should fail, even if owning program"
|
||||
);
|
||||
|
||||
@ -715,12 +709,12 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_process_message_credit_only_handling() {
|
||||
fn test_process_message_readonly_handling() {
|
||||
#[derive(Serialize, Deserialize)]
|
||||
enum MockSystemInstruction {
|
||||
Correct { lamports: u64 },
|
||||
AttemptDebit { lamports: u64 },
|
||||
Misbehave { lamports: u64 },
|
||||
Correct,
|
||||
AttemptCredit { lamports: u64 },
|
||||
AttemptDataChange { data: u8 },
|
||||
}
|
||||
|
||||
fn mock_system_process_instruction(
|
||||
@ -730,20 +724,15 @@ mod tests {
|
||||
) -> Result<(), InstructionError> {
|
||||
if let Ok(instruction) = bincode::deserialize(data) {
|
||||
match instruction {
|
||||
MockSystemInstruction::Correct { lamports } => {
|
||||
MockSystemInstruction::Correct => Ok(()),
|
||||
MockSystemInstruction::AttemptCredit { lamports } => {
|
||||
keyed_accounts[0].account.lamports -= lamports;
|
||||
keyed_accounts[1].account.lamports += lamports;
|
||||
Ok(())
|
||||
}
|
||||
MockSystemInstruction::AttemptDebit { lamports } => {
|
||||
keyed_accounts[0].account.lamports += lamports;
|
||||
keyed_accounts[1].account.lamports -= lamports;
|
||||
Ok(())
|
||||
}
|
||||
// Credit a credit-only account for more lamports than debited
|
||||
MockSystemInstruction::Misbehave { lamports } => {
|
||||
keyed_accounts[0].account.lamports -= lamports;
|
||||
keyed_accounts[1].account.lamports = 2 * lamports;
|
||||
// Change data in a read-only account
|
||||
MockSystemInstruction::AttemptDataChange { data } => {
|
||||
keyed_accounts[1].account.data = vec![data];
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@ -771,53 +760,46 @@ mod tests {
|
||||
let to_pubkey = Pubkey::new_rand();
|
||||
let account_metas = vec![
|
||||
AccountMeta::new(from_pubkey, true),
|
||||
AccountMeta::new_credit_only(to_pubkey, false),
|
||||
AccountMeta::new_readonly(to_pubkey, false),
|
||||
];
|
||||
let message = Message::new(vec![Instruction::new(
|
||||
mock_system_program_id,
|
||||
&MockSystemInstruction::Correct { lamports: 50 },
|
||||
&MockSystemInstruction::Correct,
|
||||
account_metas.clone(),
|
||||
)]);
|
||||
let mut deltas = vec![0, 0];
|
||||
|
||||
let result =
|
||||
message_processor.process_message(&message, &mut loaders, &mut accounts, &mut deltas);
|
||||
let result = message_processor.process_message(&message, &mut loaders, &mut accounts);
|
||||
assert_eq!(result, Ok(()));
|
||||
assert_eq!(accounts[0].lamports, 50);
|
||||
assert_eq!(accounts[1].lamports, 50);
|
||||
assert_eq!(deltas, vec![0, 50]);
|
||||
assert_eq!(accounts[0].lamports, 100);
|
||||
assert_eq!(accounts[1].lamports, 0);
|
||||
|
||||
let message = Message::new(vec![Instruction::new(
|
||||
mock_system_program_id,
|
||||
&MockSystemInstruction::AttemptDebit { lamports: 50 },
|
||||
&MockSystemInstruction::AttemptCredit { lamports: 50 },
|
||||
account_metas.clone(),
|
||||
)]);
|
||||
let mut deltas = vec![0, 0];
|
||||
|
||||
let result =
|
||||
message_processor.process_message(&message, &mut loaders, &mut accounts, &mut deltas);
|
||||
let result = message_processor.process_message(&message, &mut loaders, &mut accounts);
|
||||
assert_eq!(
|
||||
result,
|
||||
Err(TransactionError::InstructionError(
|
||||
0,
|
||||
InstructionError::CreditOnlyLamportSpend
|
||||
InstructionError::ReadonlyLamportChange
|
||||
))
|
||||
);
|
||||
|
||||
let message = Message::new(vec![Instruction::new(
|
||||
mock_system_program_id,
|
||||
&MockSystemInstruction::Misbehave { lamports: 50 },
|
||||
&MockSystemInstruction::AttemptDataChange { data: 50 },
|
||||
account_metas,
|
||||
)]);
|
||||
let mut deltas = vec![0, 0];
|
||||
|
||||
let result =
|
||||
message_processor.process_message(&message, &mut loaders, &mut accounts, &mut deltas);
|
||||
let result = message_processor.process_message(&message, &mut loaders, &mut accounts);
|
||||
assert_eq!(
|
||||
result,
|
||||
Err(TransactionError::InstructionError(
|
||||
0,
|
||||
InstructionError::UnbalancedInstruction
|
||||
InstructionError::ReadonlyDataModified
|
||||
))
|
||||
);
|
||||
}
|
||||
|
Reference in New Issue
Block a user