Skip to content

Commit 6fce479

Browse files
committed
Adds LoaderV4Instruction::Copy.
1 parent 19de967 commit 6fce479

File tree

2 files changed

+274
-10
lines changed

2 files changed

+274
-10
lines changed

programs/loader-v4/src/lib.rs

Lines changed: 205 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ use {
77
solana_log_collector::{ic_logger_msg, LogCollector},
88
solana_measure::measure::Measure,
99
solana_program::{
10+
bpf_loader, bpf_loader_deprecated,
11+
bpf_loader_upgradeable::{self, UpgradeableLoaderState},
1012
loader_v4::{self, LoaderV4State, LoaderV4Status, DEPLOYMENT_COOLDOWN_IN_SLOTS},
1113
loader_v4_instruction::LoaderV4Instruction,
1214
},
@@ -104,13 +106,10 @@ fn process_instruction_write(
104106
ic_logger_msg!(log_collector, "Program is not retracted");
105107
return Err(InstructionError::InvalidArgument);
106108
}
107-
let end_offset = (offset as usize).saturating_add(bytes.len());
109+
let destination_offset = (offset as usize).saturating_add(LoaderV4State::program_data_offset());
108110
program
109111
.get_data_mut()?
110-
.get_mut(
111-
LoaderV4State::program_data_offset().saturating_add(offset as usize)
112-
..LoaderV4State::program_data_offset().saturating_add(end_offset),
113-
)
112+
.get_mut(destination_offset..destination_offset.saturating_add(bytes.len()))
114113
.ok_or_else(|| {
115114
ic_logger_msg!(log_collector, "Write out of bounds");
116115
InstructionError::AccountDataTooSmall
@@ -119,6 +118,65 @@ fn process_instruction_write(
119118
Ok(())
120119
}
121120

121+
fn process_instruction_copy(
122+
invoke_context: &mut InvokeContext,
123+
destination_offset: u32,
124+
source_offset: u32,
125+
length: u32,
126+
) -> Result<(), InstructionError> {
127+
let log_collector = invoke_context.get_log_collector();
128+
let transaction_context = &invoke_context.transaction_context;
129+
let instruction_context = transaction_context.get_current_instruction_context()?;
130+
let mut program = instruction_context.try_borrow_instruction_account(transaction_context, 0)?;
131+
let authority_address = instruction_context
132+
.get_index_of_instruction_account_in_transaction(1)
133+
.and_then(|index| transaction_context.get_key_of_account_at_index(index))?;
134+
let source_program =
135+
instruction_context.try_borrow_instruction_account(transaction_context, 2)?;
136+
let state = check_program_account(
137+
&log_collector,
138+
instruction_context,
139+
&program,
140+
authority_address,
141+
)?;
142+
if !matches!(state.status, LoaderV4Status::Retracted) {
143+
ic_logger_msg!(log_collector, "Program is not retracted");
144+
return Err(InstructionError::InvalidArgument);
145+
}
146+
let source_owner = &source_program.get_owner();
147+
let source_offset =
148+
(source_offset as usize).saturating_add(if loader_v4::check_id(source_owner) {
149+
LoaderV4State::program_data_offset()
150+
} else if bpf_loader_upgradeable::check_id(source_owner) {
151+
UpgradeableLoaderState::size_of_programdata_metadata()
152+
} else if bpf_loader_deprecated::check_id(source_owner)
153+
|| bpf_loader::check_id(source_owner)
154+
{
155+
0
156+
} else {
157+
ic_logger_msg!(log_collector, "Source is not a program");
158+
return Err(InstructionError::InvalidArgument);
159+
});
160+
let data = source_program
161+
.get_data()
162+
.get(source_offset..source_offset.saturating_add(length as usize))
163+
.ok_or_else(|| {
164+
ic_logger_msg!(log_collector, "Read out of bounds");
165+
InstructionError::AccountDataTooSmall
166+
})?;
167+
let destination_offset =
168+
(destination_offset as usize).saturating_add(LoaderV4State::program_data_offset());
169+
program
170+
.get_data_mut()?
171+
.get_mut(destination_offset..destination_offset.saturating_add(length as usize))
172+
.ok_or_else(|| {
173+
ic_logger_msg!(log_collector, "Write out of bounds");
174+
InstructionError::AccountDataTooSmall
175+
})?
176+
.copy_from_slice(data);
177+
Ok(())
178+
}
179+
122180
fn process_instruction_truncate(
123181
invoke_context: &mut InvokeContext,
124182
new_size: u32,
@@ -432,6 +490,13 @@ fn process_instruction_inner(
432490
LoaderV4Instruction::Write { offset, bytes } => {
433491
process_instruction_write(invoke_context, offset, bytes)
434492
}
493+
LoaderV4Instruction::Copy {
494+
destination_offset,
495+
source_offset,
496+
length,
497+
} => {
498+
process_instruction_copy(invoke_context, destination_offset, source_offset, length)
499+
}
435500
LoaderV4Instruction::Truncate { new_size } => {
436501
process_instruction_truncate(invoke_context, new_size)
437502
}
@@ -755,6 +820,141 @@ mod tests {
755820
});
756821
}
757822

823+
#[test]
824+
fn test_loader_instruction_copy() {
825+
let authority_address = Pubkey::new_unique();
826+
let transaction_accounts = vec![
827+
(
828+
Pubkey::new_unique(),
829+
load_program_account_from_elf(
830+
authority_address,
831+
LoaderV4Status::Retracted,
832+
"sbpfv3_return_err",
833+
),
834+
),
835+
(
836+
authority_address,
837+
AccountSharedData::new(0, 0, &Pubkey::new_unique()),
838+
),
839+
(
840+
Pubkey::new_unique(),
841+
load_program_account_from_elf(
842+
authority_address,
843+
LoaderV4Status::Deployed,
844+
"sbpfv3_return_err",
845+
),
846+
),
847+
(
848+
clock::id(),
849+
create_account_shared_data_for_test(&clock::Clock::default()),
850+
),
851+
(
852+
rent::id(),
853+
create_account_shared_data_for_test(&rent::Rent::default()),
854+
),
855+
];
856+
857+
// Overwrite existing data
858+
process_instruction(
859+
vec![],
860+
&bincode::serialize(&LoaderV4Instruction::Copy {
861+
destination_offset: 1,
862+
source_offset: 2,
863+
length: 3,
864+
})
865+
.unwrap(),
866+
transaction_accounts.clone(),
867+
&[(0, false, true), (1, true, false), (2, false, false)],
868+
Ok(()),
869+
);
870+
871+
// Empty copy
872+
process_instruction(
873+
vec![],
874+
&bincode::serialize(&LoaderV4Instruction::Copy {
875+
destination_offset: 1,
876+
source_offset: 2,
877+
length: 0,
878+
})
879+
.unwrap(),
880+
transaction_accounts.clone(),
881+
&[(0, false, true), (1, true, false), (2, false, false)],
882+
Ok(()),
883+
);
884+
885+
// Error: Program is not retracted
886+
process_instruction(
887+
vec![],
888+
&bincode::serialize(&LoaderV4Instruction::Copy {
889+
destination_offset: 1,
890+
source_offset: 2,
891+
length: 3,
892+
})
893+
.unwrap(),
894+
transaction_accounts.clone(),
895+
&[(2, false, true), (1, true, false), (0, false, false)],
896+
Err(InstructionError::InvalidArgument),
897+
);
898+
899+
// Error: Destination and source collide
900+
process_instruction(
901+
vec![],
902+
&bincode::serialize(&LoaderV4Instruction::Copy {
903+
destination_offset: 1,
904+
source_offset: 2,
905+
length: 3,
906+
})
907+
.unwrap(),
908+
transaction_accounts.clone(),
909+
&[(2, false, true), (1, true, false), (2, false, false)],
910+
Err(InstructionError::AccountBorrowFailed),
911+
);
912+
913+
// Error: Read out of bounds
914+
process_instruction(
915+
vec![],
916+
&bincode::serialize(&LoaderV4Instruction::Copy {
917+
destination_offset: 1,
918+
source_offset: transaction_accounts[2]
919+
.1
920+
.data()
921+
.len()
922+
.saturating_sub(loader_v4::LoaderV4State::program_data_offset())
923+
.saturating_sub(3) as u32,
924+
length: 4,
925+
})
926+
.unwrap(),
927+
transaction_accounts.clone(),
928+
&[(0, false, true), (1, true, false), (2, false, false)],
929+
Err(InstructionError::AccountDataTooSmall),
930+
);
931+
932+
// Error: Write out of bounds
933+
process_instruction(
934+
vec![],
935+
&bincode::serialize(&LoaderV4Instruction::Copy {
936+
destination_offset: transaction_accounts[0]
937+
.1
938+
.data()
939+
.len()
940+
.saturating_sub(loader_v4::LoaderV4State::program_data_offset())
941+
.saturating_sub(3) as u32,
942+
source_offset: 2,
943+
length: 4,
944+
})
945+
.unwrap(),
946+
transaction_accounts.clone(),
947+
&[(0, false, true), (1, true, false), (2, false, false)],
948+
Err(InstructionError::AccountDataTooSmall),
949+
);
950+
951+
test_loader_instruction_general_errors(LoaderV4Instruction::Copy {
952+
destination_offset: 1,
953+
source_offset: 2,
954+
length: 3,
955+
});
956+
}
957+
758958
#[test]
759959
fn test_loader_instruction_truncate() {
760960
let authority_address = Pubkey::new_unique();

sdk/loader-v4-interface/src/instruction.rs

Lines changed: 69 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,21 @@ pub enum LoaderV4Instruction {
2626
bytes: Vec<u8>,
2727
},
2828

29+
/// Copy ELF data into an undeployed program account.
30+
///
31+
/// # Account references
32+
/// 0. `[writable]` The program account to write to.
33+
/// 1. `[signer]` The authority of the program.
34+
/// 2. `[]` The program account to copy from.
35+
Copy {
36+
/// Offset at which to write.
37+
destination_offset: u32,
38+
/// Offset at which to read.
39+
source_offset: u32,
40+
/// Amount of bytes to copy.
41+
length: u32,
42+
},
43+
2944
/// Changes the size of an undeployed program account.
3045
///
3146
/// A program account is automatically initialized when its size is first increased.
@@ -90,26 +105,30 @@ pub fn is_write_instruction(instruction_data: &[u8]) -> bool {
90105
!instruction_data.is_empty() && 0 == instruction_data[0]
91106
}
92107

93-
pub fn is_truncate_instruction(instruction_data: &[u8]) -> bool {
108+
pub fn is_copy_instruction(instruction_data: &[u8]) -> bool {
94109
!instruction_data.is_empty() && 1 == instruction_data[0]
95110
}
96111

97-
pub fn is_deploy_instruction(instruction_data: &[u8]) -> bool {
112+
pub fn is_truncate_instruction(instruction_data: &[u8]) -> bool {
98113
!instruction_data.is_empty() && 2 == instruction_data[0]
99114
}
100115

101-
pub fn is_retract_instruction(instruction_data: &[u8]) -> bool {
116+
pub fn is_deploy_instruction(instruction_data: &[u8]) -> bool {
102117
!instruction_data.is_empty() && 3 == instruction_data[0]
103118
}
104119

105-
pub fn is_transfer_authority_instruction(instruction_data: &[u8]) -> bool {
120+
pub fn is_retract_instruction(instruction_data: &[u8]) -> bool {
106121
!instruction_data.is_empty() && 4 == instruction_data[0]
107122
}
108123

109-
pub fn is_finalize_instruction(instruction_data: &[u8]) -> bool {
124+
pub fn is_transfer_authority_instruction(instruction_data: &[u8]) -> bool {
110125
!instruction_data.is_empty() && 5 == instruction_data[0]
111126
}
112127

128+
pub fn is_finalize_instruction(instruction_data: &[u8]) -> bool {
129+
!instruction_data.is_empty() && 6 == instruction_data[0]
130+
}
131+
113132
/// Returns the instructions required to initialize a program/buffer account.
114133
#[cfg(feature = "bincode")]
115134
pub fn create_buffer(
@@ -190,6 +209,31 @@ pub fn write(
190209
)
191210
}
192211

212+
/// Returns the instructions required to copy a chunk of program data.
213+
#[cfg(feature = "bincode")]
214+
pub fn copy(
215+
program_address: &Pubkey,
216+
authority: &Pubkey,
217+
source_address: &Pubkey,
218+
destination_offset: u32,
219+
source_offset: u32,
220+
length: u32,
221+
) -> Instruction {
222+
Instruction::new_with_bincode(
223+
id(),
224+
&LoaderV4Instruction::Copy {
225+
destination_offset,
226+
source_offset,
227+
length,
228+
},
229+
vec![
230+
AccountMeta::new(*program_address, false),
231+
AccountMeta::new_readonly(*authority, true),
232+
AccountMeta::new_readonly(*source_address, false),
233+
],
234+
)
235+
}
236+
193237
/// Returns the instructions required to deploy a program.
194238
#[cfg(feature = "bincode")]
195239
pub fn deploy(program_address: &Pubkey, authority: &Pubkey) -> Instruction {
@@ -319,6 +363,26 @@ mod tests {
319363
assert!(instruction.accounts[1].is_signer);
320364
}
321365

366+
#[test]
367+
fn test_copy_instruction() {
368+
let program = Pubkey::new_unique();
369+
let authority = Pubkey::new_unique();
370+
let source = Pubkey::new_unique();
371+
let instruction = copy(&program, &authority, &source, 1, 2, 3);
372+
assert!(is_copy_instruction(&instruction.data));
373+
assert_eq!(instruction.program_id, id());
374+
assert_eq!(instruction.accounts.len(), 3);
375+
assert_eq!(instruction.accounts[0].pubkey, program);
376+
assert!(instruction.accounts[0].is_writable);
377+
assert!(!instruction.accounts[0].is_signer);
378+
assert_eq!(instruction.accounts[1].pubkey, authority);
379+
assert!(!instruction.accounts[1].is_writable);
380+
assert!(instruction.accounts[1].is_signer);
381+
assert_eq!(instruction.accounts[2].pubkey, source);
382+
assert!(!instruction.accounts[2].is_writable);
383+
assert!(!instruction.accounts[2].is_signer);
384+
}
385+
322386
#[test]
323387
fn test_truncate_instruction() {
324388
let program = Pubkey::new_unique();

0 commit comments

Comments
 (0)