/* eslint-disable no-unused-vars */ import "mocha"; import { ixnsDeepEqual, ixnsEqual, TransactionObject, TransactionObjectOptions, } from "../src/index.js"; import { setupTest, TestContext } from "./utils.js"; import { AccountMeta, Connection, Keypair, NONCE_ACCOUNT_LENGTH, NonceAccount, NonceInformation, PublicKey, RpcResponseAndContext, SystemProgram, TransactionInstruction, } from "@solana/web3.js"; import assert from "assert"; const getNonceAccount = async ( connection: Connection, pubkey: PublicKey ): Promise> => { const nonceInfoResponse = await connection.getNonceAndContext(pubkey); if (nonceInfoResponse.value === null) { throw new Error(`NonceAccount not found`); } return nonceInfoResponse as RpcResponseAndContext; }; const getNonceIxn = ( connection: Connection, payer: PublicKey, pubkey: PublicKey ): TransactionInstruction => { return SystemProgram.nonceAdvance({ noncePubkey: pubkey, authorizedPubkey: payer, }); }; const getNonceInfo = async ( connection: Connection, payer: PublicKey, pubkey: PublicKey ): Promise => { const advanceNonce = getNonceIxn(connection, payer, pubkey); const nonceAccount = await getNonceAccount(connection, pubkey); return { nonce: nonceAccount.value.nonce, nonceInstruction: advanceNonce, }; }; const createDummyIxn = ( numKeys: number, dataLen = 0 ): TransactionInstruction => { return new TransactionInstruction({ keys: Array.from(new Array(numKeys)).map((): AccountMeta => { return { isSigner: false, isWritable: false, pubkey: PublicKey.default, }; }), programId: PublicKey.default, data: Buffer.from(new Array(dataLen + 1).join("X")), }); }; describe("TransactionObject Tests", () => { let ctx: TestContext; const nonceAccountKeypair = Keypair.generate(); let nonceAccount: NonceAccount; let nonceInfo: NonceInformation; before(async () => { ctx = await setupTest(); const nonceInitTxn = SystemProgram.createNonceAccount({ fromPubkey: ctx.payer.publicKey, noncePubkey: nonceAccountKeypair.publicKey, authorizedPubkey: ctx.payer.publicKey, lamports: await ctx.program.connection.getMinimumBalanceForRentExemption( NONCE_ACCOUNT_LENGTH ), }); const nonceInit = new TransactionObject( ctx.payer.publicKey, nonceInitTxn.instructions, [nonceAccountKeypair] ); await ctx.program.signAndSend(nonceInit); const nonceAccountResponse = await getNonceAccount( ctx.program.connection, nonceAccountKeypair.publicKey ); nonceAccount = nonceAccountResponse.value; nonceInfo = await getNonceInfo( ctx.program.connection, ctx.payer.publicKey, nonceAccountKeypair.publicKey ); }); it("Compares two instructions for equality", async () => { const setComputeIxn1 = TransactionObject.getComputeUnitLimitIxn(100000)!; const setComputeIxn2 = TransactionObject.getComputeUnitLimitIxn(100000)!; assert( ixnsEqual(setComputeIxn1, setComputeIxn2), `Expected instructions to be equal` ); const advanceNonce = getNonceIxn( ctx.program.connection, ctx.payer.publicKey, nonceAccountKeypair.publicKey ); const nonceInfo = await getNonceInfo( ctx.program.connection, ctx.payer.publicKey, nonceAccountKeypair.publicKey ); assert( ixnsEqual(advanceNonce, nonceInfo.nonceInstruction), `Expected instructions to be equal` ); }); it("Fails to add a duplicate nonce ixn", async () => { const advanceNonce = getNonceIxn( ctx.program.connection, ctx.payer.publicKey, nonceAccountKeypair.publicKey ); const txn = new TransactionObject(ctx.payer.publicKey, [advanceNonce], [], { enableDurableNonce: true, }); assert( txn.ixns.length === 1, `Transaction Object length mismatch, expected ${1}, received ${ txn.ixns.length }` ); }); it("Creates a txn object with options", async () => { const ixns = [ createDummyIxn(5), createDummyIxn(3, 64), createDummyIxn(8, 32), createDummyIxn(2), ]; const txn = new TransactionObject(ctx.payer.publicKey, ixns, [], { enableDurableNonce: true, computeUnitLimit: 200000, computeUnitPrice: 10000, }); assert( txn.length === ixns.length + 3, `Transaction Object length mismatch, expected ${ ixns.length + 3 }, received ${txn.length}` ); }); // it('Creates a transaction object with signers', async () => { // // const ixn = // }); it("Fails to create a txn object with options if too big", async () => { const ixns = [ createDummyIxn(5), createDummyIxn(3, 64), createDummyIxn(8, 32), createDummyIxn(2), createDummyIxn(5), createDummyIxn(3, 64), createDummyIxn(8, 32), createDummyIxn(2), createDummyIxn(5), createDummyIxn(3, 64), createDummyIxn(8, 32), createDummyIxn(2), ]; assert.throws(() => { new TransactionObject(ctx.payer.publicKey, ixns, [], { enableDurableNonce: true, computeUnitLimit: 200000, computeUnitPrice: 10000, }); }, `Did not throw`); }); it("Packs transactions with options", async () => { const options: TransactionObjectOptions = { computeUnitLimit: 200000, computeUnitPrice: 10000, }; const computeUnitLimitIxn = TransactionObject.getComputeUnitLimitIxn( options.computeUnitLimit! )!; const priorityIxn = TransactionObject.getComputeUnitPriceIxn( options.computeUnitPrice! )!; const txns = [ new TransactionObject( ctx.payer.publicKey, [ createDummyIxn(5), createDummyIxn(3, 64), createDummyIxn(8, 32), createDummyIxn(2), createDummyIxn(8, 32), createDummyIxn(2), ], [] ), new TransactionObject(ctx.payer.publicKey, [createDummyIxn(5)], []), new TransactionObject(ctx.payer.publicKey, [createDummyIxn(5)], []), new TransactionObject( ctx.payer.publicKey, [createDummyIxn(3, 64), createDummyIxn(5)], [] ), new TransactionObject( ctx.payer.publicKey, [ createDummyIxn(5), createDummyIxn(3, 64), createDummyIxn(8, 32), createDummyIxn(2), createDummyIxn(8, 32), createDummyIxn(2), ], [] ), new TransactionObject(ctx.payer.publicKey, [createDummyIxn(5)], []), new TransactionObject(ctx.payer.publicKey, [createDummyIxn(5)], []), new TransactionObject( ctx.payer.publicKey, [ createDummyIxn(3, 64), createDummyIxn(5), createDummyIxn(3, 64), createDummyIxn(8, 32), ], [] ), new TransactionObject( ctx.payer.publicKey, [ createDummyIxn(5), createDummyIxn(3, 64), createDummyIxn(8, 32), createDummyIxn(2), createDummyIxn(8, 32), createDummyIxn(2), ], [] ), new TransactionObject( ctx.payer.publicKey, [createDummyIxn(5), createDummyIxn(3, 64), createDummyIxn(8, 32)], [] ), new TransactionObject(ctx.payer.publicKey, [createDummyIxn(5)], []), new TransactionObject( ctx.payer.publicKey, [ createDummyIxn(3, 64), createDummyIxn(5), createDummyIxn(3, 64), createDummyIxn(8, 32), ], [] ), new TransactionObject( ctx.payer.publicKey, [ createDummyIxn(3, 64), createDummyIxn(5), createDummyIxn(3, 64), createDummyIxn(8, 32), createDummyIxn(5), createDummyIxn(3, 64), createDummyIxn(8, 32), ], [] ), new TransactionObject( ctx.payer.publicKey, [ createDummyIxn(3, 64), createDummyIxn(5), createDummyIxn(5), createDummyIxn(3, 64), createDummyIxn(8, 32), ], [] ), ]; const packed = TransactionObject.pack(txns, options); assert( packed.length > 0, `Failed to pack transactions, received empty array` ); for (const txn of packed) { assert( ixnsDeepEqual(txn.ixns[0], priorityIxn), `Expected the first instruction to be setComputeUnitPrice` ); assert( ixnsDeepEqual(txn.ixns[1], computeUnitLimitIxn), `Expected the second instruction to be setComputeUnitLimit` ); } }); it("Packs transactions and adds a preIxn and postIxn to each new transaction", async () => { const preIxn = createDummyIxn(6, 96); const postIxn1 = createDummyIxn(4, 48); const postIxn2 = createDummyIxn(3, 24); const ixns = [ createDummyIxn(5), createDummyIxn(3, 64), createDummyIxn(8, 32), createDummyIxn(2), ]; const packed = TransactionObject.packIxns( ctx.payer.publicKey, [ createDummyIxn(5), createDummyIxn(3, 64), createDummyIxn(8, 32), createDummyIxn(2), createDummyIxn(5), createDummyIxn(3, 64), createDummyIxn(8, 32), createDummyIxn(2), createDummyIxn(5), createDummyIxn(3, 64), createDummyIxn(8, 32), createDummyIxn(2), ], undefined, { preIxns: [preIxn], postIxns: [postIxn1, postIxn2], } ); for (const tx of packed) { assert( tx.ixns.length > 3, `Expected packed ixn to be greater than 3 ixns` ); const firstIxn = tx.ixns.shift()!; assert( ixnsDeepEqual(preIxn, firstIxn), `Expected preIxn to be first instruction in txn` ); const expectedPostIxn2 = tx.ixns.pop()!; assert( ixnsDeepEqual(postIxn2, expectedPostIxn2), `Expected postIxn2 to be last instruction in txn` ); const expectedPostIxn1 = tx.ixns.pop()!; assert( ixnsDeepEqual(postIxn1, expectedPostIxn1), `Expected postIxn1 to be second to last instruction in txn` ); } }); });