feat(vote-program): support VoteInstruction::Authorize (#22978)
This commit is contained in:
parent
86d465c531
commit
ae175a026b
|
@ -49,6 +49,17 @@ export type InitializeAccountParams = {
|
|||
voteInit: VoteInit;
|
||||
};
|
||||
|
||||
/**
|
||||
* Authorize instruction params
|
||||
*/
|
||||
export type AuthorizeVoteParams = {
|
||||
votePubkey: PublicKey;
|
||||
/** Current vote or withdraw authority, depending on `voteAuthorizationType` */
|
||||
authorizedPubkey: PublicKey;
|
||||
newAuthorizedPubkey: PublicKey;
|
||||
voteAuthorizationType: VoteAuthorizationType;
|
||||
};
|
||||
|
||||
/**
|
||||
* Withdraw from vote account transaction params
|
||||
*/
|
||||
|
@ -120,6 +131,30 @@ export class VoteInstruction {
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode an authorize instruction and retrieve the instruction params.
|
||||
*/
|
||||
static decodeAuthorize(
|
||||
instruction: TransactionInstruction,
|
||||
): AuthorizeVoteParams {
|
||||
this.checkProgramId(instruction.programId);
|
||||
this.checkKeyLength(instruction.keys, 3);
|
||||
|
||||
const {newAuthorized, voteAuthorizationType} = decodeData(
|
||||
VOTE_INSTRUCTION_LAYOUTS.Authorize,
|
||||
instruction.data,
|
||||
);
|
||||
|
||||
return {
|
||||
votePubkey: instruction.keys[0].pubkey,
|
||||
authorizedPubkey: instruction.keys[2].pubkey,
|
||||
newAuthorizedPubkey: new PublicKey(newAuthorized),
|
||||
voteAuthorizationType: {
|
||||
index: voteAuthorizationType,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode a withdraw instruction and retrieve the instruction params.
|
||||
*/
|
||||
|
@ -166,7 +201,10 @@ export class VoteInstruction {
|
|||
/**
|
||||
* An enumeration of valid VoteInstructionType's
|
||||
*/
|
||||
export type VoteInstructionType = 'InitializeAccount' | 'Withdraw';
|
||||
export type VoteInstructionType =
|
||||
| 'Authorize'
|
||||
| 'InitializeAccount'
|
||||
| 'Withdraw';
|
||||
|
||||
const VOTE_INSTRUCTION_LAYOUTS: {
|
||||
[type in VoteInstructionType]: InstructionType;
|
||||
|
@ -178,6 +216,14 @@ const VOTE_INSTRUCTION_LAYOUTS: {
|
|||
Layout.voteInit(),
|
||||
]),
|
||||
},
|
||||
Authorize: {
|
||||
index: 1,
|
||||
layout: BufferLayout.struct([
|
||||
BufferLayout.u32('instruction'),
|
||||
Layout.publicKey('newAuthorized'),
|
||||
BufferLayout.u32('voteAuthorizationType'),
|
||||
]),
|
||||
},
|
||||
Withdraw: {
|
||||
index: 3,
|
||||
layout: BufferLayout.struct([
|
||||
|
@ -187,6 +233,26 @@ const VOTE_INSTRUCTION_LAYOUTS: {
|
|||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* VoteAuthorize type
|
||||
*/
|
||||
export type VoteAuthorizationType = {
|
||||
/** The VoteAuthorize index (from solana-vote-program) */
|
||||
index: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* An enumeration of valid VoteAuthorization layouts.
|
||||
*/
|
||||
export const VoteAuthorizationLayout = Object.freeze({
|
||||
Voter: {
|
||||
index: 0,
|
||||
},
|
||||
Withdrawer: {
|
||||
index: 1,
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Factory class for transactions to interact with the Vote program
|
||||
*/
|
||||
|
@ -267,6 +333,36 @@ export class VoteProgram {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a transaction that authorizes a new Voter or Withdrawer on the Vote account.
|
||||
*/
|
||||
static authorize(params: AuthorizeVoteParams): Transaction {
|
||||
const {
|
||||
votePubkey,
|
||||
authorizedPubkey,
|
||||
newAuthorizedPubkey,
|
||||
voteAuthorizationType,
|
||||
} = params;
|
||||
|
||||
const type = VOTE_INSTRUCTION_LAYOUTS.Authorize;
|
||||
const data = encodeData(type, {
|
||||
newAuthorized: toBuffer(newAuthorizedPubkey.toBuffer()),
|
||||
voteAuthorizationType: voteAuthorizationType.index,
|
||||
});
|
||||
|
||||
const keys = [
|
||||
{pubkey: votePubkey, isSigner: false, isWritable: true},
|
||||
{pubkey: SYSVAR_CLOCK_PUBKEY, isSigner: false, isWritable: false},
|
||||
{pubkey: authorizedPubkey, isSigner: true, isWritable: false},
|
||||
];
|
||||
|
||||
return new Transaction().add({
|
||||
keys,
|
||||
programId: this.programId,
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a transaction to withdraw from a Vote account.
|
||||
*/
|
||||
|
|
|
@ -4,6 +4,7 @@ import chaiAsPromised from 'chai-as-promised';
|
|||
import {
|
||||
Keypair,
|
||||
LAMPORTS_PER_SOL,
|
||||
VoteAuthorizationLayout,
|
||||
VoteInit,
|
||||
VoteInstruction,
|
||||
VoteProgram,
|
||||
|
@ -76,6 +77,25 @@ describe('VoteProgram', () => {
|
|||
);
|
||||
});
|
||||
|
||||
it('authorize', () => {
|
||||
const votePubkey = Keypair.generate().publicKey;
|
||||
const authorizedPubkey = Keypair.generate().publicKey;
|
||||
const newAuthorizedPubkey = Keypair.generate().publicKey;
|
||||
const voteAuthorizationType = VoteAuthorizationLayout.Voter;
|
||||
const params = {
|
||||
votePubkey,
|
||||
authorizedPubkey,
|
||||
newAuthorizedPubkey,
|
||||
voteAuthorizationType,
|
||||
};
|
||||
const transaction = VoteProgram.authorize(params);
|
||||
expect(transaction.instructions).to.have.length(1);
|
||||
const [authorizeInstruction] = transaction.instructions;
|
||||
expect(params).to.eql(
|
||||
VoteInstruction.decodeAuthorize(authorizeInstruction),
|
||||
);
|
||||
});
|
||||
|
||||
it('withdraw', () => {
|
||||
const votePubkey = Keypair.generate().publicKey;
|
||||
const authorizedWithdrawerPubkey = Keypair.generate().publicKey;
|
||||
|
@ -133,9 +153,8 @@ describe('VoteProgram', () => {
|
|||
authorized.publicKey,
|
||||
5,
|
||||
),
|
||||
lamports: minimumAmount + 2 * LAMPORTS_PER_SOL,
|
||||
lamports: minimumAmount + 10 * LAMPORTS_PER_SOL,
|
||||
});
|
||||
|
||||
await sendAndConfirmTransaction(
|
||||
connection,
|
||||
createAndInitialize,
|
||||
|
@ -143,24 +162,104 @@ describe('VoteProgram', () => {
|
|||
{preflightCommitment: 'confirmed'},
|
||||
);
|
||||
expect(await connection.getBalance(newVoteAccount.publicKey)).to.eq(
|
||||
minimumAmount + 2 * LAMPORTS_PER_SOL,
|
||||
minimumAmount + 10 * LAMPORTS_PER_SOL,
|
||||
);
|
||||
|
||||
// Withdraw from Vote account
|
||||
const recipient = Keypair.generate();
|
||||
let recipient = Keypair.generate();
|
||||
let withdraw = VoteProgram.withdraw({
|
||||
votePubkey: newVoteAccount.publicKey,
|
||||
authorizedWithdrawerPubkey: authorized.publicKey,
|
||||
lamports: LAMPORTS_PER_SOL,
|
||||
toPubkey: recipient.publicKey,
|
||||
});
|
||||
|
||||
await sendAndConfirmTransaction(connection, withdraw, [authorized], {
|
||||
preflightCommitment: 'confirmed',
|
||||
});
|
||||
expect(await connection.getBalance(recipient.publicKey)).to.eq(
|
||||
LAMPORTS_PER_SOL,
|
||||
);
|
||||
|
||||
const newAuthorizedWithdrawer = Keypair.generate();
|
||||
await helpers.airdrop({
|
||||
connection,
|
||||
address: newAuthorizedWithdrawer.publicKey,
|
||||
amount: LAMPORTS_PER_SOL,
|
||||
});
|
||||
expect(
|
||||
await connection.getBalance(newAuthorizedWithdrawer.publicKey),
|
||||
).to.eq(LAMPORTS_PER_SOL);
|
||||
|
||||
// Authorize a new Withdrawer.
|
||||
let authorize = VoteProgram.authorize({
|
||||
votePubkey: newVoteAccount.publicKey,
|
||||
authorizedPubkey: authorized.publicKey,
|
||||
newAuthorizedPubkey: newAuthorizedWithdrawer.publicKey,
|
||||
voteAuthorizationType: VoteAuthorizationLayout.Withdrawer,
|
||||
});
|
||||
await sendAndConfirmTransaction(connection, authorize, [authorized], {
|
||||
preflightCommitment: 'confirmed',
|
||||
});
|
||||
|
||||
// Test old authorized cannot withdraw anymore.
|
||||
withdraw = VoteProgram.withdraw({
|
||||
votePubkey: newVoteAccount.publicKey,
|
||||
authorizedWithdrawerPubkey: authorized.publicKey,
|
||||
lamports: minimumAmount,
|
||||
toPubkey: recipient.publicKey,
|
||||
});
|
||||
await expect(
|
||||
sendAndConfirmTransaction(connection, withdraw, [authorized], {
|
||||
preflightCommitment: 'confirmed',
|
||||
}),
|
||||
).to.be.rejected;
|
||||
|
||||
// Test newAuthorizedWithdrawer may withdraw.
|
||||
recipient = Keypair.generate();
|
||||
withdraw = VoteProgram.withdraw({
|
||||
votePubkey: newVoteAccount.publicKey,
|
||||
authorizedWithdrawerPubkey: newAuthorizedWithdrawer.publicKey,
|
||||
lamports: LAMPORTS_PER_SOL,
|
||||
toPubkey: recipient.publicKey,
|
||||
});
|
||||
await sendAndConfirmTransaction(
|
||||
connection,
|
||||
withdraw,
|
||||
[newAuthorizedWithdrawer],
|
||||
{
|
||||
preflightCommitment: 'confirmed',
|
||||
},
|
||||
);
|
||||
expect(await connection.getBalance(recipient.publicKey)).to.eq(
|
||||
LAMPORTS_PER_SOL,
|
||||
);
|
||||
|
||||
const newAuthorizedVoter = Keypair.generate();
|
||||
await helpers.airdrop({
|
||||
connection,
|
||||
address: newAuthorizedVoter.publicKey,
|
||||
amount: LAMPORTS_PER_SOL,
|
||||
});
|
||||
expect(await connection.getBalance(newAuthorizedVoter.publicKey)).to.eq(
|
||||
LAMPORTS_PER_SOL,
|
||||
);
|
||||
|
||||
// The authorized Withdrawer may sign to authorize a new Voter, see
|
||||
// https://github.com/solana-labs/solana/issues/22521
|
||||
authorize = VoteProgram.authorize({
|
||||
votePubkey: newVoteAccount.publicKey,
|
||||
authorizedPubkey: newAuthorizedWithdrawer.publicKey,
|
||||
newAuthorizedPubkey: newAuthorizedVoter.publicKey,
|
||||
voteAuthorizationType: VoteAuthorizationLayout.Voter,
|
||||
});
|
||||
await sendAndConfirmTransaction(
|
||||
connection,
|
||||
authorize,
|
||||
[newAuthorizedWithdrawer],
|
||||
{
|
||||
preflightCommitment: 'confirmed',
|
||||
},
|
||||
);
|
||||
}).timeout(10 * 1000);
|
||||
}
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue