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
85 changes: 85 additions & 0 deletions clients/js/test/close.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import {
appendTransactionMessageInstructions,
generateKeyPairSigner,
getUtf8Encoder,
isSolanaError,
pipe,
SOLANA_ERROR__INSTRUCTION_ERROR__INCORRECT_AUTHORITY,
} from '@solana/kit';
import test from 'ava';
import { fetchMaybeMetadata, getCloseInstruction, getSetAuthorityInstruction } from '../src';
Expand Down Expand Up @@ -270,3 +272,86 @@ test('it can close keypair buffers', async t => {
const account = await fetchMaybeMetadata(client.rpc, buffer.address);
t.false(account.exists);
});

test('it cannot close a keypair buffer with a different authority set using the buffer keypair', async t => {
// Given the following payer.
const client = createDefaultSolanaClient();
const payer = await generateKeyPairSignerWithSol(client);

// And the following pre-allocated keypair buffer.
const buffer = await createKeypairBuffer(client, {
payer,
data: getUtf8Encoder().encode('Hello, World!'),
});

// And we set a different authority on the buffer account.
const bufferAuthority = await generateKeyPairSigner();
const setAuthorityIx = getSetAuthorityInstruction({
account: buffer.address,
authority: buffer,
newAuthority: bufferAuthority.address,
});
await pipe(
await createDefaultTransaction(client, payer),
tx => appendTransactionMessageInstruction(setAuthorityIx, tx),
tx => signAndSendTransaction(client, tx),
);

// When the buffer keypair tries to close its own account.
const closeIx = getCloseInstruction({
account: buffer.address,
authority: buffer,
destination: payer.address,
});
const promise = pipe(
await createDefaultTransaction(client, payer),
tx => appendTransactionMessageInstruction(closeIx, tx),
tx => signAndSendTransaction(client, tx),
);

// Then we expect a program error.
const error = await t.throwsAsync(promise);
t.true(isSolanaError(error));
t.true(isSolanaError(error.cause, SOLANA_ERROR__INSTRUCTION_ERROR__INCORRECT_AUTHORITY));
});

test('it can close a keypair buffer with its authority', async t => {
// Given the following payer.
const client = createDefaultSolanaClient();
const payer = await generateKeyPairSignerWithSol(client);

// And the following pre-allocated keypair buffer.
const buffer = await createKeypairBuffer(client, {
payer,
data: getUtf8Encoder().encode('Hello, World!'),
});

// And we set a different authority on the buffer account.
const bufferAuthority = await generateKeyPairSigner();
const setAuthorityIx = getSetAuthorityInstruction({
account: buffer.address,
authority: buffer,
newAuthority: bufferAuthority.address,
});
await pipe(
await createDefaultTransaction(client, payer),
tx => appendTransactionMessageInstruction(setAuthorityIx, tx),
tx => signAndSendTransaction(client, tx),
);

// When the authority closes the buffer account.
const closeIx = getCloseInstruction({
account: buffer.address,
authority: bufferAuthority,
destination: payer.address,
});
await pipe(
await createDefaultTransaction(client, payer),
tx => appendTransactionMessageInstruction(closeIx, tx),
tx => signAndSendTransaction(client, tx),
);

// Then we expect the buffer account to no longer exist.
const account = await fetchMaybeMetadata(client.rpc, buffer.address);
t.false(account.exists);
});
24 changes: 10 additions & 14 deletions program/src/processor/close.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,28 +27,24 @@ pub fn close(accounts: &[AccountInfo]) -> ProgramResult {

// account
// - must have data
// - authority must match (if not a keypair buffer)
// - authority must match

let account_data = if account.data_is_empty() {
return Err(ProgramError::UninitializedAccount);
} else {
unsafe { account.borrow_data_unchecked() }
};

// We only need to validate the authority if it is not a keypair buffer,
// since we already validated that the authority is a signer.
if account.key() != authority.key() {
match AccountDiscriminator::try_from(account_data[0])? {
AccountDiscriminator::Buffer => {
let buffer = unsafe { Buffer::from_bytes_unchecked(account_data) };
validate_authority(buffer, authority, program, program_data)?
}
AccountDiscriminator::Metadata => {
let header = validate_metadata(account)?;
validate_authority(header, authority, program, program_data)?
}
_ => return Err(ProgramError::InvalidAccountData),
match AccountDiscriminator::try_from(account_data[0])? {
AccountDiscriminator::Buffer => {
let buffer = unsafe { Buffer::from_bytes_unchecked(account_data) };
validate_authority(buffer, authority, program, program_data)?
}
AccountDiscriminator::Metadata => {
let header = validate_metadata(account)?;
validate_authority(header, authority, program, program_data)?
}
_ => return Err(ProgramError::InvalidAccountData),
}

// Move the lamports to the destination account and close the account.
Expand Down
Loading