Skip to content

Commit

Permalink
feat(xc-admin): add support for deserializing lazer instructions in x…
Browse files Browse the repository at this point in the history
…c-admin (#2246)

* feat: add support for deserializing lazer instructions in xc-admin

* fix error

* add test
  • Loading branch information
cctdaniel authored Jan 13, 2025
1 parent 2faddf9 commit cbfc885
Show file tree
Hide file tree
Showing 4 changed files with 542 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
import {
PublicKey,
TransactionInstruction,
SystemProgram,
} from "@solana/web3.js";
import { LazerMultisigInstruction } from "../multisig_transaction/LazerMultisigInstruction";
import {
MultisigInstructionProgram,
UNRECOGNIZED_INSTRUCTION,
} from "../multisig_transaction";

describe("LazerMultisigInstruction", () => {
const mockProgramId = new PublicKey(
"pytd2yyk641x7ak7mkaasSJVXh6YYZnC7wTmtgAyxPt"
);
const systemProgram = SystemProgram.programId;

// Generate reusable keypairs for tests
const topAuthority = PublicKey.unique();
const storage = PublicKey.unique();
const payer = PublicKey.unique();

// Test recognized instruction
test("fromInstruction should decode update instruction", () => {
const instructionData = Buffer.from([
// Anchor discriminator for update (from IDL)
219,
200,
88,
176,
158,
63,
253,
127,
// trusted_signer (pubkey - 32 bytes)
...Array(32).fill(1),
// expires_at (i64 - 8 bytes)
42,
0,
0,
0,
0,
0,
0,
0,
]);

const keys = [
{
pubkey: topAuthority,
isSigner: true,
isWritable: false,
},
{
pubkey: storage,
isSigner: false,
isWritable: true,
},
];

const instruction = new TransactionInstruction({
programId: mockProgramId,
keys,
data: instructionData,
});

const lazerInstruction =
LazerMultisigInstruction.fromInstruction(instruction);

expect(lazerInstruction.name).toBe("update");
expect(lazerInstruction.args).toBeDefined();
expect(lazerInstruction.args.trustedSigner).toBeDefined();
expect(lazerInstruction.args.expiresAt).toBeDefined();
expect(lazerInstruction.accounts).toBeDefined();
expect(lazerInstruction.accounts.named.topAuthority).toBeDefined();
expect(lazerInstruction.accounts.named.storage).toBeDefined();
});

// Test unrecognized instruction
test("fromInstruction should handle unrecognized instruction", () => {
const unrecognizedData = Buffer.from([1, 2, 3, 4]);
const keys = [
{
pubkey: topAuthority,
isSigner: false,
isWritable: true,
},
];

const instruction = new TransactionInstruction({
programId: mockProgramId,
keys,
data: unrecognizedData,
});

const lazerInstruction =
LazerMultisigInstruction.fromInstruction(instruction);

expect(lazerInstruction.name).toBe(UNRECOGNIZED_INSTRUCTION);
expect(lazerInstruction.args).toEqual({ data: unrecognizedData });
expect(lazerInstruction.accounts.remaining).toEqual(keys);
});

// Test initialize instruction
test("fromInstruction should decode initialize instruction", () => {
const instructionData = Buffer.from([
// Anchor discriminator for initialize (from IDL)
175,
175,
109,
31,
13,
152,
155,
237,
// top_authority (pubkey - 32 bytes)
...Array(32).fill(2),
// treasury (pubkey - 32 bytes)
...Array(32).fill(3),
]);

const keys = [
{
pubkey: payer,
isSigner: true,
isWritable: true,
},
{
pubkey: storage,
isSigner: false,
isWritable: true,
},
{
pubkey: systemProgram,
isSigner: false,
isWritable: false,
},
];

const instruction = new TransactionInstruction({
programId: mockProgramId,
keys,
data: instructionData,
});

const lazerInstruction =
LazerMultisigInstruction.fromInstruction(instruction);

expect(lazerInstruction.name).toBe("initialize");
expect(lazerInstruction.args).toBeDefined();
expect(lazerInstruction.args.topAuthority).toBeDefined();
expect(lazerInstruction.args.treasury).toBeDefined();
expect(lazerInstruction.accounts).toBeDefined();
expect(lazerInstruction.accounts.named.payer).toBeDefined();
expect(lazerInstruction.accounts.named.storage).toBeDefined();
expect(lazerInstruction.accounts.named.systemProgram).toBeDefined();
});

// Test program field
test("should have correct program type", () => {
const instruction = new TransactionInstruction({
programId: mockProgramId,
keys: [],
data: Buffer.from([]),
});

const lazerInstruction =
LazerMultisigInstruction.fromInstruction(instruction);
expect(lazerInstruction.program).toBe(MultisigInstructionProgram.Lazer);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import {
MultisigInstruction,
MultisigInstructionProgram,
UNRECOGNIZED_INSTRUCTION,
} from "./index";
import { AnchorAccounts, resolveAccountNames } from "./anchor";
import { PublicKey, TransactionInstruction } from "@solana/web3.js";
import { Idl, BorshInstructionCoder } from "@coral-xyz/anchor";
import lazerIdl from "./idl/lazer.json";

export const LAZER_PROGRAM_ID = new PublicKey(
"pytd2yyk641x7ak7mkaasSJVXh6YYZnC7wTmtgAyxPt"
);

export class LazerMultisigInstruction implements MultisigInstruction {
readonly program = MultisigInstructionProgram.Lazer;
readonly name: string;
readonly args: { [key: string]: any };
readonly accounts: AnchorAccounts;

constructor(
name: string,
args: { [key: string]: any },
accounts: AnchorAccounts
) {
this.name = name;
this.args = args;
this.accounts = accounts;
}

static fromInstruction(
instruction: TransactionInstruction
): LazerMultisigInstruction {
// TODO: This is a hack to transform the IDL to be compatible with the anchor version we are using, we can't upgrade anchor to 0.30.1 because then the existing idls will break
const idl = lazerIdl as Idl;

const coder = new BorshInstructionCoder(idl);

const deserializedData = coder.decode(instruction.data);

if (deserializedData) {
return new LazerMultisigInstruction(
deserializedData.name,
deserializedData.data,
resolveAccountNames(idl, deserializedData.name, instruction)
);
} else {
return new LazerMultisigInstruction(
UNRECOGNIZED_INSTRUCTION,
{ data: instruction.data },
{ named: {}, remaining: instruction.keys }
);
}
}
}
Loading

0 comments on commit cbfc885

Please sign in to comment.