Skip to content

Commit 1700d1b

Browse files
committed
Merge remote-tracking branch 'origin/main' into feat/better-cli
2 parents f89186c + 37c2504 commit 1700d1b

File tree

10 files changed

+357
-32
lines changed

10 files changed

+357
-32
lines changed

.github/actions/setup/action.yaml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,9 @@ runs:
1313
shell: bash
1414
- run: git submodule update --init --recursive --depth 1
1515
shell: bash
16-
- name: Install Protoc
17-
uses: arduino/setup-protoc@v3
1816
- uses: dtolnay/rust-toolchain@stable
1917
with:
20-
toolchain: 1.80.1
18+
toolchain: 1.82.0
2119
- uses: actions/cache@v3
2220
with:
2321
path: |

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

solana-programs/Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

solana-programs/programs/cron/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,4 @@ solana-zk-sdk = { workspace = true }
2727
tuktuk-program = { git = "https://github.com/helium/tuktuk.git", rev = "112afe5e80aff8199c3b779203b76b35d97c42d1" }
2828
clockwork-cron = "2.0.19"
2929
chrono = "0.4.39"
30+

solana-programs/programs/tuktuk/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "tuktuk"
3-
version = "0.2.2"
3+
version = "0.2.4"
44
description = "Created with Anchor"
55
edition = "2021"
66

solana-programs/programs/tuktuk/src/error.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,18 @@ pub enum ErrorCode {
4040
TaskQueueHasQueueAuthorities,
4141
#[msg("Free tasks must be less than the capacity of the task queue")]
4242
FreeTasksGreaterThanCapacity,
43+
#[msg("Task ID is already in use")]
44+
TaskIdAlreadyInUse,
45+
#[msg("Duplicate task IDs provided")]
46+
DuplicateTaskIds,
47+
#[msg("Number of free task IDs does not match number of free task accounts")]
48+
MismatchedFreeTaskCounts,
49+
#[msg("Too many returned tasks, increase free tasks count")]
50+
TooManyReturnedTasks,
51+
#[msg("Malformed remote transaction")]
52+
MalformedRemoteTransaction,
53+
#[msg("Invalid account key")]
54+
InvalidAccountKey,
55+
#[msg("Invalid crank reward")]
56+
InvalidCrankReward,
4357
}

solana-programs/programs/tuktuk/src/instructions/initialize_task_queue_v0.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ pub struct InitializeTaskQueueV0<'info> {
3535
payer = payer,
3636
seeds = ["task_queue".as_bytes(), tuktuk_config.key().as_ref(), &tuktuk_config.next_task_queue_id.to_le_bytes()[..]],
3737
bump,
38-
space = 60 + std::mem::size_of::<TaskQueueV0>() + args.name.len() + ((args.capacity + 7) / 8) as usize,
38+
space = 60 + std::mem::size_of::<TaskQueueV0>() + args.name.len() + ((args.capacity + 7) / 8) as usize + args.lookup_tables.len() * 32,
3939
)]
4040
pub task_queue: Box<Account<'info, TaskQueueV0>>,
4141
#[account(

solana-programs/programs/tuktuk/src/instructions/run_task_v0.rs

Lines changed: 89 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -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
4850
pub 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)?;

tuktuk-cli/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ thiserror = "1"
2020
anchor-lang = { workspace = true }
2121
solana-quic-client = { workspace = true }
2222
solana-client = { workspace = true }
23+
solana-transaction-status-client-types = "2.2.1"
2324
anyhow = { workspace = true }
2425
clap = { workspace = true }
2526
tokio = { workspace = true }

0 commit comments

Comments
 (0)