Skip to content

Commit 7208586

Browse files
committed
Attempting to work through CPI rescheduling
1 parent 28cd8eb commit 7208586

File tree

14 files changed

+296
-11
lines changed

14 files changed

+296
-11
lines changed

solana-programs/Anchor.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,12 @@ skip-lint = false
66

77
[programs.localnet]
88
tuktuk = "tukpKuBbnQwG6yQbYRbeDM9Dk3D9fDkUpc6sytJsyGC"
9+
cpi-example = "cpic9j9sjqvhn2ZX3mqcCgzHKCwiiBTyEszyCwN7MBC"
910

1011
[workspace]
1112
members = [
12-
"programs/tuktuk"
13+
"programs/tuktuk",
14+
"programs/cpi-example"
1315
]
1416

1517
[registry]

solana-programs/packages/tuktuk-sdk/src/transaction.ts

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
1-
import { CustomAccountResolver, Idl, IdlTypes, Program } from "@coral-xyz/anchor";
1+
import {
2+
CustomAccountResolver,
3+
Idl,
4+
IdlTypes,
5+
Program,
6+
} from "@coral-xyz/anchor";
27
import { Tuktuk } from "@helium/tuktuk-idls/lib/types/tuktuk";
38
import {
49
AccountMeta,
510
PublicKey,
611
TransactionInstruction,
712
} from "@solana/web3.js";
8-
import { customSignerKey, tuktukConfigKey } from "./pdas";
13+
import { customSignerKey, taskKey, tuktukConfigKey } from "./pdas";
914
import { getAssociatedTokenAddressSync } from "@solana/spl-token";
1015

1116
export type CompiledTransactionArgV0 =
@@ -17,7 +22,7 @@ export type CustomAccountResolverFactory<T extends Idl> = (
1722

1823
export function compileTransaction(
1924
instructions: TransactionInstruction[],
20-
signerSeeds: Buffer[][]
25+
signerSeeds: Buffer[][],
2126
): { transaction: CompiledTransactionArgV0; remainingAccounts: AccountMeta[] } {
2227
let pubkeysToMetadata: Record<
2328
string,
@@ -110,6 +115,25 @@ export function compileTransaction(
110115
};
111116
}
112117

118+
function nextAvailableTaskIds(taskBitmap: Buffer, n: number): number[] {
119+
const availableTaskIds: number[] = [];
120+
for (let byteIdx = 0; byteIdx < taskBitmap.length; byteIdx++) {
121+
const byte = taskBitmap[byteIdx];
122+
if (byte !== 0xff) {
123+
// If byte is not all 1s
124+
for (let bitIdx = 0; bitIdx < 8; bitIdx++) {
125+
if ((byte & (1 << bitIdx)) === 0) {
126+
availableTaskIds.push(byteIdx * 8 + bitIdx);
127+
if (availableTaskIds.length === n) {
128+
return availableTaskIds;
129+
}
130+
}
131+
}
132+
}
133+
}
134+
return availableTaskIds;
135+
}
136+
113137
export async function runTask({
114138
program,
115139
task,
@@ -121,8 +145,11 @@ export async function runTask({
121145
}) {
122146
const {
123147
taskQueue,
148+
freeTasks,
124149
transaction: { numRwSigners, numRoSigners, numRw, accounts, signerSeeds },
125150
} = await program.account.taskV0.fetch(task);
151+
const taskQueueAcc = await program.account.taskQueueV0.fetch(taskQueue);
152+
126153
const configAcc = await program.account.tuktukConfigV0.fetch(
127154
tuktukConfigKey()[0]
128155
);
@@ -137,15 +164,24 @@ export async function runTask({
137164
isSigner: false,
138165
};
139166
});
167+
const nextAvailable = nextAvailableTaskIds(
168+
taskQueueAcc.taskBitmap,
169+
freeTasks
170+
);
171+
const freeTasksAccounts = nextAvailable.map((id) => ({
172+
pubkey: taskKey(taskQueue, id)[0],
173+
isWritable: true,
174+
isSigner: false,
175+
}));
140176

141177
return program.methods
142178
.runTaskV0()
143179
.accounts({
144180
task,
145181
rewardsDestination: getAssociatedTokenAddressSync(
146182
configAcc.networkMint,
147-
rewardsDestinationWallet,
183+
rewardsDestinationWallet
148184
),
149185
})
150-
.remainingAccounts(remainingAccounts);
186+
.remainingAccounts([...remainingAccounts, ...freeTasksAccounts]);
151187
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
[package]
2+
name = "cpi-example"
3+
version = "0.1.0"
4+
description = "Created with Anchor"
5+
edition = "2021"
6+
7+
[lib]
8+
crate-type = ["cdylib", "lib"]
9+
name = "cpi_example"
10+
11+
[features]
12+
default = []
13+
cpi = ["no-entrypoint"]
14+
no-entrypoint = []
15+
no-idl = []
16+
no-log-ix-name = []
17+
idl-build = ["anchor-lang/idl-build", "anchor-spl/idl-build"]
18+
19+
[dependencies]
20+
anchor-lang = { workspace = true, features = ["init-if-needed"] }
21+
anchor-spl = { workspace = true }
22+
tuktuk-sdk = { path = "../../../tuktuk-sdk" }
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[target.bpfel-unknown-unknown.dependencies.std]
2+
features = []
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
use anchor_lang::prelude::*;
2+
use tuktuk_sdk::tuktuk::{tuktuk::program::Tuktuk, TaskQueueV0};
3+
4+
declare_id!("cpic9j9sjqvhn2ZX3mqcCgzHKCwiiBTyEszyCwN7MBC");
5+
6+
#[program]
7+
pub mod cpi_example {
8+
use anchor_lang::{solana_program::instruction::Instruction, InstructionData};
9+
use tuktuk_sdk::{
10+
compiled_transaction::*,
11+
tuktuk::{
12+
tuktuk::cpi::{accounts::QueueTaskV0, queue_task_v0},
13+
types::{QueueTaskArgsV0, TriggerV0},
14+
},
15+
};
16+
17+
use super::*;
18+
19+
pub fn schedule_next(ctx: Context<ScheduleNext>) -> Result<()> {
20+
let my_tx = CpiContext::new(
21+
ctx.accounts.system_program.to_account_info(),
22+
crate::__cpi_client_accounts_schedule_next::ScheduleNext {
23+
queue_authority: ctx.accounts.queue_authority.to_account_info(),
24+
system_program: ctx.accounts.system_program.to_account_info(),
25+
tuktuk_program: ctx.accounts.tuktuk_program.to_account_info(),
26+
task_queue: ctx.accounts.task_queue.to_account_info(),
27+
free_task_1: ctx.accounts.free_task_1.to_account_info(),
28+
},
29+
);
30+
// Only take the first 3 accounts. Tuktuk will pass the task queue and a free task account automatically.
31+
// We do this because we don't actually know what the task ID will be since its PDA depends on the available task ID.
32+
let account_metas = my_tx.accounts.to_account_metas(None)[..3].to_vec();
33+
let data = crate::instruction::ScheduleNext.data();
34+
let (compiled_tx, _) = compile_transaction(
35+
vec![Instruction {
36+
program_id: crate::ID,
37+
accounts: account_metas,
38+
data,
39+
}],
40+
vec![],
41+
)
42+
.unwrap();
43+
44+
queue_task_v0(
45+
CpiContext::new_with_signer(
46+
ctx.accounts.tuktuk_program.to_account_info(),
47+
QueueTaskV0 {
48+
payer: ctx.accounts.queue_authority.to_account_info(),
49+
queue_authority: ctx.accounts.queue_authority.to_account_info(),
50+
task_queue: ctx.accounts.task_queue.to_account_info(),
51+
task: ctx.accounts.free_task_1.to_account_info(),
52+
system_program: ctx.accounts.system_program.to_account_info(),
53+
},
54+
&[&[b"queue_authority".as_ref(), &[ctx.bumps.queue_authority]]],
55+
)
56+
.with_remaining_accounts(vec![
57+
ctx.accounts.queue_authority.to_account_info(),
58+
ctx.accounts.system_program.to_account_info(),
59+
ctx.accounts.tuktuk_program.to_account_info(),
60+
]),
61+
QueueTaskArgsV0 {
62+
id: ctx.accounts.task_queue.next_available_task_id().unwrap() as u16,
63+
// 5 seconds from now
64+
trigger: TriggerV0::Timestamp(Clock::get()?.unix_timestamp + 5),
65+
transaction: compiled_tx.into(),
66+
crank_reward: None,
67+
free_tasks: 1,
68+
},
69+
)?;
70+
Ok(())
71+
}
72+
}
73+
74+
#[derive(Accounts)]
75+
pub struct ScheduleNext<'info> {
76+
#[account(
77+
mut,
78+
seeds = [b"queue_authority".as_ref()],
79+
bump,
80+
)]
81+
pub queue_authority: AccountInfo<'info>,
82+
pub system_program: Program<'info, System>,
83+
pub tuktuk_program: Program<'info, Tuktuk>,
84+
pub task_queue: Account<'info, TaskQueueV0>,
85+
/// CHECK: free task account
86+
pub free_task_1: UncheckedAccount<'info>,
87+
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,6 @@ pub enum ErrorCode {
1616
TaskNotReady,
1717
#[msg("Task queue not empty")]
1818
TaskQueueNotEmpty,
19+
#[msg("Free task account not empty")]
20+
FreeTaskAccountNotEmpty,
1921
}

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ pub struct QueueTaskArgsV0 {
1212
pub trigger: TriggerV0,
1313
pub transaction: CompiledTransactionArgV0,
1414
pub crank_reward: Option<u64>,
15+
// Number of free tasks to append to the end of the accounts. This allows
16+
// you to easily add new tasks
17+
pub free_tasks: u8,
1518
}
1619

1720
#[derive(AnchorSerialize, AnchorDeserialize, Clone, Default)]
@@ -32,7 +35,7 @@ pub struct CompiledTransactionArgV0 {
3235

3336
#[derive(Accounts)]
3437
#[instruction(args: QueueTaskArgsV0)]
35-
pub struct QueuetaskV0<'info> {
38+
pub struct QueueTaskV0<'info> {
3639
#[account(mut)]
3740
pub payer: Signer<'info>,
3841
pub queue_authority: Signer<'info>,
@@ -54,8 +57,9 @@ pub struct QueuetaskV0<'info> {
5457
pub system_program: Program<'info, System>,
5558
}
5659

57-
pub fn handler(ctx: Context<QueuetaskV0>, args: QueueTaskArgsV0) -> Result<()> {
60+
pub fn handler(ctx: Context<QueueTaskV0>, args: QueueTaskArgsV0) -> Result<()> {
5861
ctx.accounts.task.set_inner(TaskV0 {
62+
free_tasks: args.free_tasks,
5963
task_queue: ctx.accounts.task_queue.key(),
6064
id: args.id,
6165
trigger: args.trigger,

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

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,9 +93,22 @@ pub fn handler(ctx: Context<RunTaskV0>) -> Result<()> {
9393
ErrorCode::InvalidAccount
9494
);
9595
}
96+
97+
// Validate that all free task accounts are empty
98+
let free_tasks_start_index = transaction.accounts.len();
99+
for i in 0..ctx.accounts.task.free_tasks {
100+
let free_task_index = free_tasks_start_index + i as usize;
101+
let free_task_account = &ctx.remaining_accounts[free_task_index];
102+
require!(
103+
free_task_account.data_is_empty(),
104+
ErrorCode::FreeTaskAccountNotEmpty
105+
);
106+
}
107+
96108
for ix in &transaction.instructions {
97109
let mut accounts = Vec::new();
98110
let mut account_infos = Vec::new();
111+
99112
for i in &ix.accounts {
100113
let acct = ctx.remaining_accounts[*i as usize].clone();
101114
accounts.push(acct.clone());
@@ -105,6 +118,25 @@ pub fn handler(ctx: Context<RunTaskV0>) -> Result<()> {
105118
is_writable: acct.is_writable,
106119
})
107120
}
121+
122+
// Add task_queue and free tasks at the end
123+
account_infos.push(AccountMeta {
124+
pubkey: ctx.accounts.task_queue.key(),
125+
is_signer: false,
126+
is_writable: true,
127+
});
128+
129+
// Add free tasks accounts, starting from the correct index
130+
for i in 0..ctx.accounts.task.free_tasks {
131+
let free_task_index = free_tasks_start_index + i as usize;
132+
let acct = ctx.remaining_accounts[free_task_index].clone();
133+
account_infos.push(AccountMeta {
134+
pubkey: acct.key(),
135+
is_signer: false,
136+
is_writable: true,
137+
});
138+
}
139+
108140
solana_program::program::invoke_signed(
109141
&Instruction {
110142
program_id: *ctx.remaining_accounts[ix.program_id_index as usize].key,

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ pub mod tuktuk {
2626
initialize_task_queue_v0::handler(ctx, args)
2727
}
2828

29-
pub fn queue_task_v0(ctx: Context<QueuetaskV0>, args: QueueTaskArgsV0) -> Result<()> {
29+
pub fn queue_task_v0(ctx: Context<QueueTaskV0>, args: QueueTaskArgsV0) -> Result<()> {
3030
queue_task_v0::handler(ctx, args)
3131
}
3232

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,9 @@ pub struct TaskV0 {
8989
pub transaction: CompiledTransactionV0,
9090
pub queued_at: i64,
9191
pub bump_seed: u8,
92+
// Number of free tasks to append to the end of the accounts. This allows
93+
// you to easily add new tasks
94+
pub free_tasks: u8,
9295
}
9396

9497
#[derive(AnchorSerialize, AnchorDeserialize, Clone, Default)]

0 commit comments

Comments
 (0)