Refactor - Loader-v4 grows accounts with "truncate" not "write" (#33054)
* Moves initialization and growth of program accounts from "write" to "truncate" instruction in loader-v4. * Removes the parameter payer account from the truncate instruction.
This commit is contained in:
parent
e88d6c6130
commit
ef16b5e27d
|
@ -247,10 +247,44 @@ pub fn process_instruction_write(
|
|||
let authority_address = instruction_context
|
||||
.get_index_of_instruction_account_in_transaction(1)
|
||||
.and_then(|index| transaction_context.get_key_of_account_at_index(index))?;
|
||||
let mut payer = instruction_context
|
||||
.try_borrow_instruction_account(transaction_context, 2)
|
||||
.ok();
|
||||
let is_initialization = offset == 0 && program.get_data().is_empty();
|
||||
let state = check_program_account(
|
||||
&log_collector,
|
||||
instruction_context,
|
||||
&program,
|
||||
authority_address,
|
||||
)?;
|
||||
if !matches!(state.status, LoaderV4Status::Retracted) {
|
||||
ic_logger_msg!(log_collector, "Program is not retracted");
|
||||
return Err(InstructionError::InvalidArgument);
|
||||
}
|
||||
let end_offset = (offset as usize).saturating_add(bytes.len());
|
||||
program
|
||||
.get_data_mut()?
|
||||
.get_mut(
|
||||
LoaderV4State::program_data_offset().saturating_add(offset as usize)
|
||||
..LoaderV4State::program_data_offset().saturating_add(end_offset),
|
||||
)
|
||||
.ok_or_else(|| {
|
||||
ic_logger_msg!(log_collector, "Write out of bounds");
|
||||
InstructionError::AccountDataTooSmall
|
||||
})?
|
||||
.copy_from_slice(&bytes);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn process_instruction_truncate(
|
||||
invoke_context: &mut InvokeContext,
|
||||
new_size: u32,
|
||||
) -> Result<(), InstructionError> {
|
||||
let log_collector = invoke_context.get_log_collector();
|
||||
let transaction_context = &invoke_context.transaction_context;
|
||||
let instruction_context = transaction_context.get_current_instruction_context()?;
|
||||
let mut program = instruction_context.try_borrow_instruction_account(transaction_context, 0)?;
|
||||
let authority_address = instruction_context
|
||||
.get_index_of_instruction_account_in_transaction(1)
|
||||
.and_then(|index| transaction_context.get_key_of_account_at_index(index))?;
|
||||
let is_initialization =
|
||||
new_size > 0 && program.get_data().len() < LoaderV4State::program_data_offset();
|
||||
if is_initialization {
|
||||
if !loader_v4::check_id(program.get_owner()) {
|
||||
ic_logger_msg!(log_collector, "Program not owned by loader");
|
||||
|
@ -260,6 +294,10 @@ pub fn process_instruction_write(
|
|||
ic_logger_msg!(log_collector, "Program is not writeable");
|
||||
return Err(InstructionError::InvalidArgument);
|
||||
}
|
||||
if !program.is_signer() {
|
||||
ic_logger_msg!(log_collector, "Program did not sign");
|
||||
return Err(InstructionError::MissingRequiredSignature);
|
||||
}
|
||||
if !instruction_context.is_instruction_account_signer(1)? {
|
||||
ic_logger_msg!(log_collector, "Authority did not sign");
|
||||
return Err(InstructionError::MissingRequiredSignature);
|
||||
|
@ -276,30 +314,14 @@ pub fn process_instruction_write(
|
|||
return Err(InstructionError::InvalidArgument);
|
||||
}
|
||||
}
|
||||
if payer.is_some() && !instruction_context.is_instruction_account_signer(2)? {
|
||||
ic_logger_msg!(log_collector, "Payer did not sign");
|
||||
return Err(InstructionError::MissingRequiredSignature);
|
||||
}
|
||||
if payer.is_some() && !instruction_context.is_instruction_account_writable(2)? {
|
||||
ic_logger_msg!(log_collector, "Payer is not writeable");
|
||||
return Err(InstructionError::InvalidArgument);
|
||||
}
|
||||
let program_size = program
|
||||
.get_data()
|
||||
.len()
|
||||
.saturating_sub(LoaderV4State::program_data_offset());
|
||||
if offset as usize > program_size {
|
||||
ic_logger_msg!(log_collector, "Write out of bounds");
|
||||
return Err(InstructionError::AccountDataTooSmall);
|
||||
}
|
||||
let end_offset = (offset as usize).saturating_add(bytes.len());
|
||||
let rent = invoke_context.get_sysvar_cache().get_rent()?;
|
||||
let required_lamports =
|
||||
rent.minimum_balance(LoaderV4State::program_data_offset().saturating_add(end_offset));
|
||||
let transfer_lamports = required_lamports.saturating_sub(program.get_lamports());
|
||||
if transfer_lamports > 0 {
|
||||
payer = payer.filter(|payer| payer.get_lamports() >= transfer_lamports);
|
||||
if payer.is_none() {
|
||||
let required_lamports = if new_size == 0 {
|
||||
0
|
||||
} else {
|
||||
let rent = invoke_context.get_sysvar_cache().get_rent()?;
|
||||
rent.minimum_balance(LoaderV4State::program_data_offset().saturating_add(new_size as usize))
|
||||
};
|
||||
match program.get_lamports().cmp(&required_lamports) {
|
||||
std::cmp::Ordering::Less => {
|
||||
ic_logger_msg!(
|
||||
log_collector,
|
||||
"Insufficient lamports, {} are required",
|
||||
|
@ -307,75 +329,32 @@ pub fn process_instruction_write(
|
|||
);
|
||||
return Err(InstructionError::InsufficientFunds);
|
||||
}
|
||||
std::cmp::Ordering::Greater => {
|
||||
let mut recipient =
|
||||
instruction_context.try_borrow_instruction_account(transaction_context, 2)?;
|
||||
if !instruction_context.is_instruction_account_writable(2)? {
|
||||
ic_logger_msg!(log_collector, "Recipient is not writeable");
|
||||
return Err(InstructionError::InvalidArgument);
|
||||
}
|
||||
let lamports_to_receive = program.get_lamports().saturating_sub(required_lamports);
|
||||
program.checked_sub_lamports(lamports_to_receive)?;
|
||||
recipient.checked_add_lamports(lamports_to_receive)?;
|
||||
}
|
||||
std::cmp::Ordering::Equal => {}
|
||||
}
|
||||
if end_offset > program_size {
|
||||
program.set_data_length(LoaderV4State::program_data_offset().saturating_add(end_offset))?;
|
||||
}
|
||||
if let Some(mut payer) = payer {
|
||||
payer.checked_sub_lamports(transfer_lamports)?;
|
||||
program.checked_add_lamports(transfer_lamports)?;
|
||||
}
|
||||
if is_initialization {
|
||||
let state = get_state_mut(program.get_data_mut()?)?;
|
||||
state.slot = invoke_context.get_sysvar_cache().get_clock()?.slot;
|
||||
state.status = LoaderV4Status::Retracted;
|
||||
state.authority_address = *authority_address;
|
||||
}
|
||||
program
|
||||
.get_data_mut()?
|
||||
.get_mut(
|
||||
LoaderV4State::program_data_offset().saturating_add(offset as usize)
|
||||
..LoaderV4State::program_data_offset().saturating_add(end_offset),
|
||||
)
|
||||
.ok_or(InstructionError::AccountDataTooSmall)?
|
||||
.copy_from_slice(&bytes);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn process_instruction_truncate(
|
||||
invoke_context: &mut InvokeContext,
|
||||
offset: u32,
|
||||
) -> Result<(), InstructionError> {
|
||||
let log_collector = invoke_context.get_log_collector();
|
||||
let transaction_context = &invoke_context.transaction_context;
|
||||
let instruction_context = transaction_context.get_current_instruction_context()?;
|
||||
let mut program = instruction_context.try_borrow_instruction_account(transaction_context, 0)?;
|
||||
let authority_address = instruction_context
|
||||
.get_index_of_instruction_account_in_transaction(1)
|
||||
.and_then(|index| transaction_context.get_key_of_account_at_index(index))?;
|
||||
let mut recipient =
|
||||
instruction_context.try_borrow_instruction_account(transaction_context, 2)?;
|
||||
let state = check_program_account(
|
||||
&log_collector,
|
||||
instruction_context,
|
||||
&program,
|
||||
authority_address,
|
||||
)?;
|
||||
if !matches!(state.status, LoaderV4Status::Retracted) {
|
||||
ic_logger_msg!(log_collector, "Program is not retracted");
|
||||
return Err(InstructionError::InvalidArgument);
|
||||
}
|
||||
let program_size = program
|
||||
.get_data()
|
||||
.len()
|
||||
.saturating_sub(LoaderV4State::program_data_offset());
|
||||
if offset as usize > program_size {
|
||||
ic_logger_msg!(log_collector, "Truncate out of bounds");
|
||||
return Err(InstructionError::AccountDataTooSmall);
|
||||
}
|
||||
let required_lamports = if offset == 0 {
|
||||
if new_size == 0 {
|
||||
program.set_data_length(0)?;
|
||||
0
|
||||
} else {
|
||||
program.set_data_length(
|
||||
LoaderV4State::program_data_offset().saturating_add(offset as usize),
|
||||
LoaderV4State::program_data_offset().saturating_add(new_size as usize),
|
||||
)?;
|
||||
let rent = invoke_context.get_sysvar_cache().get_rent()?;
|
||||
rent.minimum_balance(program.get_data().len())
|
||||
};
|
||||
let transfer_lamports = program.get_lamports().saturating_sub(required_lamports);
|
||||
program.checked_sub_lamports(transfer_lamports)?;
|
||||
recipient.checked_add_lamports(transfer_lamports)?;
|
||||
if is_initialization {
|
||||
let state = get_state_mut(program.get_data_mut()?)?;
|
||||
state.slot = invoke_context.get_sysvar_cache().get_clock()?.slot;
|
||||
state.status = LoaderV4Status::Retracted;
|
||||
state.authority_address = *authority_address;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -588,8 +567,8 @@ pub fn process_instruction_inner(
|
|||
LoaderV4Instruction::Write { offset, bytes } => {
|
||||
process_instruction_write(invoke_context, offset, bytes)
|
||||
}
|
||||
LoaderV4Instruction::Truncate { offset } => {
|
||||
process_instruction_truncate(invoke_context, offset)
|
||||
LoaderV4Instruction::Truncate { new_size } => {
|
||||
process_instruction_truncate(invoke_context, new_size)
|
||||
}
|
||||
LoaderV4Instruction::Deploy => process_instruction_deploy(invoke_context),
|
||||
LoaderV4Instruction::Retract => process_instruction_retract(invoke_context),
|
||||
|
@ -871,19 +850,15 @@ mod tests {
|
|||
#[test]
|
||||
fn test_loader_instruction_write() {
|
||||
let authority_address = Pubkey::new_unique();
|
||||
let mut transaction_accounts = vec![
|
||||
let transaction_accounts = vec![
|
||||
(
|
||||
Pubkey::new_unique(),
|
||||
AccountSharedData::new(0, 0, &loader_v4::id()),
|
||||
load_program_account_from_elf(authority_address, LoaderV4Status::Retracted, "noop"),
|
||||
),
|
||||
(
|
||||
authority_address,
|
||||
AccountSharedData::new(0, 0, &Pubkey::new_unique()),
|
||||
),
|
||||
(
|
||||
Pubkey::new_unique(),
|
||||
AccountSharedData::new(10000000, 0, &loader_v4::id()),
|
||||
),
|
||||
(
|
||||
Pubkey::new_unique(),
|
||||
load_program_account_from_elf(authority_address, LoaderV4Status::Deployed, "noop"),
|
||||
|
@ -898,90 +873,8 @@ mod tests {
|
|||
),
|
||||
];
|
||||
|
||||
// Initialize account by first write
|
||||
let accounts = process_instruction(
|
||||
vec![],
|
||||
&bincode::serialize(&LoaderV4Instruction::Write {
|
||||
offset: 0,
|
||||
bytes: vec![0, 1, 2, 3],
|
||||
})
|
||||
.unwrap(),
|
||||
transaction_accounts.clone(),
|
||||
&[(0, false, true), (1, true, false), (2, true, true)],
|
||||
Ok(()),
|
||||
);
|
||||
assert_eq!(
|
||||
accounts[0].data().len(),
|
||||
loader_v4::LoaderV4State::program_data_offset().saturating_add(4),
|
||||
);
|
||||
assert_eq!(accounts[0].lamports(), 1252800);
|
||||
assert_eq!(
|
||||
accounts[2].lamports(),
|
||||
transaction_accounts[2]
|
||||
.1
|
||||
.lamports()
|
||||
.saturating_sub(accounts[0].lamports()),
|
||||
);
|
||||
|
||||
// Error: Program is not writeable
|
||||
// Overwrite existing data
|
||||
process_instruction(
|
||||
vec![],
|
||||
&bincode::serialize(&LoaderV4Instruction::Write {
|
||||
offset: 0,
|
||||
bytes: vec![0, 1, 2, 3],
|
||||
})
|
||||
.unwrap(),
|
||||
transaction_accounts.clone(),
|
||||
&[(0, false, false), (1, true, false), (2, true, true)],
|
||||
Err(InstructionError::InvalidArgument),
|
||||
);
|
||||
|
||||
// Error: Authority did not sign
|
||||
process_instruction(
|
||||
vec![],
|
||||
&bincode::serialize(&LoaderV4Instruction::Write {
|
||||
offset: 0,
|
||||
bytes: vec![0, 1, 2, 3],
|
||||
})
|
||||
.unwrap(),
|
||||
transaction_accounts.clone(),
|
||||
&[(0, false, true), (1, false, false), (2, true, true)],
|
||||
Err(InstructionError::MissingRequiredSignature),
|
||||
);
|
||||
|
||||
// Extend account by writing at the end
|
||||
transaction_accounts[0].1 = accounts[0].clone();
|
||||
let accounts = process_instruction(
|
||||
vec![],
|
||||
&bincode::serialize(&LoaderV4Instruction::Write {
|
||||
offset: 4,
|
||||
bytes: vec![4, 5, 6, 7],
|
||||
})
|
||||
.unwrap(),
|
||||
transaction_accounts.clone(),
|
||||
&[(0, false, true), (1, true, false), (2, true, true)],
|
||||
Ok(()),
|
||||
);
|
||||
assert_eq!(
|
||||
accounts[0].data().len(),
|
||||
loader_v4::LoaderV4State::program_data_offset().saturating_add(8),
|
||||
);
|
||||
assert_eq!(
|
||||
accounts[0].lamports(),
|
||||
transaction_accounts[0].1.lamports().saturating_add(27840),
|
||||
);
|
||||
assert_eq!(
|
||||
accounts[2].lamports(),
|
||||
transaction_accounts[2].1.lamports().saturating_sub(
|
||||
accounts[0]
|
||||
.lamports()
|
||||
.saturating_sub(transaction_accounts[0].1.lamports()),
|
||||
),
|
||||
);
|
||||
|
||||
// Overwrite existing data (no payer required)
|
||||
transaction_accounts[0].1 = accounts[0].clone();
|
||||
let accounts = process_instruction(
|
||||
vec![],
|
||||
&bincode::serialize(&LoaderV4Instruction::Write {
|
||||
offset: 2,
|
||||
|
@ -992,15 +885,9 @@ mod tests {
|
|||
&[(0, false, true), (1, true, false)],
|
||||
Ok(()),
|
||||
);
|
||||
assert_eq!(
|
||||
accounts[0].data().len(),
|
||||
loader_v4::LoaderV4State::program_data_offset().saturating_add(8),
|
||||
);
|
||||
assert_eq!(accounts[0].lamports(), transaction_accounts[0].1.lamports());
|
||||
|
||||
// Empty write
|
||||
transaction_accounts[0].1 = accounts[0].clone();
|
||||
let accounts = process_instruction(
|
||||
process_instruction(
|
||||
vec![],
|
||||
&bincode::serialize(&LoaderV4Instruction::Write {
|
||||
offset: 2,
|
||||
|
@ -1011,11 +898,6 @@ mod tests {
|
|||
&[(0, false, true), (1, true, false)],
|
||||
Ok(()),
|
||||
);
|
||||
assert_eq!(
|
||||
accounts[0].data().len(),
|
||||
loader_v4::LoaderV4State::program_data_offset().saturating_add(8),
|
||||
);
|
||||
assert_eq!(accounts[0].lamports(), transaction_accounts[0].1.lamports());
|
||||
|
||||
// Error: Program is not retracted
|
||||
process_instruction(
|
||||
|
@ -1026,33 +908,7 @@ mod tests {
|
|||
})
|
||||
.unwrap(),
|
||||
transaction_accounts.clone(),
|
||||
&[(3, false, true), (1, true, false), (2, true, true)],
|
||||
Err(InstructionError::InvalidArgument),
|
||||
);
|
||||
|
||||
// Error: Payer did not sign
|
||||
process_instruction(
|
||||
vec![],
|
||||
&bincode::serialize(&LoaderV4Instruction::Write {
|
||||
offset: 8,
|
||||
bytes: vec![8, 8, 8, 8],
|
||||
})
|
||||
.unwrap(),
|
||||
transaction_accounts.clone(),
|
||||
&[(0, false, true), (1, true, false), (2, false, true)],
|
||||
Err(InstructionError::MissingRequiredSignature),
|
||||
);
|
||||
|
||||
// Error: Payer is not writeable
|
||||
process_instruction(
|
||||
vec![],
|
||||
&bincode::serialize(&LoaderV4Instruction::Write {
|
||||
offset: 8,
|
||||
bytes: vec![8, 8, 8, 8],
|
||||
})
|
||||
.unwrap(),
|
||||
transaction_accounts.clone(),
|
||||
&[(0, false, true), (1, true, false), (2, true, false)],
|
||||
&[(2, false, true), (1, true, false)],
|
||||
Err(InstructionError::InvalidArgument),
|
||||
);
|
||||
|
||||
|
@ -1060,39 +916,18 @@ mod tests {
|
|||
process_instruction(
|
||||
vec![],
|
||||
&bincode::serialize(&LoaderV4Instruction::Write {
|
||||
offset: 9,
|
||||
bytes: vec![8, 8, 8, 8],
|
||||
})
|
||||
.unwrap(),
|
||||
transaction_accounts.clone(),
|
||||
&[(0, false, true), (1, true, false), (2, true, true)],
|
||||
Err(InstructionError::AccountDataTooSmall),
|
||||
);
|
||||
|
||||
// Error: Insufficient funds (Bankrupt payer account)
|
||||
process_instruction(
|
||||
vec![],
|
||||
&bincode::serialize(&LoaderV4Instruction::Write {
|
||||
offset: 8,
|
||||
bytes: vec![8, 8, 8, 8],
|
||||
})
|
||||
.unwrap(),
|
||||
transaction_accounts.clone(),
|
||||
&[(0, false, true), (1, true, false), (1, true, true)],
|
||||
Err(InstructionError::InsufficientFunds),
|
||||
);
|
||||
|
||||
// Error: Insufficient funds (No payer account)
|
||||
process_instruction(
|
||||
vec![],
|
||||
&bincode::serialize(&LoaderV4Instruction::Write {
|
||||
offset: 8,
|
||||
offset: transaction_accounts[0]
|
||||
.1
|
||||
.data()
|
||||
.len()
|
||||
.saturating_sub(loader_v4::LoaderV4State::program_data_offset())
|
||||
.saturating_sub(3) as u32,
|
||||
bytes: vec![8, 8, 8, 8],
|
||||
})
|
||||
.unwrap(),
|
||||
transaction_accounts.clone(),
|
||||
&[(0, false, true), (1, true, false)],
|
||||
Err(InstructionError::InsufficientFunds),
|
||||
Err(InstructionError::AccountDataTooSmall),
|
||||
);
|
||||
|
||||
test_loader_instruction_general_errors(LoaderV4Instruction::Write {
|
||||
|
@ -1104,7 +939,7 @@ mod tests {
|
|||
#[test]
|
||||
fn test_loader_instruction_truncate() {
|
||||
let authority_address = Pubkey::new_unique();
|
||||
let transaction_accounts = vec![
|
||||
let mut transaction_accounts = vec![
|
||||
(
|
||||
Pubkey::new_unique(),
|
||||
load_program_account_from_elf(authority_address, LoaderV4Status::Retracted, "noop"),
|
||||
|
@ -1117,6 +952,18 @@ mod tests {
|
|||
Pubkey::new_unique(),
|
||||
AccountSharedData::new(0, 0, &loader_v4::id()),
|
||||
),
|
||||
(
|
||||
Pubkey::new_unique(),
|
||||
AccountSharedData::new(20000000, 0, &loader_v4::id()),
|
||||
),
|
||||
(
|
||||
Pubkey::new_unique(),
|
||||
load_program_account_from_elf(
|
||||
authority_address,
|
||||
LoaderV4Status::Retracted,
|
||||
"rodata",
|
||||
),
|
||||
),
|
||||
(
|
||||
Pubkey::new_unique(),
|
||||
load_program_account_from_elf(authority_address, LoaderV4Status::Deployed, "noop"),
|
||||
|
@ -1131,31 +978,106 @@ mod tests {
|
|||
),
|
||||
];
|
||||
|
||||
// Cut the end off
|
||||
// No change
|
||||
let accounts = process_instruction(
|
||||
vec![],
|
||||
&bincode::serialize(&LoaderV4Instruction::Truncate { offset: 4 }).unwrap(),
|
||||
&bincode::serialize(&LoaderV4Instruction::Truncate {
|
||||
new_size: transaction_accounts[0]
|
||||
.1
|
||||
.data()
|
||||
.len()
|
||||
.saturating_sub(loader_v4::LoaderV4State::program_data_offset())
|
||||
as u32,
|
||||
})
|
||||
.unwrap(),
|
||||
transaction_accounts.clone(),
|
||||
&[(0, false, true), (1, true, false), (2, false, true)],
|
||||
&[(0, false, true), (1, true, false)],
|
||||
Ok(()),
|
||||
);
|
||||
assert_eq!(
|
||||
accounts[0].data().len(),
|
||||
loader_v4::LoaderV4State::program_data_offset().saturating_add(4),
|
||||
transaction_accounts[0].1.data().len(),
|
||||
);
|
||||
assert_eq!(accounts[2].lamports(), transaction_accounts[2].1.lamports());
|
||||
let lamports = transaction_accounts[4].1.lamports();
|
||||
transaction_accounts[0].1.set_lamports(lamports);
|
||||
|
||||
// Initialize program account
|
||||
let accounts = process_instruction(
|
||||
vec![],
|
||||
&bincode::serialize(&LoaderV4Instruction::Truncate {
|
||||
new_size: transaction_accounts[0]
|
||||
.1
|
||||
.data()
|
||||
.len()
|
||||
.saturating_sub(loader_v4::LoaderV4State::program_data_offset())
|
||||
as u32,
|
||||
})
|
||||
.unwrap(),
|
||||
transaction_accounts.clone(),
|
||||
&[(3, true, true), (1, true, false), (2, false, true)],
|
||||
Ok(()),
|
||||
);
|
||||
assert_eq!(
|
||||
accounts[3].data().len(),
|
||||
transaction_accounts[0].1.data().len(),
|
||||
);
|
||||
|
||||
// Increase program account size
|
||||
let accounts = process_instruction(
|
||||
vec![],
|
||||
&bincode::serialize(&LoaderV4Instruction::Truncate {
|
||||
new_size: transaction_accounts[4]
|
||||
.1
|
||||
.data()
|
||||
.len()
|
||||
.saturating_sub(loader_v4::LoaderV4State::program_data_offset())
|
||||
as u32,
|
||||
})
|
||||
.unwrap(),
|
||||
transaction_accounts.clone(),
|
||||
&[(0, false, true), (1, true, false)],
|
||||
Ok(()),
|
||||
);
|
||||
assert_eq!(
|
||||
accounts[0].data().len(),
|
||||
transaction_accounts[4].1.data().len(),
|
||||
);
|
||||
|
||||
// Decrease program account size
|
||||
let accounts = process_instruction(
|
||||
vec![],
|
||||
&bincode::serialize(&LoaderV4Instruction::Truncate {
|
||||
new_size: transaction_accounts[0]
|
||||
.1
|
||||
.data()
|
||||
.len()
|
||||
.saturating_sub(loader_v4::LoaderV4State::program_data_offset())
|
||||
as u32,
|
||||
})
|
||||
.unwrap(),
|
||||
transaction_accounts.clone(),
|
||||
&[(4, false, true), (1, true, false), (2, false, true)],
|
||||
Ok(()),
|
||||
);
|
||||
assert_eq!(
|
||||
accounts[4].data().len(),
|
||||
transaction_accounts[0].1.data().len(),
|
||||
);
|
||||
assert_eq!(accounts[0].lamports(), 1252800);
|
||||
assert_eq!(
|
||||
accounts[2].lamports(),
|
||||
transaction_accounts[0]
|
||||
.1
|
||||
.lamports()
|
||||
.saturating_sub(accounts[0].lamports()),
|
||||
transaction_accounts[2].1.lamports().saturating_add(
|
||||
transaction_accounts[4]
|
||||
.1
|
||||
.lamports()
|
||||
.saturating_sub(accounts[4].lamports())
|
||||
),
|
||||
);
|
||||
|
||||
// Close program account
|
||||
let accounts = process_instruction(
|
||||
vec![],
|
||||
&bincode::serialize(&LoaderV4Instruction::Truncate { offset: 0 }).unwrap(),
|
||||
&bincode::serialize(&LoaderV4Instruction::Truncate { new_size: 0 }).unwrap(),
|
||||
transaction_accounts.clone(),
|
||||
&[(0, false, true), (1, true, false), (2, false, true)],
|
||||
Ok(()),
|
||||
|
@ -1163,40 +1085,104 @@ mod tests {
|
|||
assert_eq!(accounts[0].data().len(), 0);
|
||||
assert_eq!(
|
||||
accounts[2].lamports(),
|
||||
transaction_accounts[0]
|
||||
.1
|
||||
.lamports()
|
||||
.saturating_sub(accounts[0].lamports()),
|
||||
transaction_accounts[2].1.lamports().saturating_add(
|
||||
transaction_accounts[0]
|
||||
.1
|
||||
.lamports()
|
||||
.saturating_sub(accounts[0].lamports())
|
||||
),
|
||||
);
|
||||
|
||||
// Error: Program is uninitialized
|
||||
// Error: Program not owned by loader
|
||||
process_instruction(
|
||||
vec![],
|
||||
&bincode::serialize(&LoaderV4Instruction::Truncate { offset: 0 }).unwrap(),
|
||||
&bincode::serialize(&LoaderV4Instruction::Truncate { new_size: 8 }).unwrap(),
|
||||
transaction_accounts.clone(),
|
||||
&[(2, false, true), (1, true, false), (0, false, true)],
|
||||
&[(1, false, true), (1, true, false), (2, true, true)],
|
||||
Err(InstructionError::InvalidAccountOwner),
|
||||
);
|
||||
|
||||
// Error: Program is not writeable
|
||||
process_instruction(
|
||||
vec![],
|
||||
&bincode::serialize(&LoaderV4Instruction::Truncate { new_size: 8 }).unwrap(),
|
||||
transaction_accounts.clone(),
|
||||
&[(3, false, false), (1, true, false), (2, true, true)],
|
||||
Err(InstructionError::InvalidArgument),
|
||||
);
|
||||
|
||||
// Error: Program did not sign
|
||||
process_instruction(
|
||||
vec![],
|
||||
&bincode::serialize(&LoaderV4Instruction::Truncate { new_size: 8 }).unwrap(),
|
||||
transaction_accounts.clone(),
|
||||
&[(3, false, true), (1, true, false), (2, true, true)],
|
||||
Err(InstructionError::MissingRequiredSignature),
|
||||
);
|
||||
|
||||
// Error: Authority did not sign
|
||||
process_instruction(
|
||||
vec![],
|
||||
&bincode::serialize(&LoaderV4Instruction::Truncate { new_size: 8 }).unwrap(),
|
||||
transaction_accounts.clone(),
|
||||
&[(3, true, true), (1, false, false), (2, true, true)],
|
||||
Err(InstructionError::MissingRequiredSignature),
|
||||
);
|
||||
|
||||
// Error: Program is and stays uninitialized
|
||||
process_instruction(
|
||||
vec![],
|
||||
&bincode::serialize(&LoaderV4Instruction::Truncate { new_size: 0 }).unwrap(),
|
||||
transaction_accounts.clone(),
|
||||
&[(3, false, true), (1, true, false), (2, true, true)],
|
||||
Err(InstructionError::InvalidAccountData),
|
||||
);
|
||||
|
||||
// Error: Program is not retracted
|
||||
process_instruction(
|
||||
vec![],
|
||||
&bincode::serialize(&LoaderV4Instruction::Truncate { offset: 0 }).unwrap(),
|
||||
&bincode::serialize(&LoaderV4Instruction::Truncate { new_size: 8 }).unwrap(),
|
||||
transaction_accounts.clone(),
|
||||
&[(3, false, true), (1, true, false), (2, false, true)],
|
||||
&[(5, false, true), (1, true, false), (2, false, true)],
|
||||
Err(InstructionError::InvalidArgument),
|
||||
);
|
||||
|
||||
// Error: Truncate out of bounds
|
||||
// Error: Missing recipient account
|
||||
process_instruction(
|
||||
vec![],
|
||||
&bincode::serialize(&LoaderV4Instruction::Truncate { offset: 10000 }).unwrap(),
|
||||
transaction_accounts,
|
||||
&[(0, false, true), (1, true, false), (2, false, true)],
|
||||
Err(InstructionError::AccountDataTooSmall),
|
||||
&bincode::serialize(&LoaderV4Instruction::Truncate { new_size: 0 }).unwrap(),
|
||||
transaction_accounts.clone(),
|
||||
&[(0, true, true), (1, true, false)],
|
||||
Err(InstructionError::NotEnoughAccountKeys),
|
||||
);
|
||||
|
||||
test_loader_instruction_general_errors(LoaderV4Instruction::Truncate { offset: 0 });
|
||||
// Error: Recipient is not writeable
|
||||
process_instruction(
|
||||
vec![],
|
||||
&bincode::serialize(&LoaderV4Instruction::Truncate { new_size: 0 }).unwrap(),
|
||||
transaction_accounts.clone(),
|
||||
&[(0, false, true), (1, true, false), (2, false, false)],
|
||||
Err(InstructionError::InvalidArgument),
|
||||
);
|
||||
|
||||
// Error: Insufficient funds
|
||||
process_instruction(
|
||||
vec![],
|
||||
&bincode::serialize(&LoaderV4Instruction::Truncate {
|
||||
new_size: transaction_accounts[4]
|
||||
.1
|
||||
.data()
|
||||
.len()
|
||||
.saturating_sub(loader_v4::LoaderV4State::program_data_offset())
|
||||
.saturating_add(1) as u32,
|
||||
})
|
||||
.unwrap(),
|
||||
transaction_accounts.clone(),
|
||||
&[(0, false, true), (1, true, false)],
|
||||
Err(InstructionError::InsufficientFunds),
|
||||
);
|
||||
|
||||
test_loader_instruction_general_errors(LoaderV4Instruction::Truncate { new_size: 0 });
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -5,16 +5,9 @@
|
|||
pub enum LoaderV4Instruction {
|
||||
/// Write ELF data into an undeployed program account.
|
||||
///
|
||||
/// Writing at the end (offset is length) increases the size of the program account and
|
||||
/// providing additional lamports (via the payer account) is necessary to reach rent exemption.
|
||||
/// The first write (at offset zero when the account is empty) automatically
|
||||
/// initializes the program account and sets the authority needed for subsequent writes.
|
||||
/// Thus, the first write should be in the same transaction as the account creation.
|
||||
///
|
||||
/// # Account references
|
||||
/// 0. `[writable]` The program account to write to.
|
||||
/// 1. `[signer]` The authority of the program.
|
||||
/// 2. `[signer]` Optional, the payer account.
|
||||
Write {
|
||||
/// Offset at which to write the given bytes.
|
||||
offset: u32,
|
||||
|
@ -23,18 +16,23 @@ pub enum LoaderV4Instruction {
|
|||
bytes: Vec<u8>,
|
||||
},
|
||||
|
||||
/// Decrease the size of an undeployed program account.
|
||||
/// Changes the size of an undeployed program account.
|
||||
///
|
||||
/// Decreasing to size zero closes the program account and resets it into an uninitialized state.
|
||||
/// Superflous lamports are transfered to the recipient account.
|
||||
/// A program account is automatically initialized when its size is first increased.
|
||||
/// In this initial truncate, the program account needs to be a signer and
|
||||
/// it also sets the authority needed for subsequent operations.
|
||||
/// Decreasing to size zero closes the program account and resets it
|
||||
/// into an uninitialized state.
|
||||
/// Providing additional lamports upfront might be necessary to reach rent exemption.
|
||||
/// Superflous funds are transfered to the recipient account.
|
||||
///
|
||||
/// # Account references
|
||||
/// 0. `[writable]` The program account to change the size of.
|
||||
/// 0. `[(signer), writable]` The program account to change the size of.
|
||||
/// 1. `[signer]` The authority of the program.
|
||||
/// 2. `[writable]` The recipient account.
|
||||
/// 2. `[writable]` Optional, the recipient account.
|
||||
Truncate {
|
||||
/// Offset at which to cut off the rest. This will be the size after the operation.
|
||||
offset: u32,
|
||||
/// The new size after the operation.
|
||||
new_size: u32,
|
||||
},
|
||||
|
||||
/// Verify the data of a program account to be a valid ELF.
|
||||
|
|
Loading…
Reference in New Issue