feat: add API for decoding stake instructions

This commit is contained in:
Justin Starry
2020-03-02 23:58:10 +08:00
committed by Michael Vines
parent 01e65d2070
commit aba7e14f3a
6 changed files with 660 additions and 426 deletions

134
web3.js/module.d.ts vendored
View File

@ -369,52 +369,74 @@ declare module '@solana/web3.js' {
constructor(unixTimestamp: number, epoch: number, custodian: PublicKey); 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 { export class StakeProgram {
static programId: PublicKey; static programId: PublicKey;
static space: number; static space: number;
static createAccount(params: CreateStakeAccountParams): Transaction;
static createAccount(
from: PublicKey,
stakeAccount: PublicKey,
authorized: Authorized,
lockup: Lockup,
lamports: number,
): Transaction;
static createAccountWithSeed( static createAccountWithSeed(
from: PublicKey, params: CreateStakeAccountWithSeedParams,
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,
): Transaction; ): 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 = export type StakeInstructionType =
@ -429,16 +451,26 @@ declare module '@solana/web3.js' {
[type in StakeInstructionType]: InstructionType; [type in StakeInstructionType]: InstructionType;
}; };
export class StakeInstruction extends TransactionInstruction { export class StakeInstruction {
type: StakeInstructionType; static decodeInstructionType(
stakePublicKey: PublicKey | null; instruction: TransactionInstruction,
authorizedPublicKey: PublicKey | null; ): StakeInstructionType;
static decodeInitialize(
constructor( instruction: TransactionInstruction,
opts: TransactionInstructionCtorFields, ): InitializeStakeParams;
type: StakeInstructionType, static decodeDelegate(
); instruction: TransactionInstruction,
static from(instruction: TransactionInstruction): StakeInstruction; ): 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 === // === src/system-program.js ===

View File

@ -266,52 +266,74 @@ declare module '@solana/web3.js' {
): Lockup; ): Lockup;
} }
declare export class StakeProgram { declare export type CreateStakeAccountParams = {|
static programId: PublicKey; fromPubkey: PublicKey,
static space: number; stakePubkey: PublicKey,
static createAccount(
from: PublicKey,
stakeAccount: PublicKey,
authorized: Authorized, authorized: Authorized,
lockup: Lockup, lockup: Lockup,
lamports: number, lamports: number,
): Transaction; |};
static createAccountWithSeed(
from: PublicKey, declare export type CreateStakeAccountWithSeedParams = {|
stakeAccount: PublicKey, fromPubkey: PublicKey,
stakePubkey: PublicKey,
basePubkey: PublicKey,
seed: string, seed: string,
authorized: Authorized, authorized: Authorized,
lockup: Lockup, lockup: Lockup,
lamports: number, lamports: number,
): Transaction; |};
static delegate(
stakeAccount: PublicKey, declare export type InitializeStakeParams = {|
stakePubkey: PublicKey,
authorized: Authorized,
lockup: Lockup,
|};
declare export type DelegateStakeParams = {|
stakePubkey: PublicKey,
authorizedPubkey: PublicKey, authorizedPubkey: PublicKey,
votePubkey: PublicKey, votePubkey: PublicKey,
): Transaction; |};
static authorize(
stakeAccount: PublicKey, declare export type AuthorizeStakeParams = {|
stakePubkey: PublicKey,
authorizedPubkey: PublicKey, authorizedPubkey: PublicKey,
newAuthorized: PublicKey, newAuthorizedPubkey: PublicKey,
stakeAuthorizationType: StakeAuthorizationType, stakeAuthorizationType: StakeAuthorizationType,
): Transaction; |};
static split(
stakeAccount: PublicKey, declare export type SplitStakeParams = {|
stakePubkey: PublicKey,
authorizedPubkey: PublicKey, authorizedPubkey: PublicKey,
lamports: number,
splitStakePubkey: PublicKey, splitStakePubkey: PublicKey,
): Transaction;
static withdraw(
stakeAccount: PublicKey,
withdrawerPubkey: PublicKey,
to: PublicKey,
lamports: number, lamports: number,
): Transaction; |};
static deactivate(
stakeAccount: PublicKey, declare export type WithdrawStakeParams = {|
stakePubkey: PublicKey,
authorizedPubkey: 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(params: CreateStakeAccountParams): Transaction;
static createAccountWithSeed(
params: CreateStakeAccountWithSeedParams,
): Transaction; ): 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 = declare export type StakeInstructionType =
@ -326,16 +348,26 @@ declare module '@solana/web3.js' {
[StakeInstructionType]: InstructionType, [StakeInstructionType]: InstructionType,
}; };
declare export class StakeInstruction extends TransactionInstruction { declare export class StakeInstruction {
type: StakeInstructionType; static decodeInstructionType(
stakePublicKey: PublicKey | null; instruction: TransactionInstruction,
authorizedPublicKey: PublicKey | null; ): StakeInstructionType;
static decodeInitialize(
constructor( instruction: TransactionInstruction,
opts?: TransactionInstructionCtorFields, ): InitializeStakeParams;
type: StakeInstructionType, static decodeDelegate(
): StakeInstruction; instruction: TransactionInstruction,
static from(instruction: TransactionInstruction): StakeInstruction; ): 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 === // === src/system-program.js ===

View File

@ -25,3 +25,23 @@ export function encodeData(type: InstructionType, fields: Object): Buffer {
type.layout.encode(layoutFields, data); type.layout.encode(layoutFields, data);
return 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;
}

View File

@ -2,7 +2,7 @@
import * as BufferLayout from 'buffer-layout'; import * as BufferLayout from 'buffer-layout';
import {encodeData} from './instruction'; import {encodeData, decodeData} from './instruction';
import * as Layout from './layout'; import * as Layout from './layout';
import {PublicKey} from './publickey'; import {PublicKey} from './publickey';
import {SystemProgram} from './system-program'; import {SystemProgram} from './system-program';
@ -12,7 +12,6 @@ import {
SYSVAR_STAKE_HISTORY_PUBKEY, SYSVAR_STAKE_HISTORY_PUBKEY,
} from './sysvar'; } from './sysvar';
import {Transaction, TransactionInstruction} from './transaction'; import {Transaction, TransactionInstruction} from './transaction';
import type {TransactionInstructionCtorFields} from './transaction';
export const STAKE_CONFIG_ID = new PublicKey( export const STAKE_CONFIG_ID = new PublicKey(
'StakeConfig11111111111111111111111111111111', '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 * Stake Instruction class
*/ */
export class StakeInstruction extends TransactionInstruction { export class StakeInstruction {
_type: StakeInstructionType; /**
* Decode a stake instruction and retrieve the instruction type.
constructor( */
opts?: TransactionInstructionCtorFields, static decodeInstructionType(
type: StakeInstructionType, instruction: TransactionInstruction,
) { ): StakeInstructionType {
if ( this.checkProgramId(instruction.programId);
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');
}
const instructionTypeLayout = BufferLayout.u32('instruction'); const instructionTypeLayout = BufferLayout.u32('instruction');
const typeIndex = instructionTypeLayout.decode(instruction.data); const typeIndex = instructionTypeLayout.decode(instruction.data);
@ -81,63 +186,155 @@ export class StakeInstruction extends TransactionInstruction {
type = t; type = t;
} }
} }
if (!type) { if (!type) {
throw new Error('Instruction type incorrect; not a StakeInstruction'); throw new Error('Instruction type incorrect; not a StakeInstruction');
} }
return new StakeInstruction(
{ return type;
keys: instruction.keys, }
programId: instruction.programId,
data: instruction.data, /**
}, * Decode a initialize stake instruction and retrieve the instruction params.
type, */
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 { static decodeDelegate(
return this._type; 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; * Decode an authorize stake instruction and retrieve the instruction params.
* returns null if StakeInstructionType does not support this field
*/ */
get stakePublicKey(): PublicKey | null { static decodeAuthorize(
switch (this.type) { instruction: TransactionInstruction,
case 'Initialize': ): AuthorizeStakeParams {
case 'Delegate': this.checkProgramId(instruction.programId);
case 'Authorize': this.checkKeyLength(instruction.keys, 3);
case 'Split': const {newAuthorized, stakeAuthorizationType} = decodeData(
case 'Withdraw': STAKE_INSTRUCTION_LAYOUTS.Authorize,
case 'Deactivate': instruction.data,
return this.keys[0].pubkey; );
default:
return null; 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; * @private
*
* returns null if StakeInstructionType does not support this field
*/ */
get authorizedPublicKey(): PublicKey | null { static checkKeyLength(keys: Array<any>, expectedLength: number) {
switch (this.type) { if (keys.length !== expectedLength) {
case 'Delegate': throw new Error(
return this.keys[5].pubkey; `invalid instruction; key length mismatch ${keys.length} != ${expectedLength}`,
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;
} }
} }
} }
@ -234,11 +431,8 @@ export class StakeProgram {
/** /**
* Generate an Initialize instruction to add to a Stake Create transaction * Generate an Initialize instruction to add to a Stake Create transaction
*/ */
static initialize( static initialize(params: InitializeStakeParams): TransactionInstruction {
stakePubkey: PublicKey, const {stakePubkey, authorized, lockup} = params;
authorized: Authorized,
lockup: Lockup,
): TransactionInstruction {
const type = STAKE_INSTRUCTION_LAYOUTS.Initialize; const type = STAKE_INSTRUCTION_LAYOUTS.Initialize;
const data = encodeData(type, { const data = encodeData(type, {
authorized: { authorized: {
@ -267,46 +461,36 @@ export class StakeProgram {
* an address generated with `from`, a seed, and the Stake programId * an address generated with `from`, a seed, and the Stake programId
*/ */
static createAccountWithSeed( static createAccountWithSeed(
from: PublicKey, params: CreateStakeAccountWithSeedParams,
stakePubkey: PublicKey,
base: PublicKey,
seed: string,
authorized: Authorized,
lockup: Lockup,
lamports: number,
): Transaction { ): Transaction {
let transaction = SystemProgram.createAccountWithSeed( let transaction = SystemProgram.createAccountWithSeed(
from, params.fromPubkey,
stakePubkey, params.stakePubkey,
base, params.basePubkey,
seed, params.seed,
lamports, params.lamports,
this.space, this.space,
this.programId, 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 * Generate a Transaction that creates a new Stake account
*/ */
static createAccount( static createAccount(params: CreateStakeAccountParams): Transaction {
from: PublicKey,
stakePubkey: PublicKey,
authorized: Authorized,
lockup: Lockup,
lamports: number,
): Transaction {
let transaction = SystemProgram.createAccount( let transaction = SystemProgram.createAccount(
from, params.fromPubkey,
stakePubkey, params.stakePubkey,
lamports, params.lamports,
this.space, this.space,
this.programId, 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 * Vote PublicKey. This transaction can also be used to redelegate Stake
* to a new validator Vote PublicKey. * to a new validator Vote PublicKey.
*/ */
static delegate( static delegate(params: DelegateStakeParams): Transaction {
stakePubkey: PublicKey, const {stakePubkey, authorizedPubkey, votePubkey} = params;
authorizedPubkey: PublicKey,
votePubkey: PublicKey,
): Transaction {
const type = STAKE_INSTRUCTION_LAYOUTS.Delegate; const type = STAKE_INSTRUCTION_LAYOUTS.Delegate;
const data = encodeData(type); const data = encodeData(type);
@ -344,15 +526,17 @@ export class StakeProgram {
* Generate a Transaction that authorizes a new PublicKey as Staker * Generate a Transaction that authorizes a new PublicKey as Staker
* or Withdrawer on the Stake account. * or Withdrawer on the Stake account.
*/ */
static authorize( static authorize(params: AuthorizeStakeParams): Transaction {
stakePubkey: PublicKey, const {
authorizedPubkey: PublicKey, stakePubkey,
newAuthorized: PublicKey, authorizedPubkey,
stakeAuthorizationType: StakeAuthorizationType, newAuthorizedPubkey,
): Transaction { stakeAuthorizationType,
} = params;
const type = STAKE_INSTRUCTION_LAYOUTS.Authorize; const type = STAKE_INSTRUCTION_LAYOUTS.Authorize;
const data = encodeData(type, { const data = encodeData(type, {
newAuthorized: newAuthorized.toBuffer(), newAuthorized: newAuthorizedPubkey.toBuffer(),
stakeAuthorizationType: stakeAuthorizationType.index, stakeAuthorizationType: stakeAuthorizationType.index,
}); });
@ -370,12 +554,9 @@ export class StakeProgram {
/** /**
* Generate a Transaction that splits Stake tokens into another stake account * Generate a Transaction that splits Stake tokens into another stake account
*/ */
static split( static split(params: SplitStakeParams): Transaction {
stakePubkey: PublicKey, const {stakePubkey, authorizedPubkey, splitStakePubkey, lamports} = params;
authorizedPubkey: PublicKey,
lamports: number,
splitStakePubkey: PublicKey,
): Transaction {
let transaction = SystemProgram.createAccount( let transaction = SystemProgram.createAccount(
stakePubkey, stakePubkey,
splitStakePubkey, splitStakePubkey,
@ -401,19 +582,15 @@ export class StakeProgram {
/** /**
* Generate a Transaction that withdraws deactivated Stake tokens. * Generate a Transaction that withdraws deactivated Stake tokens.
*/ */
static withdraw( static withdraw(params: WithdrawStakeParams): Transaction {
stakePubkey: PublicKey, const {stakePubkey, authorizedPubkey, toPubkey, lamports} = params;
authorizedPubkey: PublicKey,
to: PublicKey,
lamports: number,
): Transaction {
const type = STAKE_INSTRUCTION_LAYOUTS.Withdraw; const type = STAKE_INSTRUCTION_LAYOUTS.Withdraw;
const data = encodeData(type, {lamports}); const data = encodeData(type, {lamports});
return new Transaction().add({ return new Transaction().add({
keys: [ keys: [
{pubkey: stakePubkey, isSigner: false, isWritable: true}, {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_CLOCK_PUBKEY, isSigner: false, isWritable: false},
{ {
pubkey: SYSVAR_STAKE_HISTORY_PUBKEY, pubkey: SYSVAR_STAKE_HISTORY_PUBKEY,
@ -430,10 +607,8 @@ export class StakeProgram {
/** /**
* Generate a Transaction that deactivates Stake tokens. * Generate a Transaction that deactivates Stake tokens.
*/ */
static deactivate( static deactivate(params: DeactivateStakeParams): Transaction {
stakePubkey: PublicKey, const {stakePubkey, authorizedPubkey} = params;
authorizedPubkey: PublicKey,
): Transaction {
const type = STAKE_INSTRUCTION_LAYOUTS.Deactivate; const type = STAKE_INSTRUCTION_LAYOUTS.Deactivate;
const data = encodeData(type); const data = encodeData(type);

View File

@ -24,164 +24,137 @@ if (!mockRpcEnabled) {
} }
test('createAccountWithSeed', () => { test('createAccountWithSeed', () => {
const from = new Account(); const fromPubkey = new Account().publicKey;
const seed = 'test string'; const seed = 'test string';
const newAccountPubkey = PublicKey.createWithSeed( const newAccountPubkey = PublicKey.createWithSeed(
from.publicKey, fromPubkey,
seed, seed,
StakeProgram.programId, StakeProgram.programId,
); );
const authorized = new Account(); const authorizedPubkey = new Account().publicKey;
let transaction; const authorized = new Authorized(authorizedPubkey, authorizedPubkey);
const lockup = new Lockup(0, 0, fromPubkey);
transaction = StakeProgram.createAccountWithSeed( const transaction = StakeProgram.createAccountWithSeed({
from.publicKey, fromPubkey,
newAccountPubkey, stakePubkey: newAccountPubkey,
from.publicKey, basePubkey: fromPubkey,
seed, seed,
new Authorized(authorized.publicKey, authorized.publicKey), authorized,
new Lockup(0, 0, from.publicKey), lockup,
123, lamports: 123,
); });
expect(transaction.instructions).toHaveLength(2); expect(transaction.instructions).toHaveLength(2);
expect(transaction.instructions[0].programId).toEqual( const [systemInstruction, stakeInstruction] = transaction.instructions;
SystemProgram.programId, expect(systemInstruction.programId).toEqual(SystemProgram.programId);
);
expect(transaction.instructions[1].programId).toEqual(StakeProgram.programId); // TODO decode system instruction
const stakeInstruction = StakeInstruction.from(transaction.instructions[1]);
expect(stakeInstruction.stakePublicKey).toEqual(newAccountPubkey); const params = StakeInstruction.decodeInitialize(stakeInstruction);
// TODO: Validate transaction contents more expect(params.stakePubkey).toEqual(newAccountPubkey);
expect(params.authorized).toEqual(authorized);
expect(params.lockup).toEqual(lockup);
}); });
test('createAccount', () => { test('createAccount', () => {
const from = new Account(); const fromPubkey = new Account().publicKey;
const newAccount = new Account(); const newAccountPubkey = new Account().publicKey;
const authorized = new Account(); const authorizedPubkey = new Account().publicKey;
let transaction; const authorized = new Authorized(authorizedPubkey, authorizedPubkey);
const lockup = new Lockup(0, 0, fromPubkey);
transaction = StakeProgram.createAccount( const transaction = StakeProgram.createAccount({
from.publicKey, fromPubkey,
newAccount.publicKey, stakePubkey: newAccountPubkey,
new Authorized(authorized.publicKey, authorized.publicKey), authorized,
new Lockup(0, 0, from.publicKey), lockup,
123, lamports: 123,
); });
expect(transaction.instructions).toHaveLength(2); expect(transaction.instructions).toHaveLength(2);
expect(transaction.instructions[0].programId).toEqual( const [systemInstruction, stakeInstruction] = transaction.instructions;
SystemProgram.programId, expect(systemInstruction.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);
expect(() => { // TODO decode system instruction
StakeInstruction.from(transaction.instructions[0]);
}).toThrow(); const params = StakeInstruction.decodeInitialize(stakeInstruction);
// TODO: Validate transaction contents more expect(params.stakePubkey).toEqual(newAccountPubkey);
expect(params.authorized).toEqual(authorized);
expect(params.lockup).toEqual(lockup);
}); });
test('delegate', () => { test('delegate', () => {
const stake = new Account(); const stakePubkey = new Account().publicKey;
const authorized = new Account(); const authorizedPubkey = new Account().publicKey;
const vote = new Account(); const votePubkey = new Account().publicKey;
let transaction; const params = {
stakePubkey,
transaction = StakeProgram.delegate( authorizedPubkey,
stake.publicKey, votePubkey,
authorized.publicKey, };
vote.publicKey, const transaction = StakeProgram.delegate(params);
); expect(transaction.instructions).toHaveLength(1);
const [stakeInstruction] = transaction.instructions;
expect(transaction.keys).toHaveLength(6); expect(params).toEqual(StakeInstruction.decodeDelegate(stakeInstruction));
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
}); });
test('authorize', () => { test('authorize', () => {
const stake = new Account(); const stakePubkey = new Account().publicKey;
const authorized = new Account(); const authorizedPubkey = new Account().publicKey;
const newAuthorized = new Account(); const newAuthorizedPubkey = new Account().publicKey;
const type = StakeAuthorizationLayout.Staker; const stakeAuthorizationType = StakeAuthorizationLayout.Staker;
let transaction; const params = {
stakePubkey,
transaction = StakeProgram.authorize( authorizedPubkey,
stake.publicKey, newAuthorizedPubkey,
authorized.publicKey, stakeAuthorizationType,
newAuthorized.publicKey, };
type, const transaction = StakeProgram.authorize(params);
); expect(transaction.instructions).toHaveLength(1);
const [stakeInstruction] = transaction.instructions;
expect(transaction.keys).toHaveLength(3); expect(params).toEqual(StakeInstruction.decodeAuthorize(stakeInstruction));
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
}); });
test('split', () => { test('split', () => {
const stake = new Account(); const stakePubkey = new Account().publicKey;
const authorized = new Account(); const authorizedPubkey = new Account().publicKey;
const newStake = new Account(); const splitStakePubkey = new Account().publicKey;
let transaction; const params = {
stakePubkey,
transaction = StakeProgram.split( authorizedPubkey,
stake.publicKey, splitStakePubkey,
authorized.publicKey, lamports: 123,
123, };
newStake.publicKey, const transaction = StakeProgram.split(params);
);
expect(transaction.instructions).toHaveLength(2); expect(transaction.instructions).toHaveLength(2);
expect(transaction.instructions[0].programId).toEqual( const [systemInstruction, stakeInstruction] = transaction.instructions;
SystemProgram.programId, expect(systemInstruction.programId).toEqual(SystemProgram.programId);
); expect(params).toEqual(StakeInstruction.decodeSplit(stakeInstruction));
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
}); });
test('withdraw', () => { test('withdraw', () => {
const stake = new Account(); const stakePubkey = new Account().publicKey;
const withdrawer = new Account(); const authorizedPubkey = new Account().publicKey;
const to = new Account(); const toPubkey = new Account().publicKey;
let transaction; const params = {
stakePubkey,
transaction = StakeProgram.withdraw( authorizedPubkey,
stake.publicKey, toPubkey,
withdrawer.publicKey, lamports: 123,
to.publicKey, };
123, const transaction = StakeProgram.withdraw(params);
); expect(transaction.instructions).toHaveLength(1);
const [stakeInstruction] = transaction.instructions;
expect(transaction.keys).toHaveLength(5); expect(params).toEqual(StakeInstruction.decodeWithdraw(stakeInstruction));
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
}); });
test('deactivate', () => { test('deactivate', () => {
const stake = new Account(); const stakePubkey = new Account().publicKey;
const authorized = new Account(); const authorizedPubkey = new Account().publicKey;
let transaction; const params = {stakePubkey, authorizedPubkey};
const transaction = StakeProgram.deactivate(params);
transaction = StakeProgram.deactivate(stake.publicKey, authorized.publicKey); expect(transaction.instructions).toHaveLength(1);
const [stakeInstruction] = transaction.instructions;
expect(transaction.keys).toHaveLength(3); expect(params).toEqual(StakeInstruction.decodeDeactivate(stakeInstruction));
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
}); });
test('StakeInstructions', () => { test('StakeInstructions', () => {
@ -195,19 +168,20 @@ test('StakeInstructions', () => {
const authorized = new Account(); const authorized = new Account();
const amount = 123; const amount = 123;
const recentBlockhash = 'EETubP5AKHgjPAhzPAFcb8BAY1hMH639CWCFTqi3hq1k'; // Arbitrary known recentBlockhash const recentBlockhash = 'EETubP5AKHgjPAhzPAFcb8BAY1hMH639CWCFTqi3hq1k'; // Arbitrary known recentBlockhash
const createWithSeed = StakeProgram.createAccountWithSeed( const createWithSeed = StakeProgram.createAccountWithSeed({
from.publicKey, fromPubkey: from.publicKey,
newAccountPubkey, stakePubkey: newAccountPubkey,
from.publicKey, basePubkey: from.publicKey,
seed, seed,
new Authorized(authorized.publicKey, authorized.publicKey), authorized: new Authorized(authorized.publicKey, authorized.publicKey),
new Lockup(0, 0, from.publicKey), lockup: new Lockup(0, 0, from.publicKey),
amount, lamports: amount,
); });
const createWithSeedTransaction = new Transaction({recentBlockhash}).add( const createWithSeedTransaction = new Transaction({recentBlockhash}).add(
createWithSeed, createWithSeed,
); );
expect(createWithSeedTransaction.instructions).toHaveLength(2);
const systemInstruction = SystemInstruction.from( const systemInstruction = SystemInstruction.from(
createWithSeedTransaction.instructions[0], createWithSeedTransaction.instructions[0],
); );
@ -216,29 +190,30 @@ test('StakeInstructions', () => {
expect(systemInstruction.amount).toEqual(amount); expect(systemInstruction.amount).toEqual(amount);
expect(systemInstruction.programId).toEqual(SystemProgram.programId); expect(systemInstruction.programId).toEqual(SystemProgram.programId);
const stakeInstruction = StakeInstruction.from( const stakeInstructionType = StakeInstruction.decodeInstructionType(
createWithSeedTransaction.instructions[1], createWithSeedTransaction.instructions[1],
); );
expect(stakeInstruction.type).toEqual('Initialize'); expect(stakeInstructionType).toEqual('Initialize');
expect(() => { expect(() => {
StakeInstruction.from(createWithSeedTransaction.instructions[0]); StakeInstruction.decodeInstructionType(
createWithSeedTransaction.instructions[0],
);
}).toThrow(); }).toThrow();
const stake = new Account(); const stake = new Account();
const vote = new Account(); const vote = new Account();
const delegate = StakeProgram.delegate( const delegate = StakeProgram.delegate({
stake.publicKey, stakePubkey: stake.publicKey,
authorized.publicKey, authorizedPubkey: authorized.publicKey,
vote.publicKey, votePubkey: vote.publicKey,
); });
const delegateTransaction = new Transaction({recentBlockhash}).add(delegate); const delegateTransaction = new Transaction({recentBlockhash}).add(delegate);
const anotherStakeInstructionType = StakeInstruction.decodeInstructionType(
const anotherStakeInstruction = StakeInstruction.from(
delegateTransaction.instructions[0], delegateTransaction.instructions[0],
); );
expect(anotherStakeInstruction.type).toEqual('Delegate'); expect(anotherStakeInstructionType).toEqual('Delegate');
}); });
test('live staking actions', async () => { test('live staking actions', async () => {
@ -270,15 +245,15 @@ test('live staking actions', async () => {
StakeProgram.programId, StakeProgram.programId,
); );
let createAndInitializeWithSeed = StakeProgram.createAccountWithSeed( let createAndInitializeWithSeed = StakeProgram.createAccountWithSeed({
from.publicKey, fromPubkey: from.publicKey,
newAccountPubkey, stakePubkey: newAccountPubkey,
from.publicKey, basePubkey: from.publicKey,
seed, seed,
new Authorized(authorized.publicKey, authorized.publicKey), authorized: new Authorized(authorized.publicKey, authorized.publicKey),
new Lockup(0, 0, new PublicKey('0x00')), lockup: new Lockup(0, 0, new PublicKey('0x00')),
3 * minimumAmount + 42, lamports: 3 * minimumAmount + 42,
); });
await sendAndConfirmRecentTransaction( await sendAndConfirmRecentTransaction(
connection, connection,
@ -288,33 +263,33 @@ test('live staking actions', async () => {
let originalStakeBalance = await connection.getBalance(newAccountPubkey); let originalStakeBalance = await connection.getBalance(newAccountPubkey);
expect(originalStakeBalance).toEqual(3 * minimumAmount + 42); expect(originalStakeBalance).toEqual(3 * minimumAmount + 42);
let delegation = StakeProgram.delegate( let delegation = StakeProgram.delegate({
newAccountPubkey, stakePubkey: newAccountPubkey,
authorized.publicKey, authorizedPubkey: authorized.publicKey,
votePubkey, votePubkey,
); });
await sendAndConfirmRecentTransaction(connection, delegation, authorized); await sendAndConfirmRecentTransaction(connection, delegation, authorized);
// Test that withdraw fails before deactivation // Test that withdraw fails before deactivation
const recipient = new Account(); const recipient = new Account();
let withdraw = StakeProgram.withdraw( let withdraw = StakeProgram.withdraw({
newAccountPubkey, stakePubkey: newAccountPubkey,
authorized.publicKey, authorizedPubkey: authorized.publicKey,
recipient.publicKey, toPubkey: recipient.publicKey,
1000, lamports: 1000,
); });
await expect( await expect(
sendAndConfirmRecentTransaction(connection, withdraw, authorized), sendAndConfirmRecentTransaction(connection, withdraw, authorized),
).rejects.toThrow(); ).rejects.toThrow();
// Split stake // Split stake
const newStake = new Account(); const newStake = new Account();
let split = StakeProgram.split( let split = StakeProgram.split({
newAccountPubkey, stakePubkey: newAccountPubkey,
authorized.publicKey, authorizedPubkey: authorized.publicKey,
minimumAmount + 20, splitStakePubkey: newStake.publicKey,
newStake.publicKey, lamports: minimumAmount + 20,
); });
await sendAndConfirmRecentTransaction( await sendAndConfirmRecentTransaction(
connection, connection,
split, split,
@ -326,26 +301,26 @@ test('live staking actions', async () => {
const newAuthorized = new Account(); const newAuthorized = new Account();
await connection.requestAirdrop(newAuthorized.publicKey, LAMPORTS_PER_SOL); await connection.requestAirdrop(newAuthorized.publicKey, LAMPORTS_PER_SOL);
let authorize = StakeProgram.authorize( let authorize = StakeProgram.authorize({
newAccountPubkey, stakePubkey: newAccountPubkey,
authorized.publicKey, authorizedPubkey: authorized.publicKey,
newAuthorized.publicKey, newAuthorizedPubkey: newAuthorized.publicKey,
StakeAuthorizationLayout.Withdrawer, stakeAuthorizationType: StakeAuthorizationLayout.Withdrawer,
); });
await sendAndConfirmRecentTransaction(connection, authorize, authorized); await sendAndConfirmRecentTransaction(connection, authorize, authorized);
authorize = StakeProgram.authorize( authorize = StakeProgram.authorize({
newAccountPubkey, stakePubkey: newAccountPubkey,
authorized.publicKey, authorizedPubkey: authorized.publicKey,
newAuthorized.publicKey, newAuthorizedPubkey: newAuthorized.publicKey,
StakeAuthorizationLayout.Staker, stakeAuthorizationType: StakeAuthorizationLayout.Staker,
); });
await sendAndConfirmRecentTransaction(connection, authorize, authorized); await sendAndConfirmRecentTransaction(connection, authorize, authorized);
// Test old authorized can't deactivate // Test old authorized can't deactivate
let deactivateNotAuthorized = StakeProgram.deactivate( let deactivateNotAuthorized = StakeProgram.deactivate({
newAccountPubkey, stakePubkey: newAccountPubkey,
authorized.publicKey, authorizedPubkey: authorized.publicKey,
); });
await expect( await expect(
sendAndConfirmRecentTransaction( sendAndConfirmRecentTransaction(
connection, connection,
@ -355,19 +330,19 @@ test('live staking actions', async () => {
).rejects.toThrow(); ).rejects.toThrow();
// Deactivate stake // Deactivate stake
let deactivate = StakeProgram.deactivate( let deactivate = StakeProgram.deactivate({
newAccountPubkey, stakePubkey: newAccountPubkey,
newAuthorized.publicKey, authorizedPubkey: newAuthorized.publicKey,
); });
await sendAndConfirmRecentTransaction(connection, deactivate, newAuthorized); await sendAndConfirmRecentTransaction(connection, deactivate, newAuthorized);
// Test that withdraw succeeds after deactivation // Test that withdraw succeeds after deactivation
withdraw = StakeProgram.withdraw( withdraw = StakeProgram.withdraw({
newAccountPubkey, stakePubkey: newAccountPubkey,
newAuthorized.publicKey, authorizedPubkey: newAuthorized.publicKey,
recipient.publicKey, toPubkey: recipient.publicKey,
minimumAmount + 20, lamports: minimumAmount + 20,
); });
await sendAndConfirmRecentTransaction(connection, withdraw, newAuthorized); await sendAndConfirmRecentTransaction(connection, withdraw, newAuthorized);
const balance = await connection.getBalance(newAccountPubkey); const balance = await connection.getBalance(newAccountPubkey);
expect(balance).toEqual(minimumAmount + 2); expect(balance).toEqual(minimumAmount + 2);

View File

@ -112,11 +112,11 @@ test('use nonce', () => {
const stakeAccount = new Account(); const stakeAccount = new Account();
const voteAccount = new Account(); const voteAccount = new Account();
const stakeTransaction = new Transaction({nonceInfo}).add( const stakeTransaction = new Transaction({nonceInfo}).add(
StakeProgram.delegate( StakeProgram.delegate({
stakeAccount.publicKey, stakePubkey: stakeAccount.publicKey,
account1.publicKey, authorizedPubkey: account1.publicKey,
voteAccount.publicKey, votePubkey: voteAccount.publicKey,
), }),
); );
stakeTransaction.sign(account1); stakeTransaction.sign(account1);