Skip to content

Commit

Permalink
Switch to entirely sol based
Browse files Browse the repository at this point in the history
  • Loading branch information
ChewingGlass committed Dec 13, 2024
1 parent 7631a98 commit 3c681d6
Show file tree
Hide file tree
Showing 18 changed files with 186 additions and 519 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,15 @@ Tuktuk is a permissionless crank service. If you have a Solana smart contract en

Tuktuk's architecture allows for crankers to run a simple rust util that requires only a working solana RPC url and very minimal dependencies. There is no dependency on geyser, yellowstone, or any other indexing service.

Creators of Task Queues set their payment per-crank turn in $HNT (Houlala Network Token). Crankers that run the tasks are paid out in $HNT for each crank they complete. There is a minimum deposit of 1 $HNT to create a task queue to discourage spam.
Creators of Task Queues set their payment per-crank turn in $SOL. Crankers that run the tasks are paid out in $SOL for each crank they complete. There is a minimum deposit of 1 $SOL to create a task queue to discourage spam.

## Usage

Clone this repo and run `cargo build -p tuktuk-cli` to get the command line interface. It will be in the `target/debug/tuktuk` directory.

### Create a task queue

First, you'll need to get some $HNT to fund the task queue. You can get $HNT from [Jupiter Aggregator](https://www.jup.ag/swap/USDC-hntyVP6YFm1Hg25TN9WGLqM12b8TQmcknKrdu1oxWux).
First, you'll need to get some $SOL to fund the task queue. You can get $SOL from [Jupiter Aggregator](https://www.jup.ag/swap/USDC-hntyVP6YFm1Hg25TN9WGLqM12b8TQmcknKrdu1oxWux).

Next, create a task queue. A task queue has a default crank reward that will be used for all tasks in the queue, but each task can override this reward. Since crankers pay sol (and possibly priority fees) for each crank, the crank reward should be higher than the cost of a crank or crankers will not be incentivized to run your task.

Expand Down
18 changes: 6 additions & 12 deletions solana-programs/packages/tuktuk-sdk/src/transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ import {
Program,
} from "@coral-xyz/anchor";
import { Tuktuk } from "@helium/tuktuk-idls/lib/types/tuktuk";
import { getAssociatedTokenAddressSync } from "@solana/spl-token";
import {
AccountMeta,
PublicKey,
TransactionInstruction,
} from "@solana/web3.js";
import { customSignerKey, taskKey, tuktukConfigKey } from "./pdas";
import { getAssociatedTokenAddressSync } from "@solana/spl-token";
import { taskKey, tuktukConfigKey } from "./pdas";

export type CompiledTransactionV0 =
IdlTypes<Tuktuk>["compiledTransactionV0"];
Expand Down Expand Up @@ -142,21 +142,18 @@ function nextAvailableTaskIds(taskBitmap: Buffer, n: number): number[] {
export async function runTask({
program,
task,
rewardsDestinationWallet,
crankTurner,
}: {
program: Program<Tuktuk>;
task: PublicKey;
rewardsDestinationWallet: PublicKey;
crankTurner: PublicKey;
}) {
const {
taskQueue,
freeTasks,
transaction: { numRwSigners, numRoSigners, numRw, accounts, signerSeeds },
transaction: { numRwSigners, numRoSigners, numRw, accounts },
} = await program.account.taskV0.fetch(task);
const taskQueueAcc = await program.account.taskQueueV0.fetch(taskQueue);
const configAcc = await program.account.tuktukConfigV0.fetch(
tuktukConfigKey()[0]
);

const remainingAccounts = accounts.map((acc, index) => {
return {
Expand Down Expand Up @@ -184,10 +181,7 @@ export async function runTask({
})
.accounts({
task,
rewardsDestination: getAssociatedTokenAddressSync(
configAcc.networkMint,
rewardsDestinationWallet
),
crankTurner,
})
.remainingAccounts([...remainingAccounts, ...freeTasksAccounts]);
}
7 changes: 4 additions & 3 deletions solana-programs/programs/cpi-example/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ pub mod cpi_example {

use super::*;

pub fn schedule(ctx: Context<ScheduleFirst>, task_id: u16) -> Result<()> {
pub fn schedule(ctx: Context<Schedule>, task_id: u16) -> Result<()> {
msg!("Scheduling with a PDA queue authority");
let (compiled_tx, _) = compile_transaction(
vec![Instruction {
Expand Down Expand Up @@ -92,9 +92,10 @@ pub struct RecurringTask<'info> {
}

#[derive(Accounts)]
pub struct ScheduleFirst<'info> {
pub struct Schedule<'info> {
#[account(mut)]
pub task_queue: Account<'info, TaskQueueV0>,
/// CHECK: Don't need to parse this account, just using it in CPI
pub task_queue: UncheckedAccount<'info>,
/// CHECK: Initialized in CPI
#[account(mut)]
pub task: AccountInfo<'info>,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,9 @@
use anchor_lang::prelude::*;
use anchor_spl::{
associated_token::AssociatedToken,
token::{close_account, transfer, CloseAccount, Mint, Token, TokenAccount, Transfer},
};

use super::hash_name;
use crate::{
error::ErrorCode,
state::{TaskQueueNameMappingV0, TaskQueueV0, TuktukConfigV0},
task_queue_seeds,
};

#[derive(Accounts)]
Expand All @@ -19,12 +14,8 @@ pub struct CloseTaskQueueV0<'info> {
#[account(mut)]
pub payer: Signer<'info>,
pub update_authority: Signer<'info>,
#[account(
mut,
has_one = network_mint,
)]
#[account(mut)]
pub tuktuk_config: Box<Account<'info, TuktukConfigV0>>,
pub network_mint: Box<Account<'info, Mint>>,
#[account(
mut,
close = refund,
Expand All @@ -33,19 +24,6 @@ pub struct CloseTaskQueueV0<'info> {
constraint = task_queue.task_bitmap.iter().all(|&bit| bit == 0) @ ErrorCode::TaskQueueNotEmpty,
)]
pub task_queue: Box<Account<'info, TaskQueueV0>>,
#[account(
mut,
associated_token::mint = network_mint,
associated_token::authority = task_queue,
)]
pub rewards_source: Box<Account<'info, TokenAccount>>,
#[account(
init_if_needed,
payer = payer,
associated_token::mint = network_mint,
associated_token::authority = refund,
)]
pub rewards_refund: Box<Account<'info, TokenAccount>>,
#[account(
mut,
close = refund,
Expand All @@ -57,36 +35,13 @@ pub struct CloseTaskQueueV0<'info> {
bump = task_queue_name_mapping.bump_seed
)]
pub task_queue_name_mapping: Account<'info, TaskQueueNameMappingV0>,
pub token_program: Program<'info, Token>,
pub system_program: Program<'info, System>,
pub associated_token_program: Program<'info, AssociatedToken>,
}

pub fn handler(ctx: Context<CloseTaskQueueV0>) -> Result<()> {
if ctx.accounts.task_queue.id == ctx.accounts.tuktuk_config.min_task_queue_id {
ctx.accounts.tuktuk_config.min_task_queue_id = ctx.accounts.task_queue.id + 1;
}

transfer(
CpiContext::new_with_signer(
ctx.accounts.token_program.to_account_info(),
Transfer {
from: ctx.accounts.rewards_source.to_account_info(),
to: ctx.accounts.rewards_refund.to_account_info(),
authority: ctx.accounts.task_queue.to_account_info(),
},
&[task_queue_seeds!(ctx.accounts.task_queue)],
),
ctx.accounts.rewards_source.amount,
)?;
close_account(CpiContext::new_with_signer(
ctx.accounts.token_program.to_account_info(),
CloseAccount {
account: ctx.accounts.rewards_source.to_account_info(),
destination: ctx.accounts.refund.to_account_info(),
authority: ctx.accounts.task_queue.to_account_info(),
},
&[task_queue_seeds!(ctx.accounts.task_queue)],
))?;
Ok(())
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
use anchor_lang::{prelude::*, solana_program::hash::hash};
use anchor_lang::{
prelude::*,
solana_program::hash::hash,
system_program::{transfer, Transfer},
};
use anchor_spl::{
associated_token::AssociatedToken,
token::{Mint, Token, TokenAccount},
token::{Token, TokenAccount},
};

use crate::state::{TaskQueueNameMappingV0, TaskQueueV0, TuktukConfigV0};
Expand All @@ -10,7 +14,7 @@ pub const TESTING: bool = std::option_env!("TESTING").is_some();

#[derive(AnchorSerialize, AnchorDeserialize, Clone, Default)]
pub struct InitializeTaskQueueArgsV0 {
pub crank_reward: u64,
pub min_crank_reward: u64,
pub name: String,
pub capacity: u16,
}
Expand All @@ -24,12 +28,8 @@ pub fn hash_name(name: &str) -> [u8; 32] {
pub struct InitializeTaskQueueV0<'info> {
#[account(mut)]
pub payer: Signer<'info>,
#[account(
mut,
has_one = network_mint
)]
#[account(mut)]
pub tuktuk_config: Box<Account<'info, TuktukConfigV0>>,
pub network_mint: Box<Account<'info, Mint>>,
/// CHECK: Is getting set by signer
pub update_authority: UncheckedAccount<'info>,
/// CHECK: Is getting set by signer
Expand All @@ -54,29 +54,30 @@ pub struct InitializeTaskQueueV0<'info> {
bump
)]
pub task_queue_name_mapping: Box<Account<'info, TaskQueueNameMappingV0>>,
#[account(
init_if_needed,
payer = payer,
associated_token::mint = network_mint,
associated_token::authority = task_queue,
constraint = rewards_source.amount >= tuktuk_config.min_deposit
)]
pub rewards_source: Box<Account<'info, TokenAccount>>,
pub token_program: Program<'info, Token>,
pub associated_token_program: Program<'info, AssociatedToken>,
pub system_program: Program<'info, System>,
}

pub fn handler(ctx: Context<InitializeTaskQueueV0>, args: InitializeTaskQueueArgsV0) -> Result<()> {
require_gte!(32, args.name.len());

transfer(
CpiContext::new(
ctx.accounts.system_program.to_account_info(),
Transfer {
from: ctx.accounts.payer.to_account_info(),
to: ctx.accounts.task_queue.to_account_info(),
},
),
ctx.accounts.tuktuk_config.min_deposit,
)?;

ctx.accounts.task_queue.set_inner(TaskQueueV0 {
rewards_source: ctx.accounts.rewards_source.key(),
id: ctx.accounts.tuktuk_config.next_task_queue_id,
tuktuk_config: ctx.accounts.tuktuk_config.key(),
uncollected_protocol_fees: 0,
update_authority: ctx.accounts.update_authority.key(),
queue_authority: ctx.accounts.queue_authority.key(),
default_crank_reward: args.crank_reward,
min_crank_reward: args.min_crank_reward,
capacity: args.capacity,
task_bitmap: vec![0; ((args.capacity + 7) / 8) as usize],
name: args.name.clone(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ pub struct InitializeTuktukConfigV0<'info> {
constraint = TESTING || approver.key() == APPROVER
)]
pub approver: Signer<'info>,
pub network_mint: Account<'info, Mint>,
/// CHECK: Is getting set by signer
pub authority: UncheckedAccount<'info>,
#[account(
Expand All @@ -39,7 +38,6 @@ pub fn handler(
args: InitializeTuktukConfigArgsV0,
) -> Result<()> {
ctx.accounts.tuktuk_config.set_inner(TuktukConfigV0 {
network_mint: ctx.accounts.network_mint.key(),
authority: ctx.accounts.authority.key(),
bump_seed: ctx.bumps.tuktuk_config,
min_task_queue_id: 0,
Expand Down
28 changes: 24 additions & 4 deletions solana-programs/programs/tuktuk/src/instructions/queue_task_v0.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
use anchor_lang::prelude::*;
use anchor_lang::{
prelude::*,
system_program::{transfer, Transfer},
};

use crate::{
error::ErrorCode,
Expand Down Expand Up @@ -44,6 +47,24 @@ pub struct QueueTaskV0<'info> {
}

pub fn handler(ctx: Context<QueueTaskV0>, args: QueueTaskArgsV0) -> Result<()> {
let crank_reward = args
.crank_reward
.unwrap_or(ctx.accounts.task_queue.min_crank_reward);
require_gte!(crank_reward, ctx.accounts.task_queue.min_crank_reward);

let rented_amount = ctx.accounts.task.to_account_info().lamports();

transfer(
CpiContext::new(
ctx.accounts.system_program.to_account_info(),
Transfer {
from: ctx.accounts.payer.to_account_info(),
to: ctx.accounts.task.to_account_info(),
},
),
crank_reward,
)?;

let mut transaction = args.transaction.clone();
transaction
.accounts
Expand All @@ -53,9 +74,8 @@ pub fn handler(ctx: Context<QueueTaskV0>, args: QueueTaskArgsV0) -> Result<()> {
task_queue: ctx.accounts.task_queue.key(),
id: args.id,
trigger: args.trigger,
crank_reward: args
.crank_reward
.unwrap_or(ctx.accounts.task_queue.default_crank_reward),
rent_amount: rented_amount,
crank_reward,
rent_refund: ctx.accounts.payer.key(),
transaction,
bump_seed: ctx.bumps.task,
Expand Down
Loading

0 comments on commit 3c681d6

Please sign in to comment.