@@ -44,6 +44,8 @@ impl TasksAccountHeaderV0 {
4444 }
4545}
4646
47+ const MEMO_PROGRAM_ID : Pubkey = pubkey ! ( "MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr" ) ;
48+
4749// Add new iterator struct for reading tasks
4850pub struct TasksIterator < ' a > {
4951 data : & ' a mut & ' a [ u8 ] ,
@@ -205,13 +207,17 @@ impl<'a, 'info> TaskProcessor<'a, 'info> {
205207 }
206208
207209 // Pass free tasks as remaining accounts so the task can know which IDs will be used
208- let free_tasks = & self . ctx . remaining_accounts [ self . free_task_index ..] ;
209- accounts. extend ( free_tasks. iter ( ) . cloned ( ) ) ;
210- account_infos. extend ( free_tasks. iter ( ) . map ( |acct| AccountMeta {
211- pubkey : acct. key ( ) ,
212- is_signer : false ,
213- is_writable : false ,
214- } ) ) ;
210+ let program_id = remaining_accounts[ ix. program_id_index as usize ] . key ;
211+ // Ignore memo program because it expects every account passed to be a signer.
212+ if * program_id != MEMO_PROGRAM_ID {
213+ let free_tasks = & self . ctx . remaining_accounts [ self . free_task_index ..] ;
214+ accounts. extend ( free_tasks. iter ( ) . cloned ( ) ) ;
215+ account_infos. extend ( free_tasks. iter ( ) . map ( |acct| AccountMeta {
216+ pubkey : acct. key ( ) ,
217+ is_signer : false ,
218+ is_writable : false ,
219+ } ) ) ;
220+ }
215221
216222 let signer_seeds: Vec < Vec < & [ u8 ] > > = self
217223 . signers
@@ -221,7 +227,7 @@ impl<'a, 'info> TaskProcessor<'a, 'info> {
221227
222228 solana_program:: program:: invoke_signed (
223229 & Instruction {
224- program_id : * remaining_accounts [ ix . program_id_index as usize ] . key ,
230+ program_id : * program_id ,
225231 accounts : account_infos,
226232 data : ix. data . clone ( ) ,
227233 } ,
@@ -288,12 +294,43 @@ impl<'a, 'info> TaskProcessor<'a, 'info> {
288294 }
289295
290296 fn create_new_task ( & mut self , task : TaskReturnV0 ) -> Result < ( ) > {
297+ require_gte ! (
298+ 40 ,
299+ task. description. len( ) ,
300+ ErrorCode :: InvalidDescriptionLength
301+ ) ;
302+ require_gte ! (
303+ task. crank_reward
304+ . unwrap_or( self . ctx. accounts. task_queue. min_crank_reward) ,
305+ self . ctx. accounts. task_queue. min_crank_reward,
306+ ErrorCode :: InvalidCrankReward
307+ ) ;
308+ require_gte ! (
309+ self . ctx. accounts. task_queue. capacity,
310+ ( task. free_tasks + 1 ) as u16 ,
311+ ErrorCode :: FreeTasksGreaterThanCapacity
312+ ) ;
313+
291314 let free_task_account = & self . ctx . remaining_accounts [ self . free_task_index ] ;
292315 self . free_task_index += 1 ;
293316 let task_queue = & mut self . ctx . accounts . task_queue ;
294317 let task_queue_key = task_queue. key ( ) ;
295318
296- let task_id = self . free_task_ids . pop ( ) . unwrap ( ) ;
319+ let task_id = self
320+ . free_task_ids
321+ . pop ( )
322+ . ok_or ( error ! ( ErrorCode :: TooManyReturnedTasks ) ) ?;
323+
324+ require ! (
325+ !task_queue. task_exists( task_id) ,
326+ ErrorCode :: TaskIdAlreadyInUse
327+ ) ;
328+
329+ // Verify the account is empty
330+ require ! (
331+ free_task_account. data_is_empty( ) ,
332+ ErrorCode :: FreeTaskAccountNotEmpty
333+ ) ;
297334
298335 let seeds = [ b"task" , task_queue_key. as_ref ( ) , & task_id. to_le_bytes ( ) ] ;
299336 let ( key, bump_seed) = Pubkey :: find_program_address ( & seeds, self . ctx . program_id ) ;
@@ -318,10 +355,10 @@ impl<'a, 'info> TaskProcessor<'a, 'info> {
318355 let task_size = task_data. try_to_vec ( ) ?. len ( ) + 8 + 60 ;
319356 let rent_lamports = Rent :: get ( ) ?. minimum_balance ( task_size) ;
320357 let lamports = rent_lamports + task_data. crank_reward ;
321- task_data. rent_amount = lamports ;
358+ task_data. rent_amount = rent_lamports ;
322359
323360 let task_queue_info = self . ctx . accounts . task_queue . to_account_info ( ) ;
324- let task_queue_min_lamports = Rent :: get ( ) ?. minimum_balance ( task_queue_info. data_len ( ) + 60 ) ;
361+ let task_queue_min_lamports = Rent :: get ( ) ?. minimum_balance ( task_queue_info. data_len ( ) ) ;
325362
326363 require_gt ! (
327364 task_queue_info. lamports( ) ,
@@ -343,8 +380,13 @@ impl<'a, 'info> TaskProcessor<'a, 'info> {
343380 free_task_account. realloc ( task_size, false ) ?;
344381
345382 let task_info = self . ctx . accounts . task . to_account_info ( ) ;
346- let task_remaining_lamports = self . ctx . accounts . task . to_account_info ( ) . lamports ( )
347- - self . ctx . accounts . task . crank_reward ;
383+ let task_remaining_lamports = self
384+ . ctx
385+ . accounts
386+ . task
387+ . to_account_info ( )
388+ . lamports ( )
389+ . saturating_sub ( self . ctx . accounts . task . crank_reward ) ;
348390 let lamports_from_task = task_remaining_lamports. min ( lamports) ;
349391 let lamports_needed_from_queue = lamports. saturating_sub ( lamports_from_task) ;
350392
@@ -373,13 +415,23 @@ pub fn handler<'info>(
373415 TriggerV0 :: Timestamp ( timestamp) => timestamp,
374416 } ;
375417 ctx. accounts . task_queue . updated_at = now;
418+ // Check for duplicate task IDs
419+ let mut seen_ids = std:: collections:: HashSet :: new ( ) ;
376420 for id in args. free_task_ids . clone ( ) {
377421 require_gt ! (
378422 ctx. accounts. task_queue. capacity,
379423 id,
380424 ErrorCode :: InvalidTaskId
381425 ) ;
426+ // Ensure ID is not already in use in the task queue
427+ require ! (
428+ !ctx. accounts. task_queue. task_exists( id) ,
429+ ErrorCode :: TaskIdAlreadyInUse
430+ ) ;
431+ // Check for duplicates in provided IDs
432+ require ! ( seen_ids. insert( id) , ErrorCode :: DuplicateTaskIds ) ;
382433 }
434+
383435 let remaining_accounts = ctx. remaining_accounts ;
384436
385437 let transaction = match ctx. accounts . task . transaction . clone ( ) {
@@ -393,6 +445,11 @@ pub fn handler<'info>(
393445 ) ?;
394446 let data = utils:: ed25519:: verify_ed25519_ix ( & ix, signer. to_bytes ( ) . as_slice ( ) ) ?;
395447 let mut remote_tx = RemoteTaskTransactionV0 :: try_deserialize ( & mut & data[ ..] ) ?;
448+ require_eq ! (
449+ remote_tx. transaction. accounts. len( ) ,
450+ 0 ,
451+ ErrorCode :: MalformedRemoteTransaction
452+ ) ;
396453
397454 let num_accounts = remote_tx
398455 . transaction
@@ -425,7 +482,9 @@ pub fn handler<'info>(
425482 + remote_tx. transaction . num_rw_signers ;
426483 // The rent refund account may make an account that shouldn't be writable appear writable
427484 if i >= writable_end_idx as usize
428- && * acc. key == ctx. accounts . rent_refund . key ( )
485+ && ( * acc. key == ctx. accounts . rent_refund . key ( )
486+ || * acc. key == ctx. accounts . task_queue . key ( )
487+ || * acc. key == ctx. accounts . task . key ( ) )
429488 {
430489 data. push ( 0 ) ;
431490 } else {
@@ -464,22 +523,27 @@ pub fn handler<'info>(
464523 . task_queue
465524 . set_task_exists ( ctx. accounts . task . id , false ) ;
466525
467- let free_tasks = ctx. accounts . task . free_tasks ;
468-
469- // Validate that all free task accounts are empty
526+ // Validate that all free task accounts are empty and are valid PDAs
470527 let free_tasks_start_index = transaction. accounts . len ( ) ;
471- for i in 0 ..free_tasks {
472- let free_task_index = free_tasks_start_index + i as usize ;
473- let free_task_account = & remaining_accounts[ free_task_index] ;
474- require ! (
475- free_task_account. data_is_empty( ) ,
476- ErrorCode :: FreeTaskAccountNotEmpty
477- ) ;
478- }
528+ // Validate number of free task accounts matches number of task IDs
529+ require_eq ! (
530+ args. free_task_ids. len( ) ,
531+ ctx. remaining_accounts. len( ) - free_tasks_start_index,
532+ ErrorCode :: MismatchedFreeTaskCounts
533+ ) ;
479534
480535 if now. saturating_sub ( task_time) <= ctx. accounts . task_queue . stale_task_age as i64 {
481536 let mut processor = TaskProcessor :: new ( ctx, & transaction, args. free_task_ids ) ?;
482537
538+ // Validate account keys match
539+ for ( i, account) in transaction. accounts . iter ( ) . enumerate ( ) {
540+ require_eq ! (
541+ account,
542+ remaining_accounts[ i] . key,
543+ ErrorCode :: InvalidAccountKey
544+ ) ;
545+ }
546+
483547 // Process each instruction
484548 for ix in & transaction. instructions {
485549 processor. process_instruction ( ix, remaining_accounts) ?;
0 commit comments