From ca85338f6cfcfd619a1b4f5570f53d3d6d2d4335 Mon Sep 17 00:00:00 2001 From: Iceomatic <89707822+iceomatic@users.noreply.github.com> Date: Mon, 7 Oct 2024 18:09:08 +0200 Subject: [PATCH] feat: u8 for buffer seeding --- .../instructions/transaction_buffer_close.rs | 2 +- .../instructions/transaction_buffer_create.rs | 10 +- .../instructions/transaction_buffer_extend.rs | 4 +- .../vault_transaction_create_from_buffer.rs | 6 +- .../src/state/transaction_buffer.rs | 6 +- sdk/multisig/idl/squads_multisig_program.json | 17 +- .../generated/accounts/TransactionBuffer.ts | 22 +- .../types/TransactionBufferCreateArgs.ts | 2 + tests/suites/examples/transaction-buffer.ts | 6 +- .../instructions/transactionBufferClose.ts | 12 +- .../instructions/transactionBufferCreate.ts | 770 ++++++++++-------- .../instructions/transactionBufferExtend.ts | 8 +- .../vaultTransactionCreateFromBuffer.ts | 23 +- 13 files changed, 504 insertions(+), 384 deletions(-) diff --git a/programs/squads_multisig_program/src/instructions/transaction_buffer_close.rs b/programs/squads_multisig_program/src/instructions/transaction_buffer_close.rs index c1b946bd..9d28f3ef 100644 --- a/programs/squads_multisig_program/src/instructions/transaction_buffer_close.rs +++ b/programs/squads_multisig_program/src/instructions/transaction_buffer_close.rs @@ -24,7 +24,7 @@ pub struct TransactionBufferClose<'info> { SEED_PREFIX, multisig.key().as_ref(), SEED_TRANSACTION_BUFFER, - &transaction_buffer.transaction_index.to_le_bytes(), + &transaction_buffer.buffer_index.to_le_bytes() ], bump )] diff --git a/programs/squads_multisig_program/src/instructions/transaction_buffer_create.rs b/programs/squads_multisig_program/src/instructions/transaction_buffer_create.rs index ff216674..2201b9a3 100644 --- a/programs/squads_multisig_program/src/instructions/transaction_buffer_create.rs +++ b/programs/squads_multisig_program/src/instructions/transaction_buffer_create.rs @@ -6,6 +6,8 @@ use crate::state::*; #[derive(AnchorSerialize, AnchorDeserialize)] pub struct TransactionBufferCreateArgs { + /// Index of the buffer account to seed the account derivation + pub buffer_index: u8, /// Index of the vault this transaction belongs to. pub vault_index: u8, /// Hash of the final assembled transaction message. @@ -34,7 +36,7 @@ pub struct TransactionBufferCreate<'info> { SEED_PREFIX, multisig.key().as_ref(), SEED_TRANSACTION_BUFFER, - &multisig.transaction_index.checked_add(1).unwrap().to_le_bytes(), + &args.buffer_index.to_le_bytes(), ], bump )] @@ -88,14 +90,14 @@ impl TransactionBufferCreate<'_> { let multisig = &ctx.accounts.multisig; let creator = &mut ctx.accounts.creator; - // Get the transaction index. - let transaction_index = multisig.transaction_index.checked_add(1).unwrap(); + // Get the buffer index. + let buffer_index = args.buffer_index; // Initialize the transaction fields. transaction_buffer.multisig = multisig.key(); transaction_buffer.creator = creator.key(); transaction_buffer.vault_index = args.vault_index; - transaction_buffer.transaction_index = transaction_index; + transaction_buffer.buffer_index = buffer_index; transaction_buffer.final_buffer_hash = args.final_buffer_hash; transaction_buffer.final_buffer_size = args.final_buffer_size; transaction_buffer.buffer = args.buffer; diff --git a/programs/squads_multisig_program/src/instructions/transaction_buffer_extend.rs b/programs/squads_multisig_program/src/instructions/transaction_buffer_extend.rs index 0ea56193..84d6c43c 100644 --- a/programs/squads_multisig_program/src/instructions/transaction_buffer_extend.rs +++ b/programs/squads_multisig_program/src/instructions/transaction_buffer_extend.rs @@ -23,13 +23,11 @@ pub struct TransactionBufferExtend<'info> { mut, // Only the creator can extend the buffer constraint = transaction_buffer.creator == creator.key() @ MultisigError::Unauthorized, - // Extending the buffer only work if it still represents the next - // transaction index in the multisig seeds = [ SEED_PREFIX, multisig.key().as_ref(), SEED_TRANSACTION_BUFFER, - &multisig.transaction_index.checked_add(1).unwrap().to_le_bytes(), + &transaction_buffer.buffer_index.to_le_bytes() ], bump )] diff --git a/programs/squads_multisig_program/src/instructions/vault_transaction_create_from_buffer.rs b/programs/squads_multisig_program/src/instructions/vault_transaction_create_from_buffer.rs index 388085e4..b9c5ab9f 100644 --- a/programs/squads_multisig_program/src/instructions/vault_transaction_create_from_buffer.rs +++ b/programs/squads_multisig_program/src/instructions/vault_transaction_create_from_buffer.rs @@ -15,11 +15,7 @@ pub struct VaultTransactionCreateFromBuffer<'info> { SEED_PREFIX, vault_transaction_create.multisig.key().as_ref(), SEED_TRANSACTION_BUFFER, - &vault_transaction_create.multisig - .transaction_index - .checked_add(1) - .unwrap() - .to_le_bytes(), + &transaction_buffer.buffer_index.to_le_bytes(), ], bump )] diff --git a/programs/squads_multisig_program/src/state/transaction_buffer.rs b/programs/squads_multisig_program/src/state/transaction_buffer.rs index 2479a0f1..2ed6b608 100644 --- a/programs/squads_multisig_program/src/state/transaction_buffer.rs +++ b/programs/squads_multisig_program/src/state/transaction_buffer.rs @@ -12,10 +12,10 @@ pub struct TransactionBuffer { pub multisig: Pubkey, /// Member of the Multisig who created the TransactionBuffer. pub creator: Pubkey, + /// Index to seed address derivation + pub buffer_index: u8, /// Vault index of the transaction this buffer belongs to. pub vault_index: u8, - /// Index of the transaction this buffer belongs to. - pub transaction_index: u64, /// Hash of the final assembled transaction message. pub final_buffer_hash: [u8; 32], /// The size of the final assembled transaction message. @@ -34,8 +34,8 @@ impl TransactionBuffer { 8 + // anchor account discriminator 32 + // multisig 32 + // creator + 8 + // buffer_index 8 + // vault_index - 8 + // transaction_index 32 + // transaction_message_hash 2 + // final_buffer_size 4 + // vec length bytes diff --git a/sdk/multisig/idl/squads_multisig_program.json b/sdk/multisig/idl/squads_multisig_program.json index 609af796..fed3c6fb 100644 --- a/sdk/multisig/idl/squads_multisig_program.json +++ b/sdk/multisig/idl/squads_multisig_program.json @@ -2113,18 +2113,18 @@ "type": "publicKey" }, { - "name": "vaultIndex", + "name": "bufferIndex", "docs": [ - "Vault index of the transaction this buffer belongs to." + "Index to seed address derivation" ], "type": "u8" }, { - "name": "transactionIndex", + "name": "vaultIndex", "docs": [ - "Index of the transaction this buffer belongs to." + "Vault index of the transaction this buffer belongs to." ], - "type": "u64" + "type": "u8" }, { "name": "finalBufferHash", @@ -2771,6 +2771,13 @@ "type": { "kind": "struct", "fields": [ + { + "name": "bufferIndex", + "docs": [ + "Index of the buffer account to seed the account derivation" + ], + "type": "u8" + }, { "name": "vaultIndex", "docs": [ diff --git a/sdk/multisig/src/generated/accounts/TransactionBuffer.ts b/sdk/multisig/src/generated/accounts/TransactionBuffer.ts index 7b5fba60..aa2e1f2b 100644 --- a/sdk/multisig/src/generated/accounts/TransactionBuffer.ts +++ b/sdk/multisig/src/generated/accounts/TransactionBuffer.ts @@ -6,8 +6,8 @@ */ import * as web3 from '@solana/web3.js' -import * as beet from '@metaplex-foundation/beet' import * as beetSolana from '@metaplex-foundation/beet-solana' +import * as beet from '@metaplex-foundation/beet' /** * Arguments used to create {@link TransactionBuffer} @@ -17,8 +17,8 @@ import * as beetSolana from '@metaplex-foundation/beet-solana' export type TransactionBufferArgs = { multisig: web3.PublicKey creator: web3.PublicKey + bufferIndex: number vaultIndex: number - transactionIndex: beet.bignum finalBufferHash: number[] /* size: 32 */ finalBufferSize: number buffer: Uint8Array @@ -38,8 +38,8 @@ export class TransactionBuffer implements TransactionBufferArgs { private constructor( readonly multisig: web3.PublicKey, readonly creator: web3.PublicKey, + readonly bufferIndex: number, readonly vaultIndex: number, - readonly transactionIndex: beet.bignum, readonly finalBufferHash: number[] /* size: 32 */, readonly finalBufferSize: number, readonly buffer: Uint8Array @@ -52,8 +52,8 @@ export class TransactionBuffer implements TransactionBufferArgs { return new TransactionBuffer( args.multisig, args.creator, + args.bufferIndex, args.vaultIndex, - args.transactionIndex, args.finalBufferHash, args.finalBufferSize, args.buffer @@ -167,18 +167,8 @@ export class TransactionBuffer implements TransactionBufferArgs { return { multisig: this.multisig.toBase58(), creator: this.creator.toBase58(), + bufferIndex: this.bufferIndex, vaultIndex: this.vaultIndex, - transactionIndex: (() => { - const x = <{ toNumber: () => number }>this.transactionIndex - if (typeof x.toNumber === 'function') { - try { - return x.toNumber() - } catch (_) { - return x - } - } - return x - })(), finalBufferHash: this.finalBufferHash, finalBufferSize: this.finalBufferSize, buffer: this.buffer, @@ -200,8 +190,8 @@ export const transactionBufferBeet = new beet.FixableBeetStruct< ['accountDiscriminator', beet.uniformFixedSizeArray(beet.u8, 8)], ['multisig', beetSolana.publicKey], ['creator', beetSolana.publicKey], + ['bufferIndex', beet.u8], ['vaultIndex', beet.u8], - ['transactionIndex', beet.u64], ['finalBufferHash', beet.uniformFixedSizeArray(beet.u8, 32)], ['finalBufferSize', beet.u16], ['buffer', beet.bytes], diff --git a/sdk/multisig/src/generated/types/TransactionBufferCreateArgs.ts b/sdk/multisig/src/generated/types/TransactionBufferCreateArgs.ts index 480424d2..7c34fd6c 100644 --- a/sdk/multisig/src/generated/types/TransactionBufferCreateArgs.ts +++ b/sdk/multisig/src/generated/types/TransactionBufferCreateArgs.ts @@ -7,6 +7,7 @@ import * as beet from '@metaplex-foundation/beet' export type TransactionBufferCreateArgs = { + bufferIndex: number vaultIndex: number finalBufferHash: number[] /* size: 32 */ finalBufferSize: number @@ -20,6 +21,7 @@ export type TransactionBufferCreateArgs = { export const transactionBufferCreateArgsBeet = new beet.FixableBeetArgsStruct( [ + ['bufferIndex', beet.u8], ['vaultIndex', beet.u8], ['finalBufferHash', beet.uniformFixedSizeArray(beet.u8, 32)], ['finalBufferSize', beet.u16], diff --git a/tests/suites/examples/transaction-buffer.ts b/tests/suites/examples/transaction-buffer.ts index c7ac7962..156424fe 100644 --- a/tests/suites/examples/transaction-buffer.ts +++ b/tests/suites/examples/transaction-buffer.ts @@ -18,7 +18,6 @@ import { VaultTransactionCreateFromBufferInstructionArgs, } from "@sqds/multisig/lib/generated"; import assert from "assert"; -import { BN } from "bn.js"; import * as crypto from "crypto"; import { TestMembers, @@ -74,6 +73,7 @@ describe("Examples / Transaction Buffers", () => { it("set buffer, extend, and create", async () => { const transactionIndex = 1n; + const bufferIndex = 0; const testIx = createTestTransferInstruction(vaultPda, vaultPda, 1); @@ -103,7 +103,7 @@ describe("Examples / Transaction Buffers", () => { Buffer.from("multisig"), multisigPda.toBuffer(), Buffer.from("transaction_buffer"), - new BN(Number(transactionIndex)).toBuffer("le", 8), + Buffer.from([bufferIndex]) ], programId ); @@ -122,10 +122,10 @@ describe("Examples / Transaction Buffers", () => { transactionBuffer, creator: members.almighty.publicKey, rentPayer: members.almighty.publicKey, - systemProgram: SystemProgram.programId, }, { args: { + bufferIndex: bufferIndex, vaultIndex: 0, // Must be a SHA256 hash of the message buffer. finalBufferHash: Array.from(messageHash), diff --git a/tests/suites/instructions/transactionBufferClose.ts b/tests/suites/instructions/transactionBufferClose.ts index d9e6dce5..e11a666d 100644 --- a/tests/suites/instructions/transactionBufferClose.ts +++ b/tests/suites/instructions/transactionBufferClose.ts @@ -32,11 +32,11 @@ describe("Instructions / transaction_buffer_close", () => { let vaultPda: PublicKey; let transactionBuffer: PublicKey; - const createKey = Keypair.generate(); before(async () => { members = await generateMultisigMembers(connection); + const createKey = Keypair.generate(); multisigPda = (await createAutonomousMultisigV2({ connection, createKey, @@ -59,7 +59,7 @@ describe("Instructions / transaction_buffer_close", () => { ); await connection.confirmTransaction(signature); - const transactionIndex = 1n; + const bufferIndex = 0; const testIx = await createTestTransferInstruction( vaultPda, Keypair.generate().publicKey, @@ -78,16 +78,15 @@ describe("Instructions / transaction_buffer_close", () => { vaultPda, }); - [transactionBuffer] = await PublicKey.findProgramAddressSync( + [transactionBuffer] = PublicKey.findProgramAddressSync( [ Buffer.from("multisig"), multisigPda.toBuffer(), Buffer.from("transaction_buffer"), - new BN(Number(transactionIndex)).toBuffer("le", 8), + Uint8Array.from([bufferIndex]) ], programId ); - const messageHash = crypto .createHash("sha256") .update(messageBuffer) @@ -103,6 +102,7 @@ describe("Instructions / transaction_buffer_close", () => { }, { args: { + bufferIndex: Number(bufferIndex), vaultIndex: 0, finalBufferHash: Array.from(messageHash), finalBufferSize: messageBuffer.length, @@ -121,7 +121,7 @@ describe("Instructions / transaction_buffer_close", () => { const createTx = new VersionedTransaction(createMessage); createTx.sign([members.proposer]); - const createSig = await connection.sendTransaction(createTx, { skipPreflight: true }); + const createSig = await connection.sendRawTransaction(createTx.serialize(), { skipPreflight: true }); await connection.confirmTransaction(createSig); }); diff --git a/tests/suites/instructions/transactionBufferCreate.ts b/tests/suites/instructions/transactionBufferCreate.ts index 19abca29..a185203b 100644 --- a/tests/suites/instructions/transactionBufferCreate.ts +++ b/tests/suites/instructions/transactionBufferCreate.ts @@ -2,12 +2,17 @@ import { Keypair, LAMPORTS_PER_SOL, PublicKey, + SystemProgram, TransactionMessage, VersionedTransaction, - SystemProgram, } from "@solana/web3.js"; import * as multisig from "@sqds/multisig"; +import { + TransactionBufferCreateArgs, + TransactionBufferCreateInstructionArgs, +} from "@sqds/multisig/lib/generated"; import assert from "assert"; +import * as crypto from "crypto"; import { createAutonomousMultisigV2, createLocalhostConnection, @@ -16,12 +21,6 @@ import { getTestProgramId, TestMembers, } from "../../utils"; -import { BN } from "bn.js"; -import { - TransactionBufferCreateArgs, - TransactionBufferCreateInstructionArgs, -} from "@sqds/multisig/lib/generated"; -import * as crypto from "crypto"; const programId = getTestProgramId(); const connection = createLocalhostConnection(); @@ -68,7 +67,7 @@ describe("Instructions / transaction_buffer_create", () => { }); it("set transaction buffer", async () => { - const transactionIndex = 1n; + const bufferIndex = 0; const testPayee = Keypair.generate(); const testIx = createTestTransferInstruction( @@ -92,12 +91,12 @@ describe("Instructions / transaction_buffer_create", () => { vaultPda, }); - const [transactionBuffer, _] = await PublicKey.findProgramAddressSync( + const [transactionBuffer, _] = PublicKey.findProgramAddressSync( [ Buffer.from("multisig"), multisigPda.toBuffer(), Buffer.from("transaction_buffer"), - new BN(Number(transactionIndex)).toBuffer("le", 8), + Uint8Array.from([bufferIndex]) ], programId ); @@ -108,6 +107,7 @@ describe("Instructions / transaction_buffer_create", () => { .update(messageBuffer) .digest(); + const ix = multisig.generated.createTransactionBufferCreateInstruction( { multisig: multisigPda, @@ -118,7 +118,9 @@ describe("Instructions / transaction_buffer_create", () => { }, { args: { + bufferIndex: bufferIndex, vaultIndex: 0, + createKey: Keypair.generate(), // Must be a SHA256 hash of the message buffer. finalBufferHash: Array.from(messageHash), finalBufferSize: messageBuffer.length, @@ -153,15 +155,17 @@ describe("Instructions / transaction_buffer_create", () => { assert.ok(transactionBufferAccount?.data.length! > 0); }); + + it("close transaction buffer", async () => { - const transactionIndex = 1n; + const bufferIndex = 0; - const [transactionBuffer, _] = await PublicKey.findProgramAddressSync( + const [transactionBuffer, _] = PublicKey.findProgramAddressSync( [ Buffer.from("multisig"), multisigPda.toBuffer(), Buffer.from("transaction_buffer"), - new BN(Number(transactionIndex)).toBuffer("le", 8), + Uint8Array.from([bufferIndex]) ], programId ); @@ -199,330 +203,456 @@ describe("Instructions / transaction_buffer_create", () => { assert.equal(transactionBufferAccount, null); }); - // Test: Attempt to create a transaction buffer with a non-member -it("error: creating buffer as non-member", async () => { - const transactionIndex = 1n; - // Create a keypair that is not a member of the multisig - const nonMember = Keypair.generate(); - // Airdrop some SOL to the non-member - const airdropSig = await connection.requestAirdrop( - nonMember.publicKey, - 1 * LAMPORTS_PER_SOL - ); - await connection.confirmTransaction(airdropSig); - - // Set up a test transaction - const testPayee = Keypair.generate(); - const testIx = await createTestTransferInstruction( - vaultPda, - testPayee.publicKey, - 1 * LAMPORTS_PER_SOL - ); - - // Create a transaction message - const testTransferMessage = new TransactionMessage({ - payerKey: vaultPda, - recentBlockhash: (await connection.getLatestBlockhash()).blockhash, - instructions: [testIx], + it("reinitalize transaction buffer after its been closed", async () => { + const bufferIndex = 0; + + const testPayee = Keypair.generate(); + const testIx = createTestTransferInstruction( + vaultPda, + testPayee.publicKey, + 1 * LAMPORTS_PER_SOL + ); + + // Initialize a transaction message with a single instruction. + const testTransferMessage = new TransactionMessage({ + payerKey: vaultPda, + recentBlockhash: (await connection.getLatestBlockhash()).blockhash, + instructions: [testIx], + }); + + // Serialize with SDK util + const messageBuffer = + multisig.utils.transactionMessageToMultisigTransactionMessageBytes({ + message: testTransferMessage, + addressLookupTableAccounts: [], + vaultPda, + }); + + const [transactionBuffer, _] = PublicKey.findProgramAddressSync( + [ + Buffer.from("multisig"), + multisigPda.toBuffer(), + Buffer.from("transaction_buffer"), + Uint8Array.from([bufferIndex]) + ], + programId + ); + + // Convert to a SHA256 hash. + const messageHash = crypto + .createHash("sha256") + .update(messageBuffer) + .digest(); + + + const ix = multisig.generated.createTransactionBufferCreateInstruction( + { + multisig: multisigPda, + transactionBuffer, + creator: members.proposer.publicKey, + rentPayer: members.proposer.publicKey, + systemProgram: SystemProgram.programId, + }, + { + args: { + bufferIndex: bufferIndex, + vaultIndex: 0, + createKey: Keypair.generate(), + // Must be a SHA256 hash of the message buffer. + finalBufferHash: Array.from(messageHash), + finalBufferSize: messageBuffer.length, + buffer: messageBuffer, + } as TransactionBufferCreateArgs, + } as TransactionBufferCreateInstructionArgs, + programId + ); + + const message = new TransactionMessage({ + payerKey: members.proposer.publicKey, + recentBlockhash: (await connection.getLatestBlockhash()).blockhash, + instructions: [ix], + }).compileToV0Message(); + + const tx = new VersionedTransaction(message); + + tx.sign([members.proposer]); + + // Send transaction. + const signature = await connection.sendTransaction(tx, { + skipPreflight: true, + }); + await connection.confirmTransaction(signature); + + const transactionBufferAccount = await connection.getAccountInfo( + transactionBuffer + ); + + // Verify account exists. + assert.notEqual(transactionBufferAccount, null); + assert.ok(transactionBufferAccount?.data.length! > 0); + + const ix2 = multisig.generated.createTransactionBufferCloseInstruction( + { + multisig: multisigPda, + transactionBuffer, + creator: members.proposer.publicKey, + }, + programId + ); + + const message2 = new TransactionMessage({ + payerKey: members.proposer.publicKey, + recentBlockhash: (await connection.getLatestBlockhash()).blockhash, + instructions: [ix2], + }).compileToV0Message(); + + const tx2 = new VersionedTransaction(message2); + + tx2.sign([members.proposer]); + + // Send transaction. + const signature2 = await connection.sendTransaction(tx2, { + skipPreflight: true, + }); + await connection.confirmTransaction(signature2); + + const transactionBufferAccount2 = await connection.getAccountInfo( + transactionBuffer + ); + + // Verify account is closed. + assert.equal(transactionBufferAccount2, null); }); - // Serialize the message buffer - const messageBuffer = - multisig.utils.transactionMessageToMultisigTransactionMessageBytes({ - message: testTransferMessage, - addressLookupTableAccounts: [], + // Test: Attempt to create a transaction buffer with a non-member + it("error: creating buffer as non-member", async () => { + const bufferIndex = 0; + // Create a keypair that is not a member of the multisig + const nonMember = Keypair.generate(); + // Airdrop some SOL to the non-member + const airdropSig = await connection.requestAirdrop( + nonMember.publicKey, + 1 * LAMPORTS_PER_SOL + ); + await connection.confirmTransaction(airdropSig); + + // Set up a test transaction + const testPayee = Keypair.generate(); + const testIx = await createTestTransferInstruction( vaultPda, + testPayee.publicKey, + 1 * LAMPORTS_PER_SOL + ); + + // Create a transaction message + const testTransferMessage = new TransactionMessage({ + payerKey: vaultPda, + recentBlockhash: (await connection.getLatestBlockhash()).blockhash, + instructions: [testIx], }); - // Derive the transaction buffer PDA - const [transactionBuffer, _] = await PublicKey.findProgramAddressSync( - [ - Buffer.from("multisig"), - multisigPda.toBuffer(), - Buffer.from("transaction_buffer"), - new BN(Number(transactionIndex)).toBuffer("le", 8), - ], - programId - ); - - // Create a hash of the message buffer - const messageHash = crypto - .createHash("sha256") - .update(messageBuffer) - .digest(); - - // Create the instruction to create a transaction buffer - const ix = multisig.generated.createTransactionBufferCreateInstruction( - { - multisig: multisigPda, - transactionBuffer, - creator: nonMember.publicKey, - rentPayer: nonMember.publicKey, - systemProgram: SystemProgram.programId, - }, - { - args: { - vaultIndex: 0, - finalBufferHash: Array.from(messageHash), - finalBufferSize: messageBuffer.length, - buffer: messageBuffer, - } as TransactionBufferCreateArgs, - } as TransactionBufferCreateInstructionArgs, - programId - ); - - // Create and sign the transaction - const message = new TransactionMessage({ - payerKey: nonMember.publicKey, - recentBlockhash: (await connection.getLatestBlockhash()).blockhash, - instructions: [ix], - }).compileToV0Message(); - - const tx = new VersionedTransaction(message); - tx.sign([nonMember]); - - // Attempt to send the transaction and expect it to fail - await assert.rejects( - () => - connection - .sendTransaction(tx) - .catch(multisig.errors.translateAndThrowAnchorError), - /NotAMember/ - ); -}); + // Serialize the message buffer + const messageBuffer = + multisig.utils.transactionMessageToMultisigTransactionMessageBytes({ + message: testTransferMessage, + addressLookupTableAccounts: [], + vaultPda, + }); + + // Derive the transaction buffer PDA + const [transactionBuffer, _] = await PublicKey.findProgramAddressSync( + [ + Buffer.from("multisig"), + multisigPda.toBuffer(), + Buffer.from("transaction_buffer"), + Uint8Array.from([bufferIndex]), + ], + programId + ); + + // Create a hash of the message buffer + const messageHash = crypto + .createHash("sha256") + .update(messageBuffer) + .digest(); + + // Create the instruction to create a transaction buffer + const ix = multisig.generated.createTransactionBufferCreateInstruction( + { + multisig: multisigPda, + transactionBuffer, + creator: nonMember.publicKey, + rentPayer: nonMember.publicKey, + systemProgram: SystemProgram.programId, + }, + { + args: { + bufferIndex: bufferIndex, + vaultIndex: 0, + finalBufferHash: Array.from(messageHash), + finalBufferSize: messageBuffer.length, + buffer: messageBuffer, + } as TransactionBufferCreateArgs, + } as TransactionBufferCreateInstructionArgs, + programId + ); -// Test: Attempt to create a transaction buffer with a member without initiate permissions -it("error: creating buffer as member without proposer permissions", async () => { - const memberWithoutInitiatePermissions = members.voter; - - const transactionIndex = 1n; - - // Set up a test transaction - const testPayee = Keypair.generate(); - const testIx = await createTestTransferInstruction( - vaultPda, - testPayee.publicKey, - 1 * LAMPORTS_PER_SOL - ); - - // Create a transaction message - const testTransferMessage = new TransactionMessage({ - payerKey: vaultPda, - recentBlockhash: (await connection.getLatestBlockhash()).blockhash, - instructions: [testIx], + // Create and sign the transaction + const message = new TransactionMessage({ + payerKey: nonMember.publicKey, + recentBlockhash: (await connection.getLatestBlockhash()).blockhash, + instructions: [ix], + }).compileToV0Message(); + + const tx = new VersionedTransaction(message); + tx.sign([nonMember]); + + // Attempt to send the transaction and expect it to fail + await assert.rejects( + () => + connection + .sendTransaction(tx) + .catch(multisig.errors.translateAndThrowAnchorError), + /NotAMember/ + ); }); - // Serialize the message buffer - const messageBuffer = - multisig.utils.transactionMessageToMultisigTransactionMessageBytes({ - message: testTransferMessage, - addressLookupTableAccounts: [], + // Test: Attempt to create a transaction buffer with a member without initiate permissions + it("error: creating buffer as member without proposer permissions", async () => { + const memberWithoutInitiatePermissions = members.voter; + + const bufferIndex = 0; + + // Set up a test transaction + const testPayee = Keypair.generate(); + const testIx = await createTestTransferInstruction( vaultPda, + testPayee.publicKey, + 1 * LAMPORTS_PER_SOL + ); + + // Create a transaction message + const testTransferMessage = new TransactionMessage({ + payerKey: vaultPda, + recentBlockhash: (await connection.getLatestBlockhash()).blockhash, + instructions: [testIx], }); - // Derive the transaction buffer PDA - const [transactionBuffer, _] = await PublicKey.findProgramAddressSync( - [ - Buffer.from("multisig"), - multisigPda.toBuffer(), - Buffer.from("transaction_buffer"), - new BN(Number(transactionIndex)).toBuffer("le", 8), - ], - programId - ); - - // Create a hash of the message buffer - const messageHash = crypto - .createHash("sha256") - .update(messageBuffer) - .digest(); - - // Create the instruction to create a transaction buffer - const ix = multisig.generated.createTransactionBufferCreateInstruction( - { - multisig: multisigPda, - transactionBuffer, - creator: memberWithoutInitiatePermissions.publicKey, - rentPayer: memberWithoutInitiatePermissions.publicKey, - systemProgram: SystemProgram.programId, - }, - { - args: { - vaultIndex: 0, - finalBufferHash: Array.from(messageHash), - finalBufferSize: messageBuffer.length, - buffer: messageBuffer, - } as TransactionBufferCreateArgs, - } as TransactionBufferCreateInstructionArgs, - programId - ); - - // Create and sign the transaction - const message = new TransactionMessage({ - payerKey: memberWithoutInitiatePermissions.publicKey, - recentBlockhash: (await connection.getLatestBlockhash()).blockhash, - instructions: [ix], - }).compileToV0Message(); - - const tx = new VersionedTransaction(message); - tx.sign([memberWithoutInitiatePermissions]); - - // Attempt to send the transaction and expect it to fail - await assert.rejects( - () => - connection - .sendTransaction(tx) - .catch(multisig.errors.translateAndThrowAnchorError), - /Unauthorized/ - ); -}); + // Serialize the message buffer + const messageBuffer = + multisig.utils.transactionMessageToMultisigTransactionMessageBytes({ + message: testTransferMessage, + addressLookupTableAccounts: [], + vaultPda, + }); + + // Derive the transaction buffer PDA + const [transactionBuffer, _] = await PublicKey.findProgramAddressSync( + [ + Buffer.from("multisig"), + multisigPda.toBuffer(), + Buffer.from("transaction_buffer"), + Uint8Array.from([bufferIndex]), + ], + programId + ); + + // Create a hash of the message buffer + const messageHash = crypto + .createHash("sha256") + .update(messageBuffer) + .digest(); + + // Create the instruction to create a transaction buffer + const ix = multisig.generated.createTransactionBufferCreateInstruction( + { + multisig: multisigPda, + transactionBuffer, + creator: memberWithoutInitiatePermissions.publicKey, + rentPayer: memberWithoutInitiatePermissions.publicKey, + systemProgram: SystemProgram.programId, + }, + { + args: { + bufferIndex: bufferIndex, + vaultIndex: 0, + finalBufferHash: Array.from(messageHash), + finalBufferSize: messageBuffer.length, + buffer: messageBuffer, + } as TransactionBufferCreateArgs, + } as TransactionBufferCreateInstructionArgs, + programId + ); -// Test: Attempt to create a transaction buffer with an invalid index -it("error: creating buffer for invalid index", async () => { - // Use an invalid transaction index (multisig.transaction_index + 2) - const invalidTransactionIndex = 3n; - - // Set up a test transaction - const testPayee = Keypair.generate(); - const testIx = await createTestTransferInstruction( - vaultPda, - testPayee.publicKey, - 1 * LAMPORTS_PER_SOL - ); - - // Create a transaction message - const testTransferMessage = new TransactionMessage({ - payerKey: vaultPda, - recentBlockhash: (await connection.getLatestBlockhash()).blockhash, - instructions: [testIx], + // Create and sign the transaction + const message = new TransactionMessage({ + payerKey: memberWithoutInitiatePermissions.publicKey, + recentBlockhash: (await connection.getLatestBlockhash()).blockhash, + instructions: [ix], + }).compileToV0Message(); + + const tx = new VersionedTransaction(message); + tx.sign([memberWithoutInitiatePermissions]); + + // Attempt to send the transaction and expect it to fail + await assert.rejects( + () => + connection + .sendTransaction(tx) + .catch(multisig.errors.translateAndThrowAnchorError), + /Unauthorized/ + ); }); - // Serialize the message buffer - const messageBuffer = - multisig.utils.transactionMessageToMultisigTransactionMessageBytes({ - message: testTransferMessage, - addressLookupTableAccounts: [], + // Test: Attempt to create a transaction buffer with an invalid index + it("error: creating buffer for invalid index", async () => { + // Use an invalid buffer index (non-u8 value) + const invalidBufferIndex = "random_string"; + + // Set up a test transaction + const testPayee = Keypair.generate(); + const testIx = await createTestTransferInstruction( vaultPda, + testPayee.publicKey, + 1 * LAMPORTS_PER_SOL + ); + + // Create a transaction message + const testTransferMessage = new TransactionMessage({ + payerKey: vaultPda, + recentBlockhash: (await connection.getLatestBlockhash()).blockhash, + instructions: [testIx], }); - // Derive the transaction buffer PDA with the invalid index - const [transactionBuffer, _] = await PublicKey.findProgramAddressSync( - [ - Buffer.from("multisig"), - multisigPda.toBuffer(), - Buffer.from("transaction_buffer"), - new BN(Number(invalidTransactionIndex)).toBuffer("le", 8), - ], - programId - ); - - // Create a hash of the message buffer - const messageHash = crypto - .createHash("sha256") - .update(messageBuffer) - .digest(); - - // Create the instruction to create a transaction buffer - const ix = multisig.generated.createTransactionBufferCreateInstruction( - { - multisig: multisigPda, - transactionBuffer, - creator: members.proposer.publicKey, - rentPayer: members.proposer.publicKey, - systemProgram: SystemProgram.programId, - }, - { - args: { - vaultIndex: 0, - finalBufferHash: Array.from(messageHash), - finalBufferSize: messageBuffer.length, - buffer: messageBuffer, - } as TransactionBufferCreateArgs, - } as TransactionBufferCreateInstructionArgs, - programId - ); - - // Create and sign the transaction - const message = new TransactionMessage({ - payerKey: members.proposer.publicKey, - recentBlockhash: (await connection.getLatestBlockhash()).blockhash, - instructions: [ix], - }).compileToV0Message(); - - const tx = new VersionedTransaction(message); - tx.sign([members.proposer]); - - // Attempt to send the transaction and expect it to fail - await assert.rejects( - () => - connection - .sendTransaction(tx) - .catch(multisig.errors.translateAndThrowAnchorError), - /A seeds constraint was violated/ - ); -}); + // Serialize the message buffer + const messageBuffer = + multisig.utils.transactionMessageToMultisigTransactionMessageBytes({ + message: testTransferMessage, + addressLookupTableAccounts: [], + vaultPda, + }); + // Derive the transaction buffer PDA with the invalid index + const [transactionBuffer, _] = await PublicKey.findProgramAddressSync( + [ + Buffer.from("multisig"), + multisigPda.toBuffer(), + Buffer.from("transaction_buffer"), + Buffer.from(invalidBufferIndex), + ], + programId + ); -it("error: creating buffer exceeding maximum size", async () => { - const transactionIndex = 1n; - - // Create a large buffer that exceeds the maximum size - const largeBuffer = Buffer.alloc(500, 1); // 500 bytes, filled with 1s - - // Derive the transaction buffer PDA - const [transactionBuffer, _] = await PublicKey.findProgramAddressSync( - [ - Buffer.from("multisig"), - multisigPda.toBuffer(), - Buffer.from("transaction_buffer"), - new BN(Number(transactionIndex)).toBuffer("le", 8), - ], - programId - ); - - // Create a hash of the large buffer - const messageHash = crypto - .createHash("sha256") - .update(largeBuffer) - .digest(); - - // Create the instruction to create a transaction buffer - const ix = multisig.generated.createTransactionBufferCreateInstruction( - { - multisig: multisigPda, - transactionBuffer, - creator: members.proposer.publicKey, - rentPayer: members.proposer.publicKey, - systemProgram: SystemProgram.programId, - }, - { - args: { - vaultIndex: 0, - finalBufferHash: Array.from(messageHash), - finalBufferSize: 4001, - buffer: largeBuffer, - } as TransactionBufferCreateArgs, - } as TransactionBufferCreateInstructionArgs, - programId - ); - - // Create and sign the transaction - const message = new TransactionMessage({ - payerKey: members.proposer.publicKey, - recentBlockhash: (await connection.getLatestBlockhash()).blockhash, - instructions: [ix], - }).compileToV0Message(); - - const tx = new VersionedTransaction(message); - tx.sign([members.proposer]); - - // Attempt to send the transaction and expect it to fail - await assert.rejects( - () => - connection - .sendTransaction(tx) - .catch(multisig.errors.translateAndThrowAnchorError), - /FinalBufferSizeExceeded/ // Assuming this is the error thrown for exceeding buffer size - ); -}); + // Create a hash of the message buffer + const messageHash = crypto + .createHash("sha256") + .update(messageBuffer) + .digest(); + + // Create the instruction to create a transaction buffer + const ix = multisig.generated.createTransactionBufferCreateInstruction( + { + multisig: multisigPda, + transactionBuffer, + creator: members.proposer.publicKey, + rentPayer: members.proposer.publicKey, + systemProgram: SystemProgram.programId, + }, + { + args: { + bufferIndex: 0, + vaultIndex: 0, + finalBufferHash: Array.from(messageHash), + finalBufferSize: messageBuffer.length, + buffer: messageBuffer, + } as TransactionBufferCreateArgs, + } as TransactionBufferCreateInstructionArgs, + programId + ); + + // Create and sign the transaction + const message = new TransactionMessage({ + payerKey: members.proposer.publicKey, + recentBlockhash: (await connection.getLatestBlockhash()).blockhash, + instructions: [ix], + }).compileToV0Message(); + + const tx = new VersionedTransaction(message); + // Not signing with the create_key on purpose + tx.sign([members.proposer]); + + // Attempt to send the transaction and expect it to fail + await assert.rejects( + () => + connection + .sendTransaction(tx) + .catch(multisig.errors.translateAndThrowAnchorError), + /A seeds constraint was violated/ + ); + }); + + + it("error: creating buffer exceeding maximum size", async () => { + const bufferIndex = 0; + + // Create a large buffer that exceeds the maximum size + const largeBuffer = Buffer.alloc(500, 1); // 500 bytes, filled with 1s + + // Derive the transaction buffer PDA + const [transactionBuffer, _] = await PublicKey.findProgramAddressSync( + [ + Buffer.from("multisig"), + multisigPda.toBuffer(), + Buffer.from("transaction_buffer"), + Uint8Array.from([bufferIndex]), + ], + programId + ); + + // Create a hash of the large buffer + const messageHash = crypto + .createHash("sha256") + .update(largeBuffer) + .digest(); + + // Create the instruction to create a transaction buffer + const ix = multisig.generated.createTransactionBufferCreateInstruction( + { + multisig: multisigPda, + transactionBuffer, + creator: members.proposer.publicKey, + rentPayer: members.proposer.publicKey, + systemProgram: SystemProgram.programId, + }, + { + args: { + bufferIndex: bufferIndex, + vaultIndex: 0, + finalBufferHash: Array.from(messageHash), + finalBufferSize: 4001, + buffer: largeBuffer, + } as TransactionBufferCreateArgs, + } as TransactionBufferCreateInstructionArgs, + programId + ); + + // Create and sign the transaction + const message = new TransactionMessage({ + payerKey: members.proposer.publicKey, + recentBlockhash: (await connection.getLatestBlockhash()).blockhash, + instructions: [ix], + }).compileToV0Message(); + + const tx = new VersionedTransaction(message); + tx.sign([members.proposer]); + + // Attempt to send the transaction and expect it to fail + await assert.rejects( + () => + connection + .sendTransaction(tx) + .catch(multisig.errors.translateAndThrowAnchorError), + /FinalBufferSizeExceeded/ // Assuming this is the error thrown for exceeding buffer size + ); + }); }); diff --git a/tests/suites/instructions/transactionBufferExtend.ts b/tests/suites/instructions/transactionBufferExtend.ts index 61c1aa23..75ce62cc 100644 --- a/tests/suites/instructions/transactionBufferExtend.ts +++ b/tests/suites/instructions/transactionBufferExtend.ts @@ -14,7 +14,6 @@ import { TransactionBufferExtendInstructionArgs, } from "@sqds/multisig/lib/generated"; import assert from "assert"; -import { BN } from "bn.js"; import * as crypto from "crypto"; import { TestMembers, @@ -75,7 +74,7 @@ describe("Instructions / transaction_buffer_extend", () => { Buffer.from("multisig"), multisigPda.toBuffer(), Buffer.from("transaction_buffer"), - new BN(Number(transactionIndex)).toBuffer("le", 8), + Buffer.from([Number(transactionIndex)]) ], programId ); @@ -110,6 +109,7 @@ describe("Instructions / transaction_buffer_extend", () => { }, { args: { + bufferIndex: Number(transactionIndex), vaultIndex: 0, finalBufferHash: Array.from(messageHash), finalBufferSize: messageBuffer.length, @@ -194,7 +194,7 @@ describe("Instructions / transaction_buffer_extend", () => { Buffer.from("multisig"), multisigPda.toBuffer(), Buffer.from("transaction_buffer"), - new BN(Number(transactionIndex)).toBuffer("le", 8), + Buffer.from([Number(transactionIndex)]) ], programId ); @@ -217,6 +217,7 @@ describe("Instructions / transaction_buffer_extend", () => { }, { args: { + bufferIndex: Number(transactionIndex), vaultIndex: 0, // Must be a SHA256 hash of the message buffer. finalBufferHash: Array.from(messageHash), @@ -241,6 +242,7 @@ describe("Instructions / transaction_buffer_extend", () => { const signature = await connection.sendTransaction(tx, { skipPreflight: true, }); + await connection.confirmTransaction(signature); const transactionBufferAccount = await connection.getAccountInfo( diff --git a/tests/suites/instructions/vaultTransactionCreateFromBuffer.ts b/tests/suites/instructions/vaultTransactionCreateFromBuffer.ts index e8a37db8..fd7f3a1b 100644 --- a/tests/suites/instructions/vaultTransactionCreateFromBuffer.ts +++ b/tests/suites/instructions/vaultTransactionCreateFromBuffer.ts @@ -74,9 +74,10 @@ describe("Instructions / vault_transaction_create_from_buffer", () => { it("set buffer, extend, and create", async () => { const transactionIndex = 1n; + const bufferIndex = 0; const testPayee = Keypair.generate(); - const testIx = await createTestTransferInstruction( + const testIx = createTestTransferInstruction( vaultPda, testPayee.publicKey, 1 * LAMPORTS_PER_SOL @@ -109,7 +110,7 @@ describe("Instructions / vault_transaction_create_from_buffer", () => { Buffer.from("multisig"), multisigPda.toBuffer(), Buffer.from("transaction_buffer"), - new BN(Number(transactionIndex)).toBuffer("le", 8), + Uint8Array.from([bufferIndex]), ], programId ); @@ -132,6 +133,7 @@ describe("Instructions / vault_transaction_create_from_buffer", () => { }, { args: { + bufferIndex: bufferIndex, vaultIndex: 0, // Must be a SHA256 hash of the message buffer. finalBufferHash: Array.from(messageHash), @@ -156,7 +158,6 @@ describe("Instructions / vault_transaction_create_from_buffer", () => { const signature = await connection.sendTransaction(tx, { skipPreflight: true, }); - await connection.confirmTransaction(signature); const transactionBufferAccount = await connection.getAccountInfo( @@ -248,17 +249,7 @@ describe("Instructions / vault_transaction_create_from_buffer", () => { toPubkey: members.almighty.publicKey, lamports: 100 }); - // const mockTransferMessage = new TransactionMessage({ - // payerKey: vaultPda, - // recentBlockhash: (await connection.getLatestBlockhash()).blockhash, - // instructions: [mockTransferIx], - // }); - - // const bytes = multisig.utils.transactionMessageToMultisigTransactionMessageBytes({ - // message: mockTransferMessage, - // addressLookupTableAccounts: [], - // vaultPda, - // }); + // Create final instruction. const thirdIx = @@ -319,6 +310,7 @@ describe("Instructions / vault_transaction_create_from_buffer", () => { it("error: create from buffer with mismatched hash", async () => { const transactionIndex = 2n; + const bufferIndex = 0; // Create a simple transfer instruction const testIx = await createTestTransferInstruction( @@ -346,7 +338,7 @@ describe("Instructions / vault_transaction_create_from_buffer", () => { Buffer.from("multisig"), multisigPda.toBuffer(), Buffer.from("transaction_buffer"), - new BN(Number(transactionIndex)).toBuffer("le", 8), + Uint8Array.from([bufferIndex]), ], programId ); @@ -365,6 +357,7 @@ describe("Instructions / vault_transaction_create_from_buffer", () => { }, { args: { + bufferIndex, vaultIndex: 0, finalBufferHash: Array.from(dummyHash), finalBufferSize: messageBuffer.length,