feat: added `authorizeWithSeed` to the vote program in web3.js (#27627)
* fix: recursively size variable size structs in your buffer layouts * feat: added `authorizeWithSeed` to the vote program in web3.js
This commit is contained in:
parent
a94ada8b3e
commit
466e9948bf
|
@ -1,6 +1,8 @@
|
|||
import {Buffer} from 'buffer';
|
||||
import * as BufferLayout from '@solana/buffer-layout';
|
||||
|
||||
import {VoteAuthorizeWithSeedArgs} from './programs/vote';
|
||||
|
||||
/**
|
||||
* Layout for a public key
|
||||
*/
|
||||
|
@ -141,6 +143,23 @@ export const voteInit = (property: string = 'voteInit') => {
|
|||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Layout for a VoteAuthorizeWithSeedArgs object
|
||||
*/
|
||||
export const voteAuthorizeWithSeedArgs = (
|
||||
property: string = 'voteAuthorizeWithSeedArgs',
|
||||
) => {
|
||||
return BufferLayout.struct<VoteAuthorizeWithSeedArgs>(
|
||||
[
|
||||
BufferLayout.u32('voteAuthorizationType'),
|
||||
publicKey('currentAuthorityDerivedKeyOwnerPubkey'),
|
||||
rustString('currentAuthorityDerivedKeySeed'),
|
||||
publicKey('newAuthorized'),
|
||||
],
|
||||
property,
|
||||
);
|
||||
};
|
||||
|
||||
export function getAlloc(type: any, fields: any): number {
|
||||
const getItemAlloc = (item: any): number => {
|
||||
if (item.span >= 0) {
|
||||
|
|
|
@ -65,6 +65,18 @@ export type AuthorizeVoteParams = {
|
|||
voteAuthorizationType: VoteAuthorizationType;
|
||||
};
|
||||
|
||||
/**
|
||||
* AuthorizeWithSeed instruction params
|
||||
*/
|
||||
export type AuthorizeVoteWithSeedParams = {
|
||||
currentAuthorityDerivedKeyBasePubkey: PublicKey;
|
||||
currentAuthorityDerivedKeyOwnerPubkey: PublicKey;
|
||||
currentAuthorityDerivedKeySeed: string;
|
||||
newAuthorizedPubkey: PublicKey;
|
||||
voteAuthorizationType: VoteAuthorizationType;
|
||||
votePubkey: PublicKey;
|
||||
};
|
||||
|
||||
/**
|
||||
* Withdraw from vote account transaction params
|
||||
*/
|
||||
|
@ -160,6 +172,41 @@ export class VoteInstruction {
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode an authorize instruction and retrieve the instruction params.
|
||||
*/
|
||||
static decodeAuthorizeWithSeed(
|
||||
instruction: TransactionInstruction,
|
||||
): AuthorizeVoteWithSeedParams {
|
||||
this.checkProgramId(instruction.programId);
|
||||
this.checkKeyLength(instruction.keys, 3);
|
||||
|
||||
const {
|
||||
voteAuthorizeWithSeedArgs: {
|
||||
currentAuthorityDerivedKeyOwnerPubkey,
|
||||
currentAuthorityDerivedKeySeed,
|
||||
newAuthorized,
|
||||
voteAuthorizationType,
|
||||
},
|
||||
} = decodeData(
|
||||
VOTE_INSTRUCTION_LAYOUTS.AuthorizeWithSeed,
|
||||
instruction.data,
|
||||
);
|
||||
|
||||
return {
|
||||
currentAuthorityDerivedKeyBasePubkey: instruction.keys[2].pubkey,
|
||||
currentAuthorityDerivedKeyOwnerPubkey: new PublicKey(
|
||||
currentAuthorityDerivedKeyOwnerPubkey,
|
||||
),
|
||||
currentAuthorityDerivedKeySeed: currentAuthorityDerivedKeySeed,
|
||||
newAuthorizedPubkey: new PublicKey(newAuthorized),
|
||||
voteAuthorizationType: {
|
||||
index: voteAuthorizationType,
|
||||
},
|
||||
votePubkey: instruction.keys[0].pubkey,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode a withdraw instruction and retrieve the instruction params.
|
||||
*/
|
||||
|
@ -211,13 +258,23 @@ export type VoteInstructionType =
|
|||
// It would be preferable for this type to be `keyof VoteInstructionInputData`
|
||||
// but Typedoc does not transpile `keyof` expressions.
|
||||
// See https://github.com/TypeStrong/typedoc/issues/1894
|
||||
'Authorize' | 'InitializeAccount' | 'Withdraw';
|
||||
'Authorize' | 'AuthorizeWithSeed' | 'InitializeAccount' | 'Withdraw';
|
||||
|
||||
/** @internal */
|
||||
export type VoteAuthorizeWithSeedArgs = Readonly<{
|
||||
currentAuthorityDerivedKeyOwnerPubkey: Uint8Array;
|
||||
currentAuthorityDerivedKeySeed: string;
|
||||
newAuthorized: Uint8Array;
|
||||
voteAuthorizationType: number;
|
||||
}>;
|
||||
type VoteInstructionInputData = {
|
||||
Authorize: IInstructionInputData & {
|
||||
newAuthorized: Uint8Array;
|
||||
voteAuthorizationType: number;
|
||||
};
|
||||
AuthorizeWithSeed: IInstructionInputData & {
|
||||
voteAuthorizeWithSeedArgs: VoteAuthorizeWithSeedArgs;
|
||||
};
|
||||
InitializeAccount: IInstructionInputData & {
|
||||
voteInit: Readonly<{
|
||||
authorizedVoter: Uint8Array;
|
||||
|
@ -258,6 +315,13 @@ const VOTE_INSTRUCTION_LAYOUTS = Object.freeze<{
|
|||
BufferLayout.ns64('lamports'),
|
||||
]),
|
||||
},
|
||||
AuthorizeWithSeed: {
|
||||
index: 10,
|
||||
layout: BufferLayout.struct<VoteInstructionInputData['AuthorizeWithSeed']>([
|
||||
BufferLayout.u32('instruction'),
|
||||
Layout.voteAuthorizeWithSeedArgs(),
|
||||
]),
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
|
@ -390,6 +454,49 @@ export class VoteProgram {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a transaction that authorizes a new Voter or Withdrawer on the Vote account
|
||||
* where the current Voter or Withdrawer authority is a derived key.
|
||||
*/
|
||||
static authorizeWithSeed(params: AuthorizeVoteWithSeedParams): Transaction {
|
||||
const {
|
||||
currentAuthorityDerivedKeyBasePubkey,
|
||||
currentAuthorityDerivedKeyOwnerPubkey,
|
||||
currentAuthorityDerivedKeySeed,
|
||||
newAuthorizedPubkey,
|
||||
voteAuthorizationType,
|
||||
votePubkey,
|
||||
} = params;
|
||||
|
||||
const type = VOTE_INSTRUCTION_LAYOUTS.AuthorizeWithSeed;
|
||||
const data = encodeData(type, {
|
||||
voteAuthorizeWithSeedArgs: {
|
||||
currentAuthorityDerivedKeyOwnerPubkey: toBuffer(
|
||||
currentAuthorityDerivedKeyOwnerPubkey.toBuffer(),
|
||||
),
|
||||
currentAuthorityDerivedKeySeed: currentAuthorityDerivedKeySeed,
|
||||
newAuthorized: toBuffer(newAuthorizedPubkey.toBuffer()),
|
||||
voteAuthorizationType: voteAuthorizationType.index,
|
||||
},
|
||||
});
|
||||
|
||||
const keys = [
|
||||
{pubkey: votePubkey, isSigner: false, isWritable: true},
|
||||
{pubkey: SYSVAR_CLOCK_PUBKEY, isSigner: false, isWritable: false},
|
||||
{
|
||||
pubkey: currentAuthorityDerivedKeyBasePubkey,
|
||||
isSigner: true,
|
||||
isWritable: false,
|
||||
},
|
||||
];
|
||||
|
||||
return new Transaction().add({
|
||||
keys,
|
||||
programId: this.programId,
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a transaction to withdraw from a Vote account.
|
||||
*/
|
||||
|
|
|
@ -11,6 +11,7 @@ import {
|
|||
sendAndConfirmTransaction,
|
||||
SystemInstruction,
|
||||
Connection,
|
||||
PublicKey,
|
||||
} from '../../src';
|
||||
import {helpers} from '../mocks/rpc-http';
|
||||
import {url} from '../url';
|
||||
|
@ -96,6 +97,29 @@ describe('VoteProgram', () => {
|
|||
);
|
||||
});
|
||||
|
||||
it('authorize with seed', () => {
|
||||
const votePubkey = Keypair.generate().publicKey;
|
||||
const currentAuthorityDerivedKeyBasePubkey = Keypair.generate().publicKey;
|
||||
const currentAuthorityDerivedKeyOwnerPubkey = Keypair.generate().publicKey;
|
||||
const currentAuthorityDerivedKeySeed = 'sunflower';
|
||||
const newAuthorizedPubkey = Keypair.generate().publicKey;
|
||||
const voteAuthorizationType = VoteAuthorizationLayout.Voter;
|
||||
const params = {
|
||||
currentAuthorityDerivedKeyBasePubkey,
|
||||
currentAuthorityDerivedKeyOwnerPubkey,
|
||||
currentAuthorityDerivedKeySeed,
|
||||
newAuthorizedPubkey,
|
||||
voteAuthorizationType,
|
||||
votePubkey,
|
||||
};
|
||||
const transaction = VoteProgram.authorizeWithSeed(params);
|
||||
expect(transaction.instructions).to.have.length(1);
|
||||
const [authorizeWithSeedInstruction] = transaction.instructions;
|
||||
expect(params).to.eql(
|
||||
VoteInstruction.decodeAuthorizeWithSeed(authorizeWithSeedInstruction),
|
||||
);
|
||||
});
|
||||
|
||||
it('withdraw', () => {
|
||||
const votePubkey = Keypair.generate().publicKey;
|
||||
const authorizedWithdrawerPubkey = Keypair.generate().publicKey;
|
||||
|
@ -113,6 +137,107 @@ describe('VoteProgram', () => {
|
|||
});
|
||||
|
||||
if (process.env.TEST_LIVE) {
|
||||
it('change authority from derived key', async () => {
|
||||
const connection = new Connection(url, 'confirmed');
|
||||
|
||||
const newVoteAccount = Keypair.generate();
|
||||
const nodeAccount = Keypair.generate();
|
||||
const derivedKeyOwnerProgram = Keypair.generate();
|
||||
const derivedKeySeed = 'sunflower';
|
||||
const newAuthorizedWithdrawer = Keypair.generate();
|
||||
|
||||
const derivedKeyBaseKeypair = Keypair.generate();
|
||||
const [
|
||||
_1, // eslint-disable-line @typescript-eslint/no-unused-vars
|
||||
_2, // eslint-disable-line @typescript-eslint/no-unused-vars
|
||||
minimumAmount,
|
||||
derivedKey,
|
||||
] = await Promise.all([
|
||||
(async () => {
|
||||
await helpers.airdrop({
|
||||
connection,
|
||||
address: derivedKeyBaseKeypair.publicKey,
|
||||
amount: 12 * LAMPORTS_PER_SOL,
|
||||
});
|
||||
expect(
|
||||
await connection.getBalance(derivedKeyBaseKeypair.publicKey),
|
||||
).to.eq(12 * LAMPORTS_PER_SOL);
|
||||
})(),
|
||||
(async () => {
|
||||
await helpers.airdrop({
|
||||
connection,
|
||||
address: newAuthorizedWithdrawer.publicKey,
|
||||
amount: 0.1 * LAMPORTS_PER_SOL,
|
||||
});
|
||||
expect(
|
||||
await connection.getBalance(newAuthorizedWithdrawer.publicKey),
|
||||
).to.eq(0.1 * LAMPORTS_PER_SOL);
|
||||
})(),
|
||||
connection.getMinimumBalanceForRentExemption(VoteProgram.space),
|
||||
PublicKey.createWithSeed(
|
||||
derivedKeyBaseKeypair.publicKey,
|
||||
derivedKeySeed,
|
||||
derivedKeyOwnerProgram.publicKey,
|
||||
),
|
||||
]);
|
||||
|
||||
// Create initialized Vote account
|
||||
const createAndInitialize = VoteProgram.createAccount({
|
||||
fromPubkey: derivedKeyBaseKeypair.publicKey,
|
||||
votePubkey: newVoteAccount.publicKey,
|
||||
voteInit: new VoteInit(
|
||||
nodeAccount.publicKey,
|
||||
derivedKey,
|
||||
derivedKey,
|
||||
5,
|
||||
),
|
||||
lamports: minimumAmount + 10 * LAMPORTS_PER_SOL,
|
||||
});
|
||||
await sendAndConfirmTransaction(
|
||||
connection,
|
||||
createAndInitialize,
|
||||
[derivedKeyBaseKeypair, newVoteAccount, nodeAccount],
|
||||
{preflightCommitment: 'confirmed'},
|
||||
);
|
||||
expect(await connection.getBalance(newVoteAccount.publicKey)).to.eq(
|
||||
minimumAmount + 10 * LAMPORTS_PER_SOL,
|
||||
);
|
||||
|
||||
// Authorize a new Withdrawer.
|
||||
const authorize = VoteProgram.authorizeWithSeed({
|
||||
currentAuthorityDerivedKeyBasePubkey: derivedKeyBaseKeypair.publicKey,
|
||||
currentAuthorityDerivedKeyOwnerPubkey: derivedKeyOwnerProgram.publicKey,
|
||||
currentAuthorityDerivedKeySeed: derivedKeySeed,
|
||||
newAuthorizedPubkey: newAuthorizedWithdrawer.publicKey,
|
||||
voteAuthorizationType: VoteAuthorizationLayout.Withdrawer,
|
||||
votePubkey: newVoteAccount.publicKey,
|
||||
});
|
||||
await sendAndConfirmTransaction(
|
||||
connection,
|
||||
authorize,
|
||||
[derivedKeyBaseKeypair],
|
||||
{preflightCommitment: 'confirmed'},
|
||||
);
|
||||
|
||||
// Test newAuthorizedWithdrawer may withdraw.
|
||||
const recipient = Keypair.generate();
|
||||
const 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,
|
||||
);
|
||||
});
|
||||
|
||||
it('live vote actions', async () => {
|
||||
const connection = new Connection(url, 'confirmed');
|
||||
|
||||
|
|
Loading…
Reference in New Issue