diff --git a/web3.js/module.d.ts b/web3.js/module.d.ts index 9ced19c2e5..6509952f7d 100644 --- a/web3.js/module.d.ts +++ b/web3.js/module.d.ts @@ -369,52 +369,74 @@ declare module '@solana/web3.js' { constructor(unixTimestamp: number, epoch: number, custodian: PublicKey); } + export type CreateStakeAccountParams = { + fromPubkey: PublicKey; + stakePubkey: PublicKey; + authorized: Authorized; + lockup: Lockup; + lamports: number; + }; + + export type CreateStakeAccountWithSeedParams = { + fromPubkey: PublicKey; + stakePubkey: PublicKey; + basePubkey: PublicKey; + seed: string; + authorized: Authorized; + lockup: Lockup; + lamports: number; + }; + + export type InitializeStakeParams = { + stakePubkey: PublicKey; + authorized: Authorized; + lockup: Lockup; + }; + + export type DelegateStakeParams = { + stakePubkey: PublicKey; + authorizedPubkey: PublicKey; + votePubkey: PublicKey; + }; + + export type AuthorizeStakeParams = { + stakePubkey: PublicKey; + authorizedPubkey: PublicKey; + newAuthorizedPubkey: PublicKey; + stakeAuthorizationType: StakeAuthorizationType; + }; + + export type SplitStakeParams = { + stakePubkey: PublicKey; + authorizedPubkey: PublicKey; + splitStakePubkey: PublicKey; + lamports: number; + }; + + export type WithdrawStakeParams = { + stakePubkey: PublicKey; + authorizedPubkey: PublicKey; + toPubkey: PublicKey; + lamports: number; + }; + + export type DeactivateStakeParams = { + stakePubkey: PublicKey; + authorizedPubkey: PublicKey; + }; + export class StakeProgram { static programId: PublicKey; static space: number; - - static createAccount( - from: PublicKey, - stakeAccount: PublicKey, - authorized: Authorized, - lockup: Lockup, - lamports: number, - ): Transaction; + static createAccount(params: CreateStakeAccountParams): Transaction; static createAccountWithSeed( - from: PublicKey, - stakeAccount: PublicKey, - seed: string, - authorized: Authorized, - lockup: Lockup, - lamports: number, - ): Transaction; - static delegate( - stakeAccount: PublicKey, - authorizedPubkey: PublicKey, - votePubkey: PublicKey, - ): Transaction; - static authorize( - stakeAccount: PublicKey, - authorizedPubkey: PublicKey, - newAuthorized: PublicKey, - stakeAuthorizationType: StakeAuthorizationType, - ): Transaction; - static split( - stakeAccount: PublicKey, - authorizedPubkey: PublicKey, - lamports: number, - splitStakePubkey: PublicKey, - ): Transaction; - static withdraw( - stakeAccount: PublicKey, - withdrawerPubkey: PublicKey, - to: PublicKey, - lamports: number, - ): Transaction; - static deactivate( - stakeAccount: PublicKey, - authorizedPubkey: PublicKey, + params: CreateStakeAccountWithSeedParams, ): Transaction; + static delegate(params: DelegateStakeParams): Transaction; + static authorize(params: AuthorizeStakeParams): Transaction; + static split(params: SplitStakeParams): Transaction; + static withdraw(params: WithdrawStakeParams): Transaction; + static deactivate(params: DeactivateStakeParams): Transaction; } export type StakeInstructionType = @@ -429,16 +451,26 @@ declare module '@solana/web3.js' { [type in StakeInstructionType]: InstructionType; }; - export class StakeInstruction extends TransactionInstruction { - type: StakeInstructionType; - stakePublicKey: PublicKey | null; - authorizedPublicKey: PublicKey | null; - - constructor( - opts: TransactionInstructionCtorFields, - type: StakeInstructionType, - ); - static from(instruction: TransactionInstruction): StakeInstruction; + export class StakeInstruction { + static decodeInstructionType( + instruction: TransactionInstruction, + ): StakeInstructionType; + static decodeInitialize( + instruction: TransactionInstruction, + ): InitializeStakeParams; + static decodeDelegate( + instruction: TransactionInstruction, + ): DelegateStakeParams; + static decodeAuthorize( + instruction: TransactionInstruction, + ): AuthorizeStakeParams; + static decodeSplit(instruction: TransactionInstruction): SplitStakeParams; + static decodeWithdraw( + instruction: TransactionInstruction, + ): WithdrawStakeParams; + static decodeDeactivate( + instruction: TransactionInstruction, + ): DeactivateStakeParams; } // === src/system-program.js === diff --git a/web3.js/module.flow.js b/web3.js/module.flow.js index 32129a5380..c09f53010a 100644 --- a/web3.js/module.flow.js +++ b/web3.js/module.flow.js @@ -266,52 +266,74 @@ declare module '@solana/web3.js' { ): Lockup; } + declare export type CreateStakeAccountParams = {| + fromPubkey: PublicKey, + stakePubkey: PublicKey, + authorized: Authorized, + lockup: Lockup, + lamports: number, + |}; + + declare export type CreateStakeAccountWithSeedParams = {| + fromPubkey: PublicKey, + stakePubkey: PublicKey, + basePubkey: PublicKey, + seed: string, + authorized: Authorized, + lockup: Lockup, + lamports: number, + |}; + + declare export type InitializeStakeParams = {| + stakePubkey: PublicKey, + authorized: Authorized, + lockup: Lockup, + |}; + + declare export type DelegateStakeParams = {| + stakePubkey: PublicKey, + authorizedPubkey: PublicKey, + votePubkey: PublicKey, + |}; + + declare export type AuthorizeStakeParams = {| + stakePubkey: PublicKey, + authorizedPubkey: PublicKey, + newAuthorizedPubkey: PublicKey, + stakeAuthorizationType: StakeAuthorizationType, + |}; + + declare export type SplitStakeParams = {| + stakePubkey: PublicKey, + authorizedPubkey: PublicKey, + splitStakePubkey: PublicKey, + lamports: number, + |}; + + declare export type WithdrawStakeParams = {| + stakePubkey: PublicKey, + authorizedPubkey: PublicKey, + toPubkey: PublicKey, + lamports: number, + |}; + + declare export type DeactivateStakeParams = {| + stakePubkey: PublicKey, + authorizedPubkey: PublicKey, + |}; + declare export class StakeProgram { static programId: PublicKey; static space: number; - - static createAccount( - from: PublicKey, - stakeAccount: PublicKey, - authorized: Authorized, - lockup: Lockup, - lamports: number, - ): Transaction; + static createAccount(params: CreateStakeAccountParams): Transaction; static createAccountWithSeed( - from: PublicKey, - stakeAccount: PublicKey, - seed: string, - authorized: Authorized, - lockup: Lockup, - lamports: number, - ): Transaction; - static delegate( - stakeAccount: PublicKey, - authorizedPubkey: PublicKey, - votePubkey: PublicKey, - ): Transaction; - static authorize( - stakeAccount: PublicKey, - authorizedPubkey: PublicKey, - newAuthorized: PublicKey, - stakeAuthorizationType: StakeAuthorizationType, - ): Transaction; - static split( - stakeAccount: PublicKey, - authorizedPubkey: PublicKey, - lamports: number, - splitStakePubkey: PublicKey, - ): Transaction; - static withdraw( - stakeAccount: PublicKey, - withdrawerPubkey: PublicKey, - to: PublicKey, - lamports: number, - ): Transaction; - static deactivate( - stakeAccount: PublicKey, - authorizedPubkey: PublicKey, + params: CreateStakeAccountWithSeedParams, ): Transaction; + static delegate(params: DelegateStakeParams): Transaction; + static authorize(params: AuthorizeStakeParams): Transaction; + static split(params: SplitStakeParams): Transaction; + static withdraw(params: WithdrawStakeParams): Transaction; + static deactivate(params: DeactivateStakeParams): Transaction; } declare export type StakeInstructionType = @@ -326,16 +348,26 @@ declare module '@solana/web3.js' { [StakeInstructionType]: InstructionType, }; - declare export class StakeInstruction extends TransactionInstruction { - type: StakeInstructionType; - stakePublicKey: PublicKey | null; - authorizedPublicKey: PublicKey | null; - - constructor( - opts?: TransactionInstructionCtorFields, - type: StakeInstructionType, - ): StakeInstruction; - static from(instruction: TransactionInstruction): StakeInstruction; + declare export class StakeInstruction { + static decodeInstructionType( + instruction: TransactionInstruction, + ): StakeInstructionType; + static decodeInitialize( + instruction: TransactionInstruction, + ): InitializeStakeParams; + static decodeDelegate( + instruction: TransactionInstruction, + ): DelegateStakeParams; + static decodeAuthorize( + instruction: TransactionInstruction, + ): AuthorizeStakeParams; + static decodeSplit(instruction: TransactionInstruction): SplitStakeParams; + static decodeWithdraw( + instruction: TransactionInstruction, + ): WithdrawStakeParams; + static decodeDeactivate( + instruction: TransactionInstruction, + ): DeactivateStakeParams; } // === src/system-program.js === diff --git a/web3.js/src/instruction.js b/web3.js/src/instruction.js index a507d8dd9c..6e75f2113e 100644 --- a/web3.js/src/instruction.js +++ b/web3.js/src/instruction.js @@ -25,3 +25,23 @@ export function encodeData(type: InstructionType, fields: Object): Buffer { type.layout.encode(layoutFields, data); return data; } + +/** + * Decode instruction data buffer using an InstructionType + */ +export function decodeData(type: InstructionType, buffer: Buffer): Object { + let data; + try { + data = type.layout.decode(buffer); + } catch (err) { + throw new Error('invalid instruction; ' + err); + } + + if (data.instruction !== type.index) { + throw new Error( + `invalid instruction; instruction index mismatch ${data.instruction} != ${type.index}`, + ); + } + + return data; +} diff --git a/web3.js/src/stake-program.js b/web3.js/src/stake-program.js index 17875577e1..7956ed84e5 100644 --- a/web3.js/src/stake-program.js +++ b/web3.js/src/stake-program.js @@ -2,7 +2,7 @@ import * as BufferLayout from 'buffer-layout'; -import {encodeData} from './instruction'; +import {encodeData, decodeData} from './instruction'; import * as Layout from './layout'; import {PublicKey} from './publickey'; import {SystemProgram} from './system-program'; @@ -12,7 +12,6 @@ import { SYSVAR_STAKE_HISTORY_PUBKEY, } from './sysvar'; import {Transaction, TransactionInstruction} from './transaction'; -import type {TransactionInstructionCtorFields} from './transaction'; export const STAKE_CONFIG_ID = new PublicKey( 'StakeConfig11111111111111111111111111111111', @@ -46,31 +45,137 @@ export class Lockup { } } +/** + * Create stake account transaction params + * @typedef {Object} CreateStakeAccountParams + * @property {PublicKey} fromPubkey + * @property {PublicKey} stakePubkey + * @property {Authorized} authorized + * @property {Lockup} lockup + * @property {number} lamports + */ +export type CreateStakeAccountParams = {| + fromPubkey: PublicKey, + stakePubkey: PublicKey, + authorized: Authorized, + lockup: Lockup, + lamports: number, +|}; + +/** + * Create stake account with seed transaction params + * @typedef {Object} CreateStakeAccountWithSeedParams + * @property {PublicKey} fromPubkey + * @property {PublicKey} stakePubkey + * @property {PublicKey} basePubkey + * @property {string} seed + * @property {Authorized} authorized + * @property {Lockup} lockup + * @property {number} lamports + */ +export type CreateStakeAccountWithSeedParams = {| + fromPubkey: PublicKey, + stakePubkey: PublicKey, + basePubkey: PublicKey, + seed: string, + authorized: Authorized, + lockup: Lockup, + lamports: number, +|}; + +/** + * Initialize stake instruction params + * @typedef {Object} InitializeStakeParams + * @property {PublicKey} stakePubkey + * @property {Authorized} authorized + * @property {Lockup} lockup + */ +export type InitializeStakeParams = {| + stakePubkey: PublicKey, + authorized: Authorized, + lockup: Lockup, +|}; + +/** + * Delegate stake instruction params + * @typedef {Object} DelegateStakeParams + * @property {PublicKey} stakePubkey + * @property {PublicKey} authorizedPubkey + * @property {PublicKey} votePubkey + */ +export type DelegateStakeParams = {| + stakePubkey: PublicKey, + authorizedPubkey: PublicKey, + votePubkey: PublicKey, +|}; + +/** + * Authorize stake instruction params + * @typedef {Object} AuthorizeStakeParams + * @property {PublicKey} stakePubkey + * @property {PublicKey} authorizedPubkey + * @property {PublicKey} newAuthorizedPubkey + * @property {StakeAuthorizationType} stakeAuthorizationType + */ +export type AuthorizeStakeParams = {| + stakePubkey: PublicKey, + authorizedPubkey: PublicKey, + newAuthorizedPubkey: PublicKey, + stakeAuthorizationType: StakeAuthorizationType, +|}; + +/** + * Split stake instruction params + * @typedef {Object} SplitStakeParams + * @property {PublicKey} stakePubkey + * @property {PublicKey} authorizedPubkey + * @property {PublicKey} splitStakePubkey + * @property {number} lamports + */ +export type SplitStakeParams = {| + stakePubkey: PublicKey, + authorizedPubkey: PublicKey, + splitStakePubkey: PublicKey, + lamports: number, +|}; + +/** + * Withdraw stake instruction params + * @typedef {Object} WithdrawStakeParams + * @property {PublicKey} stakePubkey + * @property {PublicKey} authorizedPubkey + * @property {PublicKey} toPubkey + * @property {number} lamports + */ +export type WithdrawStakeParams = {| + stakePubkey: PublicKey, + authorizedPubkey: PublicKey, + toPubkey: PublicKey, + lamports: number, +|}; + +/** + * Deactivate stake instruction params + * @typedef {Object} DeactivateStakeParams + * @property {PublicKey} stakePubkey + * @property {PublicKey} authorizedPubkey + */ +export type DeactivateStakeParams = {| + stakePubkey: PublicKey, + authorizedPubkey: PublicKey, +|}; + /** * Stake Instruction class */ -export class StakeInstruction extends TransactionInstruction { - _type: StakeInstructionType; - - constructor( - opts?: TransactionInstructionCtorFields, - type: StakeInstructionType, - ) { - if ( - opts && - opts.programId && - !opts.programId.equals(StakeProgram.programId) - ) { - throw new Error('programId incorrect; not a StakeInstruction'); - } - super(opts); - this._type = type; - } - - static from(instruction: TransactionInstruction): StakeInstruction { - if (!instruction.programId.equals(StakeProgram.programId)) { - throw new Error('programId incorrect; not StakeProgram'); - } +export class StakeInstruction { + /** + * Decode a stake instruction and retrieve the instruction type. + */ + static decodeInstructionType( + instruction: TransactionInstruction, + ): StakeInstructionType { + this.checkProgramId(instruction.programId); const instructionTypeLayout = BufferLayout.u32('instruction'); const typeIndex = instructionTypeLayout.decode(instruction.data); @@ -81,63 +186,155 @@ export class StakeInstruction extends TransactionInstruction { type = t; } } + if (!type) { throw new Error('Instruction type incorrect; not a StakeInstruction'); } - return new StakeInstruction( - { - keys: instruction.keys, - programId: instruction.programId, - data: instruction.data, - }, - type, + + return type; + } + + /** + * Decode a initialize stake instruction and retrieve the instruction params. + */ + static decodeInitialize( + instruction: TransactionInstruction, + ): InitializeStakeParams { + this.checkProgramId(instruction.programId); + this.checkKeyLength(instruction.keys, 2); + + const {authorized, lockup} = decodeData( + STAKE_INSTRUCTION_LAYOUTS.Initialize, + instruction.data, ); + + return { + stakePubkey: instruction.keys[0].pubkey, + authorized: new Authorized( + new PublicKey(authorized.staker), + new PublicKey(authorized.withdrawer), + ), + lockup: new Lockup( + lockup.unixTimestamp, + lockup.epoch, + new PublicKey(lockup.custodian), + ), + }; } /** - * Type of StakeInstruction + * Decode a delegate stake instruction and retrieve the instruction params. */ - get type(): StakeInstructionType { - return this._type; + static decodeDelegate( + instruction: TransactionInstruction, + ): DelegateStakeParams { + this.checkProgramId(instruction.programId); + this.checkKeyLength(instruction.keys, 6); + decodeData(STAKE_INSTRUCTION_LAYOUTS.Delegate, instruction.data); + + return { + stakePubkey: instruction.keys[0].pubkey, + votePubkey: instruction.keys[1].pubkey, + authorizedPubkey: instruction.keys[5].pubkey, + }; } /** - * The `stake account` public key of the instruction; - * returns null if StakeInstructionType does not support this field + * Decode an authorize stake instruction and retrieve the instruction params. */ - get stakePublicKey(): PublicKey | null { - switch (this.type) { - case 'Initialize': - case 'Delegate': - case 'Authorize': - case 'Split': - case 'Withdraw': - case 'Deactivate': - return this.keys[0].pubkey; - default: - return null; + static decodeAuthorize( + instruction: TransactionInstruction, + ): AuthorizeStakeParams { + this.checkProgramId(instruction.programId); + this.checkKeyLength(instruction.keys, 3); + const {newAuthorized, stakeAuthorizationType} = decodeData( + STAKE_INSTRUCTION_LAYOUTS.Authorize, + instruction.data, + ); + + return { + stakePubkey: instruction.keys[0].pubkey, + authorizedPubkey: instruction.keys[2].pubkey, + newAuthorizedPubkey: new PublicKey(newAuthorized), + stakeAuthorizationType: { + index: stakeAuthorizationType, + }, + }; + } + + /** + * Decode a split stake instruction and retrieve the instruction params. + */ + static decodeSplit(instruction: TransactionInstruction): SplitStakeParams { + this.checkProgramId(instruction.programId); + this.checkKeyLength(instruction.keys, 3); + const {lamports} = decodeData( + STAKE_INSTRUCTION_LAYOUTS.Split, + instruction.data, + ); + + return { + stakePubkey: instruction.keys[0].pubkey, + splitStakePubkey: instruction.keys[1].pubkey, + authorizedPubkey: instruction.keys[2].pubkey, + lamports, + }; + } + + /** + * Decode a withdraw stake instruction and retrieve the instruction params. + */ + static decodeWithdraw( + instruction: TransactionInstruction, + ): WithdrawStakeParams { + this.checkProgramId(instruction.programId); + this.checkKeyLength(instruction.keys, 5); + const {lamports} = decodeData( + STAKE_INSTRUCTION_LAYOUTS.Withdraw, + instruction.data, + ); + + return { + stakePubkey: instruction.keys[0].pubkey, + toPubkey: instruction.keys[1].pubkey, + authorizedPubkey: instruction.keys[4].pubkey, + lamports, + }; + } + + /** + * Decode a deactivate stake instruction and retrieve the instruction params. + */ + static decodeDeactivate( + instruction: TransactionInstruction, + ): DeactivateStakeParams { + this.checkProgramId(instruction.programId); + this.checkKeyLength(instruction.keys, 3); + decodeData(STAKE_INSTRUCTION_LAYOUTS.Deactivate, instruction.data); + + return { + stakePubkey: instruction.keys[0].pubkey, + authorizedPubkey: instruction.keys[2].pubkey, + }; + } + + /** + * @private + */ + static checkProgramId(programId: PublicKey) { + if (!programId.equals(StakeProgram.programId)) { + throw new Error('invalid instruction; programId is not StakeProgram'); } } /** - * The `authorized account` public key of the instruction; - * - * returns null if StakeInstructionType does not support this field + * @private */ - get authorizedPublicKey(): PublicKey | null { - switch (this.type) { - case 'Delegate': - return this.keys[5].pubkey; - case 'Authorize': - return this.keys[2].pubkey; - case 'Split': - return this.keys[2].pubkey; - case 'Withdraw': - return this.keys[4].pubkey; - case 'Deactivate': - return this.keys[2].pubkey; - default: - return null; + static checkKeyLength(keys: Array, expectedLength: number) { + if (keys.length !== expectedLength) { + throw new Error( + `invalid instruction; key length mismatch ${keys.length} != ${expectedLength}`, + ); } } } @@ -234,11 +431,8 @@ export class StakeProgram { /** * Generate an Initialize instruction to add to a Stake Create transaction */ - static initialize( - stakePubkey: PublicKey, - authorized: Authorized, - lockup: Lockup, - ): TransactionInstruction { + static initialize(params: InitializeStakeParams): TransactionInstruction { + const {stakePubkey, authorized, lockup} = params; const type = STAKE_INSTRUCTION_LAYOUTS.Initialize; const data = encodeData(type, { authorized: { @@ -267,46 +461,36 @@ export class StakeProgram { * an address generated with `from`, a seed, and the Stake programId */ static createAccountWithSeed( - from: PublicKey, - stakePubkey: PublicKey, - base: PublicKey, - seed: string, - authorized: Authorized, - lockup: Lockup, - lamports: number, + params: CreateStakeAccountWithSeedParams, ): Transaction { let transaction = SystemProgram.createAccountWithSeed( - from, - stakePubkey, - base, - seed, - lamports, + params.fromPubkey, + params.stakePubkey, + params.basePubkey, + params.seed, + params.lamports, this.space, this.programId, ); - return transaction.add(this.initialize(stakePubkey, authorized, lockup)); + const {stakePubkey, authorized, lockup} = params; + return transaction.add(this.initialize({stakePubkey, authorized, lockup})); } /** * Generate a Transaction that creates a new Stake account */ - static createAccount( - from: PublicKey, - stakePubkey: PublicKey, - authorized: Authorized, - lockup: Lockup, - lamports: number, - ): Transaction { + static createAccount(params: CreateStakeAccountParams): Transaction { let transaction = SystemProgram.createAccount( - from, - stakePubkey, - lamports, + params.fromPubkey, + params.stakePubkey, + params.lamports, this.space, this.programId, ); - return transaction.add(this.initialize(stakePubkey, authorized, lockup)); + const {stakePubkey, authorized, lockup} = params; + return transaction.add(this.initialize({stakePubkey, authorized, lockup})); } /** @@ -314,11 +498,9 @@ export class StakeProgram { * Vote PublicKey. This transaction can also be used to redelegate Stake * to a new validator Vote PublicKey. */ - static delegate( - stakePubkey: PublicKey, - authorizedPubkey: PublicKey, - votePubkey: PublicKey, - ): Transaction { + static delegate(params: DelegateStakeParams): Transaction { + const {stakePubkey, authorizedPubkey, votePubkey} = params; + const type = STAKE_INSTRUCTION_LAYOUTS.Delegate; const data = encodeData(type); @@ -344,15 +526,17 @@ export class StakeProgram { * Generate a Transaction that authorizes a new PublicKey as Staker * or Withdrawer on the Stake account. */ - static authorize( - stakePubkey: PublicKey, - authorizedPubkey: PublicKey, - newAuthorized: PublicKey, - stakeAuthorizationType: StakeAuthorizationType, - ): Transaction { + static authorize(params: AuthorizeStakeParams): Transaction { + const { + stakePubkey, + authorizedPubkey, + newAuthorizedPubkey, + stakeAuthorizationType, + } = params; + const type = STAKE_INSTRUCTION_LAYOUTS.Authorize; const data = encodeData(type, { - newAuthorized: newAuthorized.toBuffer(), + newAuthorized: newAuthorizedPubkey.toBuffer(), stakeAuthorizationType: stakeAuthorizationType.index, }); @@ -370,12 +554,9 @@ export class StakeProgram { /** * Generate a Transaction that splits Stake tokens into another stake account */ - static split( - stakePubkey: PublicKey, - authorizedPubkey: PublicKey, - lamports: number, - splitStakePubkey: PublicKey, - ): Transaction { + static split(params: SplitStakeParams): Transaction { + const {stakePubkey, authorizedPubkey, splitStakePubkey, lamports} = params; + let transaction = SystemProgram.createAccount( stakePubkey, splitStakePubkey, @@ -401,19 +582,15 @@ export class StakeProgram { /** * Generate a Transaction that withdraws deactivated Stake tokens. */ - static withdraw( - stakePubkey: PublicKey, - authorizedPubkey: PublicKey, - to: PublicKey, - lamports: number, - ): Transaction { + static withdraw(params: WithdrawStakeParams): Transaction { + const {stakePubkey, authorizedPubkey, toPubkey, lamports} = params; const type = STAKE_INSTRUCTION_LAYOUTS.Withdraw; const data = encodeData(type, {lamports}); return new Transaction().add({ keys: [ {pubkey: stakePubkey, isSigner: false, isWritable: true}, - {pubkey: to, isSigner: false, isWritable: true}, + {pubkey: toPubkey, isSigner: false, isWritable: true}, {pubkey: SYSVAR_CLOCK_PUBKEY, isSigner: false, isWritable: false}, { pubkey: SYSVAR_STAKE_HISTORY_PUBKEY, @@ -430,10 +607,8 @@ export class StakeProgram { /** * Generate a Transaction that deactivates Stake tokens. */ - static deactivate( - stakePubkey: PublicKey, - authorizedPubkey: PublicKey, - ): Transaction { + static deactivate(params: DeactivateStakeParams): Transaction { + const {stakePubkey, authorizedPubkey} = params; const type = STAKE_INSTRUCTION_LAYOUTS.Deactivate; const data = encodeData(type); diff --git a/web3.js/test/stake-program.test.js b/web3.js/test/stake-program.test.js index 6a08fe65f3..1b74037ad6 100644 --- a/web3.js/test/stake-program.test.js +++ b/web3.js/test/stake-program.test.js @@ -24,164 +24,137 @@ if (!mockRpcEnabled) { } test('createAccountWithSeed', () => { - const from = new Account(); + const fromPubkey = new Account().publicKey; const seed = 'test string'; const newAccountPubkey = PublicKey.createWithSeed( - from.publicKey, + fromPubkey, seed, StakeProgram.programId, ); - const authorized = new Account(); - let transaction; - - transaction = StakeProgram.createAccountWithSeed( - from.publicKey, - newAccountPubkey, - from.publicKey, + const authorizedPubkey = new Account().publicKey; + const authorized = new Authorized(authorizedPubkey, authorizedPubkey); + const lockup = new Lockup(0, 0, fromPubkey); + const transaction = StakeProgram.createAccountWithSeed({ + fromPubkey, + stakePubkey: newAccountPubkey, + basePubkey: fromPubkey, seed, - new Authorized(authorized.publicKey, authorized.publicKey), - new Lockup(0, 0, from.publicKey), - 123, - ); + authorized, + lockup, + lamports: 123, + }); expect(transaction.instructions).toHaveLength(2); - expect(transaction.instructions[0].programId).toEqual( - SystemProgram.programId, - ); - expect(transaction.instructions[1].programId).toEqual(StakeProgram.programId); - const stakeInstruction = StakeInstruction.from(transaction.instructions[1]); - expect(stakeInstruction.stakePublicKey).toEqual(newAccountPubkey); - // TODO: Validate transaction contents more + const [systemInstruction, stakeInstruction] = transaction.instructions; + expect(systemInstruction.programId).toEqual(SystemProgram.programId); + + // TODO decode system instruction + + const params = StakeInstruction.decodeInitialize(stakeInstruction); + expect(params.stakePubkey).toEqual(newAccountPubkey); + expect(params.authorized).toEqual(authorized); + expect(params.lockup).toEqual(lockup); }); test('createAccount', () => { - const from = new Account(); - const newAccount = new Account(); - const authorized = new Account(); - let transaction; - - transaction = StakeProgram.createAccount( - from.publicKey, - newAccount.publicKey, - new Authorized(authorized.publicKey, authorized.publicKey), - new Lockup(0, 0, from.publicKey), - 123, - ); + const fromPubkey = new Account().publicKey; + const newAccountPubkey = new Account().publicKey; + const authorizedPubkey = new Account().publicKey; + const authorized = new Authorized(authorizedPubkey, authorizedPubkey); + const lockup = new Lockup(0, 0, fromPubkey); + const transaction = StakeProgram.createAccount({ + fromPubkey, + stakePubkey: newAccountPubkey, + authorized, + lockup, + lamports: 123, + }); expect(transaction.instructions).toHaveLength(2); - expect(transaction.instructions[0].programId).toEqual( - SystemProgram.programId, - ); - expect(transaction.instructions[1].programId).toEqual(StakeProgram.programId); - const stakeInstruction = StakeInstruction.from(transaction.instructions[1]); - expect(stakeInstruction.stakePublicKey).toEqual(newAccount.publicKey); + const [systemInstruction, stakeInstruction] = transaction.instructions; + expect(systemInstruction.programId).toEqual(SystemProgram.programId); - expect(() => { - StakeInstruction.from(transaction.instructions[0]); - }).toThrow(); - // TODO: Validate transaction contents more + // TODO decode system instruction + + const params = StakeInstruction.decodeInitialize(stakeInstruction); + expect(params.stakePubkey).toEqual(newAccountPubkey); + expect(params.authorized).toEqual(authorized); + expect(params.lockup).toEqual(lockup); }); test('delegate', () => { - const stake = new Account(); - const authorized = new Account(); - const vote = new Account(); - let transaction; - - transaction = StakeProgram.delegate( - stake.publicKey, - authorized.publicKey, - vote.publicKey, - ); - - expect(transaction.keys).toHaveLength(6); - expect(transaction.programId).toEqual(StakeProgram.programId); - const stakeInstruction = StakeInstruction.from(transaction.instructions[0]); - expect(stakeInstruction.stakePublicKey).toEqual(stake.publicKey); - expect(stakeInstruction.authorizedPublicKey).toEqual(authorized.publicKey); - // TODO: Validate transaction contents more + const stakePubkey = new Account().publicKey; + const authorizedPubkey = new Account().publicKey; + const votePubkey = new Account().publicKey; + const params = { + stakePubkey, + authorizedPubkey, + votePubkey, + }; + const transaction = StakeProgram.delegate(params); + expect(transaction.instructions).toHaveLength(1); + const [stakeInstruction] = transaction.instructions; + expect(params).toEqual(StakeInstruction.decodeDelegate(stakeInstruction)); }); test('authorize', () => { - const stake = new Account(); - const authorized = new Account(); - const newAuthorized = new Account(); - const type = StakeAuthorizationLayout.Staker; - let transaction; - - transaction = StakeProgram.authorize( - stake.publicKey, - authorized.publicKey, - newAuthorized.publicKey, - type, - ); - - expect(transaction.keys).toHaveLength(3); - expect(transaction.programId).toEqual(StakeProgram.programId); - const stakeInstruction = StakeInstruction.from(transaction.instructions[0]); - expect(stakeInstruction.stakePublicKey).toEqual(stake.publicKey); - expect(stakeInstruction.authorizedPublicKey).toEqual(authorized.publicKey); - // TODO: Validate transaction contents more + const stakePubkey = new Account().publicKey; + const authorizedPubkey = new Account().publicKey; + const newAuthorizedPubkey = new Account().publicKey; + const stakeAuthorizationType = StakeAuthorizationLayout.Staker; + const params = { + stakePubkey, + authorizedPubkey, + newAuthorizedPubkey, + stakeAuthorizationType, + }; + const transaction = StakeProgram.authorize(params); + expect(transaction.instructions).toHaveLength(1); + const [stakeInstruction] = transaction.instructions; + expect(params).toEqual(StakeInstruction.decodeAuthorize(stakeInstruction)); }); test('split', () => { - const stake = new Account(); - const authorized = new Account(); - const newStake = new Account(); - let transaction; - - transaction = StakeProgram.split( - stake.publicKey, - authorized.publicKey, - 123, - newStake.publicKey, - ); - + const stakePubkey = new Account().publicKey; + const authorizedPubkey = new Account().publicKey; + const splitStakePubkey = new Account().publicKey; + const params = { + stakePubkey, + authorizedPubkey, + splitStakePubkey, + lamports: 123, + }; + const transaction = StakeProgram.split(params); expect(transaction.instructions).toHaveLength(2); - expect(transaction.instructions[0].programId).toEqual( - SystemProgram.programId, - ); - expect(transaction.instructions[1].programId).toEqual(StakeProgram.programId); - const stakeInstruction = StakeInstruction.from(transaction.instructions[1]); - expect(stakeInstruction.stakePublicKey).toEqual(stake.publicKey); - expect(stakeInstruction.authorizedPublicKey).toEqual(authorized.publicKey); - // TODO: Validate transaction contents more + const [systemInstruction, stakeInstruction] = transaction.instructions; + expect(systemInstruction.programId).toEqual(SystemProgram.programId); + expect(params).toEqual(StakeInstruction.decodeSplit(stakeInstruction)); }); test('withdraw', () => { - const stake = new Account(); - const withdrawer = new Account(); - const to = new Account(); - let transaction; - - transaction = StakeProgram.withdraw( - stake.publicKey, - withdrawer.publicKey, - to.publicKey, - 123, - ); - - expect(transaction.keys).toHaveLength(5); - expect(transaction.programId).toEqual(StakeProgram.programId); - const stakeInstruction = StakeInstruction.from(transaction.instructions[0]); - expect(stakeInstruction.stakePublicKey).toEqual(stake.publicKey); - expect(stakeInstruction.authorizedPublicKey).toEqual(withdrawer.publicKey); - // TODO: Validate transaction contents more + const stakePubkey = new Account().publicKey; + const authorizedPubkey = new Account().publicKey; + const toPubkey = new Account().publicKey; + const params = { + stakePubkey, + authorizedPubkey, + toPubkey, + lamports: 123, + }; + const transaction = StakeProgram.withdraw(params); + expect(transaction.instructions).toHaveLength(1); + const [stakeInstruction] = transaction.instructions; + expect(params).toEqual(StakeInstruction.decodeWithdraw(stakeInstruction)); }); test('deactivate', () => { - const stake = new Account(); - const authorized = new Account(); - let transaction; - - transaction = StakeProgram.deactivate(stake.publicKey, authorized.publicKey); - - expect(transaction.keys).toHaveLength(3); - expect(transaction.programId).toEqual(StakeProgram.programId); - const stakeInstruction = StakeInstruction.from(transaction.instructions[0]); - expect(stakeInstruction.stakePublicKey).toEqual(stake.publicKey); - expect(stakeInstruction.authorizedPublicKey).toEqual(authorized.publicKey); - // TODO: Validate transaction contents more + const stakePubkey = new Account().publicKey; + const authorizedPubkey = new Account().publicKey; + const params = {stakePubkey, authorizedPubkey}; + const transaction = StakeProgram.deactivate(params); + expect(transaction.instructions).toHaveLength(1); + const [stakeInstruction] = transaction.instructions; + expect(params).toEqual(StakeInstruction.decodeDeactivate(stakeInstruction)); }); test('StakeInstructions', () => { @@ -195,19 +168,20 @@ test('StakeInstructions', () => { const authorized = new Account(); const amount = 123; const recentBlockhash = 'EETubP5AKHgjPAhzPAFcb8BAY1hMH639CWCFTqi3hq1k'; // Arbitrary known recentBlockhash - const createWithSeed = StakeProgram.createAccountWithSeed( - from.publicKey, - newAccountPubkey, - from.publicKey, + const createWithSeed = StakeProgram.createAccountWithSeed({ + fromPubkey: from.publicKey, + stakePubkey: newAccountPubkey, + basePubkey: from.publicKey, seed, - new Authorized(authorized.publicKey, authorized.publicKey), - new Lockup(0, 0, from.publicKey), - amount, - ); + authorized: new Authorized(authorized.publicKey, authorized.publicKey), + lockup: new Lockup(0, 0, from.publicKey), + lamports: amount, + }); const createWithSeedTransaction = new Transaction({recentBlockhash}).add( createWithSeed, ); + expect(createWithSeedTransaction.instructions).toHaveLength(2); const systemInstruction = SystemInstruction.from( createWithSeedTransaction.instructions[0], ); @@ -216,29 +190,30 @@ test('StakeInstructions', () => { expect(systemInstruction.amount).toEqual(amount); expect(systemInstruction.programId).toEqual(SystemProgram.programId); - const stakeInstruction = StakeInstruction.from( + const stakeInstructionType = StakeInstruction.decodeInstructionType( createWithSeedTransaction.instructions[1], ); - expect(stakeInstruction.type).toEqual('Initialize'); + expect(stakeInstructionType).toEqual('Initialize'); expect(() => { - StakeInstruction.from(createWithSeedTransaction.instructions[0]); + StakeInstruction.decodeInstructionType( + createWithSeedTransaction.instructions[0], + ); }).toThrow(); const stake = new Account(); const vote = new Account(); - const delegate = StakeProgram.delegate( - stake.publicKey, - authorized.publicKey, - vote.publicKey, - ); + const delegate = StakeProgram.delegate({ + stakePubkey: stake.publicKey, + authorizedPubkey: authorized.publicKey, + votePubkey: vote.publicKey, + }); const delegateTransaction = new Transaction({recentBlockhash}).add(delegate); - - const anotherStakeInstruction = StakeInstruction.from( + const anotherStakeInstructionType = StakeInstruction.decodeInstructionType( delegateTransaction.instructions[0], ); - expect(anotherStakeInstruction.type).toEqual('Delegate'); + expect(anotherStakeInstructionType).toEqual('Delegate'); }); test('live staking actions', async () => { @@ -270,15 +245,15 @@ test('live staking actions', async () => { StakeProgram.programId, ); - let createAndInitializeWithSeed = StakeProgram.createAccountWithSeed( - from.publicKey, - newAccountPubkey, - from.publicKey, + let createAndInitializeWithSeed = StakeProgram.createAccountWithSeed({ + fromPubkey: from.publicKey, + stakePubkey: newAccountPubkey, + basePubkey: from.publicKey, seed, - new Authorized(authorized.publicKey, authorized.publicKey), - new Lockup(0, 0, new PublicKey('0x00')), - 3 * minimumAmount + 42, - ); + authorized: new Authorized(authorized.publicKey, authorized.publicKey), + lockup: new Lockup(0, 0, new PublicKey('0x00')), + lamports: 3 * minimumAmount + 42, + }); await sendAndConfirmRecentTransaction( connection, @@ -288,33 +263,33 @@ test('live staking actions', async () => { let originalStakeBalance = await connection.getBalance(newAccountPubkey); expect(originalStakeBalance).toEqual(3 * minimumAmount + 42); - let delegation = StakeProgram.delegate( - newAccountPubkey, - authorized.publicKey, + let delegation = StakeProgram.delegate({ + stakePubkey: newAccountPubkey, + authorizedPubkey: authorized.publicKey, votePubkey, - ); + }); await sendAndConfirmRecentTransaction(connection, delegation, authorized); // Test that withdraw fails before deactivation const recipient = new Account(); - let withdraw = StakeProgram.withdraw( - newAccountPubkey, - authorized.publicKey, - recipient.publicKey, - 1000, - ); + let withdraw = StakeProgram.withdraw({ + stakePubkey: newAccountPubkey, + authorizedPubkey: authorized.publicKey, + toPubkey: recipient.publicKey, + lamports: 1000, + }); await expect( sendAndConfirmRecentTransaction(connection, withdraw, authorized), ).rejects.toThrow(); // Split stake const newStake = new Account(); - let split = StakeProgram.split( - newAccountPubkey, - authorized.publicKey, - minimumAmount + 20, - newStake.publicKey, - ); + let split = StakeProgram.split({ + stakePubkey: newAccountPubkey, + authorizedPubkey: authorized.publicKey, + splitStakePubkey: newStake.publicKey, + lamports: minimumAmount + 20, + }); await sendAndConfirmRecentTransaction( connection, split, @@ -326,26 +301,26 @@ test('live staking actions', async () => { const newAuthorized = new Account(); await connection.requestAirdrop(newAuthorized.publicKey, LAMPORTS_PER_SOL); - let authorize = StakeProgram.authorize( - newAccountPubkey, - authorized.publicKey, - newAuthorized.publicKey, - StakeAuthorizationLayout.Withdrawer, - ); + let authorize = StakeProgram.authorize({ + stakePubkey: newAccountPubkey, + authorizedPubkey: authorized.publicKey, + newAuthorizedPubkey: newAuthorized.publicKey, + stakeAuthorizationType: StakeAuthorizationLayout.Withdrawer, + }); await sendAndConfirmRecentTransaction(connection, authorize, authorized); - authorize = StakeProgram.authorize( - newAccountPubkey, - authorized.publicKey, - newAuthorized.publicKey, - StakeAuthorizationLayout.Staker, - ); + authorize = StakeProgram.authorize({ + stakePubkey: newAccountPubkey, + authorizedPubkey: authorized.publicKey, + newAuthorizedPubkey: newAuthorized.publicKey, + stakeAuthorizationType: StakeAuthorizationLayout.Staker, + }); await sendAndConfirmRecentTransaction(connection, authorize, authorized); // Test old authorized can't deactivate - let deactivateNotAuthorized = StakeProgram.deactivate( - newAccountPubkey, - authorized.publicKey, - ); + let deactivateNotAuthorized = StakeProgram.deactivate({ + stakePubkey: newAccountPubkey, + authorizedPubkey: authorized.publicKey, + }); await expect( sendAndConfirmRecentTransaction( connection, @@ -355,19 +330,19 @@ test('live staking actions', async () => { ).rejects.toThrow(); // Deactivate stake - let deactivate = StakeProgram.deactivate( - newAccountPubkey, - newAuthorized.publicKey, - ); + let deactivate = StakeProgram.deactivate({ + stakePubkey: newAccountPubkey, + authorizedPubkey: newAuthorized.publicKey, + }); await sendAndConfirmRecentTransaction(connection, deactivate, newAuthorized); // Test that withdraw succeeds after deactivation - withdraw = StakeProgram.withdraw( - newAccountPubkey, - newAuthorized.publicKey, - recipient.publicKey, - minimumAmount + 20, - ); + withdraw = StakeProgram.withdraw({ + stakePubkey: newAccountPubkey, + authorizedPubkey: newAuthorized.publicKey, + toPubkey: recipient.publicKey, + lamports: minimumAmount + 20, + }); await sendAndConfirmRecentTransaction(connection, withdraw, newAuthorized); const balance = await connection.getBalance(newAccountPubkey); expect(balance).toEqual(minimumAmount + 2); diff --git a/web3.js/test/transaction.test.js b/web3.js/test/transaction.test.js index 67195c185d..30b63eea25 100644 --- a/web3.js/test/transaction.test.js +++ b/web3.js/test/transaction.test.js @@ -112,11 +112,11 @@ test('use nonce', () => { const stakeAccount = new Account(); const voteAccount = new Account(); const stakeTransaction = new Transaction({nonceInfo}).add( - StakeProgram.delegate( - stakeAccount.publicKey, - account1.publicKey, - voteAccount.publicKey, - ), + StakeProgram.delegate({ + stakePubkey: stakeAccount.publicKey, + authorizedPubkey: account1.publicKey, + votePubkey: voteAccount.publicKey, + }), ); stakeTransaction.sign(account1);