Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
162 changes: 161 additions & 1 deletion src/__tests__/tasks.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
* 4. cancelTask and other task function types are exported
*/

import { describe, it, expect } from "vitest";
import { describe, it, expect, vi } from "vitest";
import { PublicKey, Keypair } from "@solana/web3.js";
import {
TaskState,
Expand All @@ -18,7 +18,11 @@ import {
deriveTaskPda,
deriveClaimPda,
deriveTaskValidationConfigPda,
deriveTaskJobSpecPda,
deriveTaskAttestorConfigPda,
setTaskJobSpec,
claimTaskWithJobSpec,
getTaskJobSpec,
deriveTaskSubmissionPda,
deriveTaskValidationVotePda,
deriveEscrowPda,
Expand Down Expand Up @@ -270,6 +274,20 @@ describe("PDA derivation", () => {
});
});

describe("deriveTaskJobSpecPda", () => {
it('uses correct seeds: ["task_job_spec", taskPda]', () => {
const taskPda = Keypair.generate().publicKey;

const result = deriveTaskJobSpecPda(taskPda);

const [expected] = PublicKey.findProgramAddressSync(
[SEEDS.TASK_JOB_SPEC, taskPda.toBuffer()],
PROGRAM_ID,
);
expect(result.equals(expected)).toBe(true);
});
});

describe("deriveTaskAttestorConfigPda", () => {
it('uses correct seeds: ["task_attestor", taskPda]', () => {
const taskPda = Keypair.generate().publicKey;
Expand Down Expand Up @@ -339,3 +357,145 @@ describe("calculateEscrowFee", () => {
expect(() => calculateEscrowFee(Number.MAX_SAFE_INTEGER)).toThrow();
});
});


describe("task job spec helpers", () => {
it("sets task job spec metadata with the derived PDA", async () => {
const creator = Keypair.generate();
const taskPda = Keypair.generate().publicKey;
const jobSpecHash = new Uint8Array(32).fill(7);
const jobSpecUri = "agenc://job-spec/sha256/test";
const rpc = vi.fn().mockResolvedValue("set-job-spec-tx");
const signers = vi.fn().mockReturnValue({ rpc });
const preInstructions = vi.fn().mockReturnValue({ signers });
const accountsPartial = vi.fn().mockReturnValue({ preInstructions });
const setTaskJobSpecMethod = vi.fn().mockReturnValue({ accountsPartial });
const program = {
programId: PROGRAM_ID,
methods: { setTaskJobSpec: setTaskJobSpecMethod },
} as any;
const connection = {
confirmTransaction: vi.fn().mockResolvedValue({}),
} as any;

const result = await setTaskJobSpec(connection, program, creator, taskPda, {
jobSpecHash,
jobSpecUri,
});

const expectedPda = deriveTaskJobSpecPda(taskPda);
expect(result.txSignature).toBe("set-job-spec-tx");
expect(result.taskJobSpecPda.equals(expectedPda)).toBe(true);
expect(setTaskJobSpecMethod).toHaveBeenCalledWith(
Array.from(jobSpecHash),
jobSpecUri,
);
expect(accountsPartial).toHaveBeenCalledWith(
expect.objectContaining({
task: taskPda,
taskJobSpec: expectedPda,
creator: creator.publicKey,
}),
);
expect(connection.confirmTransaction).toHaveBeenCalledWith(
"set-job-spec-tx",
"confirmed",
);
});

it("claims task with the verified task job spec PDA", async () => {
const worker = Keypair.generate();
const workerAgentId = new Uint8Array(32).fill(5);
const taskPda = Keypair.generate().publicKey;
const rpc = vi.fn().mockResolvedValue("claim-with-job-spec-tx");
const signers = vi.fn().mockReturnValue({ rpc });
const preInstructions = vi.fn().mockReturnValue({ signers });
const accountsPartial = vi.fn().mockReturnValue({ preInstructions });
const claimTaskWithJobSpecMethod = vi
.fn()
.mockReturnValue({ accountsPartial });
const program = {
programId: PROGRAM_ID,
methods: { claimTaskWithJobSpec: claimTaskWithJobSpecMethod },
} as any;
const connection = {
confirmTransaction: vi.fn().mockResolvedValue({}),
} as any;

const result = await claimTaskWithJobSpec(
connection,
program,
worker,
workerAgentId,
taskPda,
);

const expectedTaskJobSpecPda = deriveTaskJobSpecPda(taskPda);
const [expectedWorkerAgentPda] = PublicKey.findProgramAddressSync(
[SEEDS.AGENT, workerAgentId],
PROGRAM_ID,
);
const expectedClaimPda = deriveClaimPda(taskPda, expectedWorkerAgentPda);

expect(result.txSignature).toBe("claim-with-job-spec-tx");
expect(result.taskJobSpecPda.equals(expectedTaskJobSpecPda)).toBe(true);
expect(claimTaskWithJobSpecMethod).toHaveBeenCalledOnce();
expect(accountsPartial).toHaveBeenCalledWith(
expect.objectContaining({
task: taskPda,
taskJobSpec: expectedTaskJobSpecPda,
claim: expectedClaimPda,
worker: expectedWorkerAgentPda,
authority: worker.publicKey,
}),
);
expect(connection.confirmTransaction).toHaveBeenCalledWith(
"claim-with-job-spec-tx",
"confirmed",
);
});

it("reads task job spec metadata and normalizes account values", async () => {
const taskPda = Keypair.generate().publicKey;
const creator = Keypair.generate().publicKey;
const hash = new Uint8Array(32).fill(9);
const fetch = vi.fn().mockResolvedValue({
task: taskPda,
creator,
jobSpecHash: Array.from(hash),
jobSpecUri: "agenc://job-spec/sha256/read",
createdAt: { toNumber: () => 11 },
updatedAt: { toNumber: () => 22 },
bump: 3,
});
const program = {
programId: PROGRAM_ID,
account: { taskJobSpec: { fetch } },
} as any;

const pointer = await getTaskJobSpec(program, taskPda);

expect(fetch).toHaveBeenCalledWith(deriveTaskJobSpecPda(taskPda));
expect(pointer?.task.equals(taskPda)).toBe(true);
expect(pointer?.creator.equals(creator)).toBe(true);
expect(pointer?.jobSpecHash).toEqual(hash);
expect(pointer?.jobSpecUri).toBe("agenc://job-spec/sha256/read");
expect(pointer?.createdAt).toBe(11);
expect(pointer?.updatedAt).toBe(22);
expect(pointer?.bump).toBe(3);
});

it("returns null when task job spec metadata account does not exist", async () => {
const taskPda = Keypair.generate().publicKey;
const program = {
programId: PROGRAM_ID,
account: {
taskJobSpec: {
fetch: vi.fn().mockRejectedValue(new Error("Account does not exist")),
},
},
} as any;

await expect(getTaskJobSpec(program, taskPda)).resolves.toBeNull();
});
});
7 changes: 7 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,9 @@ export const RECOMMENDED_CU_CREATE_DEPENDENT_TASK = 60_000;
/** CU budget for claim_task instruction */
export const RECOMMENDED_CU_CLAIM_TASK = 45_000;

/** CU budget for claim_task_with_job_spec instruction */
export const RECOMMENDED_CU_CLAIM_TASK_WITH_JOB_SPEC = 50_000;

/** CU budget for expire_claim instruction */
export const RECOMMENDED_CU_EXPIRE_CLAIM = 40_000;

Expand All @@ -130,6 +133,9 @@ export const RECOMMENDED_CU_COMPLETE_TASK_PRIVATE = 200_000;
/** CU budget for configure_task_validation instruction */
export const RECOMMENDED_CU_CONFIGURE_TASK_VALIDATION = 40_000;

/** CU budget for set_task_job_spec instruction */
export const RECOMMENDED_CU_SET_TASK_JOB_SPEC = 30_000;

/** CU budget for submit_task_result instruction */
export const RECOMMENDED_CU_SUBMIT_TASK_RESULT = 70_000;

Expand Down Expand Up @@ -250,6 +256,7 @@ export const SEEDS = {
TASK: Buffer.from("task"),
CLAIM: Buffer.from("claim"),
TASK_VALIDATION: Buffer.from("task_validation"),
TASK_JOB_SPEC: Buffer.from("task_job_spec"),
TASK_ATTESTOR: Buffer.from("task_attestor"),
TASK_SUBMISSION: Buffer.from("task_submission"),
TASK_VALIDATION_VOTE: Buffer.from("task_validation_vote"),
Expand Down
8 changes: 8 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,9 @@ export {
export {
createTask,
createDependentTask,
setTaskJobSpec,
claimTask,
claimTaskWithJobSpec,
expireClaim,
completeTask,
completeTaskPrivate,
Expand All @@ -50,11 +52,13 @@ export {
validateTaskResult,
cancelTask,
getTask,
getTaskJobSpec,
getTasksByCreator,
getTaskLifecycleSummary,
deriveTaskPda,
deriveClaimPda,
deriveTaskValidationConfigPda,
deriveTaskJobSpecPda,
deriveTaskAttestorConfigPda,
deriveTaskSubmissionPda,
deriveTaskValidationVotePda,
Expand All @@ -64,6 +68,9 @@ export {
calculateEscrowFee,
TaskParams,
DependentTaskParams,
TaskCreationResult,
SetTaskJobSpecParams,
TaskJobSpecPointer,
TaskState,
TaskValidationMode,
TaskSubmissionStatus,
Expand Down Expand Up @@ -208,6 +215,7 @@ export {
RECOMMENDED_CU_CREATE_TASK,
RECOMMENDED_CU_CREATE_DEPENDENT_TASK,
RECOMMENDED_CU_CLAIM_TASK,
RECOMMENDED_CU_CLAIM_TASK_WITH_JOB_SPEC,
RECOMMENDED_CU_COMPLETE_TASK,
RECOMMENDED_CU_COMPLETE_TASK_PRIVATE,
RECOMMENDED_CU_CONFIGURE_TASK_VALIDATION,
Expand Down
Loading
Loading