solana/web3.js/src/stake-program.js

452 lines
12 KiB
JavaScript
Raw Normal View History

// @flow
import * as BufferLayout from 'buffer-layout';
import hasha from 'hasha';
import {encodeData} from './instruction';
import type {InstructionType} from './instruction';
import * as Layout from './layout';
import {PublicKey} from './publickey';
import {SystemProgram} from './system-program';
import {
SYSVAR_CLOCK_PUBKEY,
SYSVAR_RENT_PUBKEY,
SYSVAR_REWARDS_PUBKEY,
SYSVAR_STAKE_HISTORY_PUBKEY,
} from './sysvar';
import {Transaction, TransactionInstruction} from './transaction';
import type {TransactionInstructionCtorFields} from './transaction';
export const STAKE_CONFIG_ID = new PublicKey(
'StakeConfig11111111111111111111111111111111',
);
export class Authorized {
staker: PublicKey;
withdrawer: PublicKey;
/**
* Create a new Authorized object
*/
constructor(staker: PublicKey, withdrawer: PublicKey) {
this.staker = staker;
this.withdrawer = withdrawer;
}
}
export class Lockup {
unixTimestamp: number;
epoch: number;
custodian: PublicKey;
/**
* Create a new Lockup object
*/
constructor(unixTimestamp: number, epoch: number, custodian: PublicKey) {
this.unixTimestamp = unixTimestamp;
this.epoch = epoch;
this.custodian = custodian;
}
}
/**
* Stake Instruction class
*/
export class StakeInstruction extends TransactionInstruction {
/**
* Type of StakeInstruction
*/
type: InstructionType;
constructor(opts?: TransactionInstructionCtorFields, type?: InstructionType) {
if (
opts &&
opts.programId &&
!opts.programId.equals(StakeProgram.programId)
) {
throw new Error('programId incorrect; not a StakeInstruction');
}
super(opts);
if (type) {
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 typeIndex = instructionTypeLayout.decode(instruction.data);
let type;
for (const t in StakeInstructionLayout) {
if (StakeInstructionLayout[t].index == typeIndex) {
type = StakeInstructionLayout[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,
);
}
}
/**
* An enumeration of valid StakeInstructionTypes
*/
2019-12-23 15:50:27 -08:00
export const StakeInstructionLayout = Object.freeze({
Initialize: {
index: 0,
layout: BufferLayout.struct([
BufferLayout.u32('instruction'),
Layout.authorized(),
Layout.lockup(),
]),
},
Authorize: {
index: 1,
layout: BufferLayout.struct([
BufferLayout.u32('instruction'),
Layout.publicKey('newAuthorized'),
BufferLayout.u32('stakeAuthorizationType'),
]),
},
DelegateStake: {
index: 2,
layout: BufferLayout.struct([BufferLayout.u32('instruction')]),
},
RedeemVoteCredits: {
index: 3,
layout: BufferLayout.struct([BufferLayout.u32('instruction')]),
},
Split: {
index: 4,
layout: BufferLayout.struct([
BufferLayout.u32('instruction'),
BufferLayout.ns64('lamports'),
]),
},
Withdraw: {
index: 5,
layout: BufferLayout.struct([
BufferLayout.u32('instruction'),
BufferLayout.ns64('lamports'),
]),
},
Deactivate: {
index: 6,
layout: BufferLayout.struct([BufferLayout.u32('instruction')]),
},
});
/**
* @typedef {Object} StakeAuthorizationType
* @property (index} The Stake Authorization index (from solana-stake-program)
*/
export type StakeAuthorizationType = {|
index: number,
|};
/**
* An enumeration of valid StakeInstructionTypes
*/
export const StakeAuthorizationLayout = Object.freeze({
Staker: {
index: 0,
},
Withdrawer: {
index: 1,
},
});
class RewardsPoolPublicKey extends PublicKey {
static get rewardsPoolBaseId(): PublicKey {
return new PublicKey('StakeRewards1111111111111111111111111111111');
}
/**
* Generate Derive a public key from another key, a seed, and a programId.
*/
static randomId(): PublicKey {
const randomInt = Math.floor(Math.random() * (256 - 1));
let pubkey = this.rewardsPoolBaseId;
for (let i = 0; i < randomInt; i++) {
const buffer = pubkey.toBuffer();
const hash = hasha(buffer, {algorithm: 'sha256'});
return new PublicKey('0x' + hash);
}
return pubkey;
}
}
/**
* Factory class for transactions to interact with the Stake program
*/
export class StakeProgram {
/**
* Public key that identifies the Stake program
*/
static get programId(): PublicKey {
return new PublicKey('Stake11111111111111111111111111111111111111');
}
/**
* Max space of a Stake account
*/
static get space(): number {
return 2008;
}
/**
* Generate an Initialize instruction to add to a Stake Create transaction
*/
static initialize(
stakeAccount: PublicKey,
authorized: Authorized,
lockup: Lockup,
): TransactionInstruction {
const type = StakeInstructionLayout.Initialize;
const data = encodeData(type, {
2019-12-23 15:50:27 -08:00
authorized: {
staker: authorized.staker.toBuffer(),
withdrawer: authorized.withdrawer.toBuffer(),
},
lockup: {
unixTimestamp: lockup.unixTimestamp,
2019-12-23 15:50:27 -08:00
epoch: lockup.epoch,
custodian: lockup.custodian.toBuffer(),
},
});
const instructionData = {
keys: [
{pubkey: stakeAccount, isSigner: false, isWritable: true},
{pubkey: SYSVAR_RENT_PUBKEY, isSigner: false, isWritable: false},
],
programId: this.programId,
data,
};
return new TransactionInstruction(instructionData);
}
/**
* Generate a Transaction that creates a new Stake account at
* an address generated with `from`, a seed, and the Stake programId
*/
static createAccountWithSeed(
from: PublicKey,
stakeAccount: PublicKey,
base: PublicKey,
seed: string,
authorized: Authorized,
lockup: Lockup,
lamports: number,
): Transaction {
let transaction = SystemProgram.createAccountWithSeed(
from,
stakeAccount,
base,
seed,
lamports,
this.space,
this.programId,
);
return transaction.add(this.initialize(stakeAccount, authorized, lockup));
}
/**
* Generate a Transaction that creates a new Stake account
*/
static createAccount(
from: PublicKey,
stakeAccount: PublicKey,
authorized: Authorized,
lockup: Lockup,
lamports: number,
): Transaction {
let transaction = SystemProgram.createAccount(
from,
stakeAccount,
lamports,
this.space,
this.programId,
);
return transaction.add(this.initialize(stakeAccount, authorized, lockup));
}
/**
* Generate a Transaction that delegates Stake tokens to a validator
* Vote PublicKey. This transaction can also be used to redelegate Stake
* to a new validator Vote PublicKey.
*/
static delegate(
stakeAccount: PublicKey,
authorizedPubkey: PublicKey,
votePubkey: PublicKey,
): Transaction {
const type = StakeInstructionLayout.DelegateStake;
const data = encodeData(type);
return new Transaction().add({
keys: [
{pubkey: stakeAccount, isSigner: false, isWritable: true},
{pubkey: votePubkey, isSigner: false, isWritable: false},
{pubkey: SYSVAR_CLOCK_PUBKEY, isSigner: false, isWritable: false},
{pubkey: STAKE_CONFIG_ID, isSigner: false, isWritable: false},
{pubkey: authorizedPubkey, isSigner: true, isWritable: false},
],
programId: this.programId,
data,
});
}
/**
* Generate a Transaction that authorizes a new PublicKey as Staker
* or Withdrawer on the Stake account.
*/
static authorize(
stakeAccount: PublicKey,
authorizedPubkey: PublicKey,
newAuthorized: PublicKey,
stakeAuthorizationType: StakeAuthorizationType,
): Transaction {
const type = StakeInstructionLayout.Authorize;
const data = encodeData(type, {
2019-12-23 15:50:27 -08:00
newAuthorized: newAuthorized.toBuffer(),
stakeAuthorizationType: stakeAuthorizationType.index,
});
return new Transaction().add({
keys: [
{pubkey: stakeAccount, isSigner: false, isWritable: true},
2020-01-02 08:56:15 -08:00
{pubkey: SYSVAR_CLOCK_PUBKEY, isSigner: false, isWritable: true},
{pubkey: authorizedPubkey, isSigner: true, isWritable: false},
],
programId: this.programId,
data,
});
}
/**
* Generate a Transaction that authorizes a new PublicKey as Staker
* or Withdrawer on the Stake account.
*/
static redeemVoteCredits(
stakeAccount: PublicKey,
votePubkey: PublicKey,
): Transaction {
const type = StakeInstructionLayout.RedeemVoteCredits;
const data = encodeData(type);
return new Transaction().add({
keys: [
{pubkey: stakeAccount, isSigner: false, isWritable: true},
{pubkey: votePubkey, isSigner: false, isWritable: true},
{
pubkey: RewardsPoolPublicKey.randomId(),
isSigner: false,
isWritable: true,
},
{pubkey: SYSVAR_REWARDS_PUBKEY, isSigner: false, isWritable: false},
{
pubkey: SYSVAR_STAKE_HISTORY_PUBKEY,
isSigner: false,
isWritable: false,
},
],
programId: this.programId,
data,
});
}
/**
* Generate a Transaction that splits Stake tokens into another stake account
*/
static split(
stakeAccount: PublicKey,
authorizedPubkey: PublicKey,
lamports: number,
splitStakePubkey: PublicKey,
): Transaction {
let transaction = SystemProgram.createAccount(
stakeAccount,
splitStakePubkey,
0,
this.space,
this.programId,
);
2019-12-27 11:13:45 -08:00
transaction.instructions[0].keys[0].isSigner = false;
const type = StakeInstructionLayout.Split;
const data = encodeData(type, {lamports});
return transaction.add({
keys: [
{pubkey: stakeAccount, isSigner: false, isWritable: true},
{pubkey: splitStakePubkey, isSigner: false, isWritable: true},
{pubkey: authorizedPubkey, isSigner: true, isWritable: false},
],
programId: this.programId,
data,
});
}
/**
* Generate a Transaction that withdraws deactivated Stake tokens.
*/
static withdraw(
stakeAccount: PublicKey,
withdrawerPubkey: PublicKey,
to: PublicKey,
lamports: number,
): Transaction {
const type = StakeInstructionLayout.Withdraw;
const data = encodeData(type, {lamports});
return new Transaction().add({
keys: [
{pubkey: stakeAccount, isSigner: false, isWritable: true},
{pubkey: to, isSigner: false, isWritable: true},
{pubkey: SYSVAR_CLOCK_PUBKEY, isSigner: false, isWritable: false},
{
pubkey: SYSVAR_STAKE_HISTORY_PUBKEY,
isSigner: false,
isWritable: false,
},
{pubkey: withdrawerPubkey, isSigner: true, isWritable: false},
],
programId: this.programId,
data,
});
}
/**
* Generate a Transaction that deactivates Stake tokens.
*/
static deactivate(
stakeAccount: PublicKey,
authorizedPubkey: PublicKey,
): Transaction {
const type = StakeInstructionLayout.Deactivate;
const data = encodeData(type);
return new Transaction().add({
keys: [
{pubkey: stakeAccount, isSigner: false, isWritable: true},
{pubkey: SYSVAR_CLOCK_PUBKEY, isSigner: false, isWritable: false},
{pubkey: authorizedPubkey, isSigner: true, isWritable: false},
],
programId: this.programId,
data,
});
}
}