feat: add API for decoding system instructions

This commit is contained in:
Justin Starry
2020-03-03 16:05:50 +08:00
committed by Michael Vines
parent 662ce22cdd
commit 6ed2bad9d0
12 changed files with 932 additions and 580 deletions

View File

@ -2,116 +2,351 @@
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 {SYSVAR_RECENT_BLOCKHASHES_PUBKEY, SYSVAR_RENT_PUBKEY} from './sysvar';
import {Transaction, TransactionInstruction} from './transaction';
import type {TransactionInstructionCtorFields} from './transaction';
/**
* Create account system transaction params
* @typedef {Object} CreateAccountParams
* @property {PublicKey} fromPubkey
* @property {PublicKey} newAccountPubkey
* @property {number} lamports
* @property {number} space
* @property {PublicKey} programId
*/
export type CreateAccountParams = {|
fromPubkey: PublicKey,
newAccountPubkey: PublicKey,
lamports: number,
space: number,
programId: PublicKey,
|};
/**
* Transfer system transaction params
* @typedef {Object} TransferParams
* @property {PublicKey} fromPubkey
* @property {PublicKey} toPubkey
* @property {number} lamports
*/
export type TransferParams = {|
fromPubkey: PublicKey,
toPubkey: PublicKey,
lamports: number,
|};
/**
* Assign system transaction params
* @typedef {Object} AssignParams
* @property {PublicKey} fromPubkey
* @property {PublicKey} programId
*/
export type AssignParams = {|
fromPubkey: PublicKey,
programId: PublicKey,
|};
/**
* Create account with seed system transaction params
* @typedef {Object} CreateAccountWithSeedParams
* @property {PublicKey} fromPubkey
* @property {PublicKey} newAccountPubkey
* @property {PublicKey} basePubkey
* @property {string} seed
* @property {number} lamports
* @property {number} space
* @property {PublicKey} programId
*/
export type CreateAccountWithSeedParams = {|
fromPubkey: PublicKey,
newAccountPubkey: PublicKey,
basePubkey: PublicKey,
seed: string,
lamports: number,
space: number,
programId: PublicKey,
|};
/**
* Create nonce account system transaction params
* @typedef {Object} AssignParams
* @property {PublicKey} fromPubkey
* @property {PublicKey} programId
*/
export type CreateNonceAccountParams = {|
fromPubkey: PublicKey,
noncePubkey: PublicKey,
authorizedPubkey: PublicKey,
lamports: number,
|};
/**
* Initialize nonce account system instruction params
* @typedef {Object} InitializeNonceParams
* @property {PublicKey} fromPubkey
* @property {PublicKey} programId
*/
export type InitializeNonceParams = {|
noncePubkey: PublicKey,
authorizedPubkey: PublicKey,
|};
/**
* Advance nonce account system instruction params
* @typedef {Object} AdvanceNonceParams
* @property {PublicKey} fromPubkey
* @property {PublicKey} programId
*/
export type AdvanceNonceParams = {|
noncePubkey: PublicKey,
authorizedPubkey: PublicKey,
|};
/**
* Withdraw nonce account system transaction params
* @typedef {Object} WithdrawNonceParams
* @property {PublicKey} noncePubkey
* @property {PublicKey} authorizedPubkey
* @property {PublicKey} toPubkey
* @property {number} lamports
*/
export type WithdrawNonceParams = {|
noncePubkey: PublicKey,
authorizedPubkey: PublicKey,
toPubkey: PublicKey,
lamports: number,
|};
/**
* Authorize nonce account system transaction params
* @typedef {Object} AuthorizeNonceParams
* @property {PublicKey} noncePubkey
* @property {PublicKey} authorizedPubkey
* @property {PublicKey} newAuthorizedPubkey
*/
export type AuthorizeNonceParams = {|
noncePubkey: PublicKey,
authorizedPubkey: PublicKey,
newAuthorizedPubkey: PublicKey,
|};
/**
* System Instruction class
*/
export class SystemInstruction extends TransactionInstruction {
export class SystemInstruction {
/**
* Type of SystemInstruction
* Decode a system instruction and retrieve the instruction type.
*/
_type: SystemInstructionType;
constructor(
opts?: TransactionInstructionCtorFields,
type: SystemInstructionType,
) {
if (
opts &&
opts.programId &&
!opts.programId.equals(SystemProgram.programId)
) {
throw new Error('programId incorrect; not a SystemInstruction');
}
super(opts);
this._type = type;
}
static from(instruction: TransactionInstruction): SystemInstruction {
if (!instruction.programId.equals(SystemProgram.programId)) {
throw new Error('programId incorrect; not SystemProgram');
}
static decodeInstructionType(
instruction: TransactionInstruction,
): SystemInstructionType {
this.checkProgramId(instruction.programId);
const instructionTypeLayout = BufferLayout.u32('instruction');
const typeIndex = instructionTypeLayout.decode(instruction.data);
let type;
for (const t of Object.keys(SYSTEM_INSTRUCTION_LAYOUTS)) {
if (SYSTEM_INSTRUCTION_LAYOUTS[t].index == typeIndex) {
type = t;
}
}
if (!type) {
throw new Error('Instruction type incorrect; not a SystemInstruction');
}
return new SystemInstruction(
{
keys: instruction.keys,
programId: instruction.programId,
data: instruction.data,
},
type,
return type;
}
/**
* Decode a create account system instruction and retrieve the instruction params.
*/
static decodeCreateAccount(
instruction: TransactionInstruction,
): CreateAccountParams {
this.checkProgramId(instruction.programId);
this.checkKeyLength(instruction.keys, 2);
const {lamports, space, programId} = decodeData(
SYSTEM_INSTRUCTION_LAYOUTS.Create,
instruction.data,
);
return {
fromPubkey: instruction.keys[0].pubkey,
newAccountPubkey: instruction.keys[1].pubkey,
lamports,
space,
programId: new PublicKey(programId),
};
}
/**
* Type of SystemInstruction
* Decode a transfer system instruction and retrieve the instruction params.
*/
get type(): SystemInstructionType {
return this._type;
static decodeTransfer(instruction: TransactionInstruction): TransferParams {
this.checkProgramId(instruction.programId);
this.checkKeyLength(instruction.keys, 2);
const {lamports} = decodeData(
SYSTEM_INSTRUCTION_LAYOUTS.Transfer,
instruction.data,
);
return {
fromPubkey: instruction.keys[0].pubkey,
toPubkey: instruction.keys[1].pubkey,
lamports,
};
}
/**
* The `from` public key of the instruction;
* returns null if SystemInstructionType does not support this field
* Decode an assign system instruction and retrieve the instruction params.
*/
get fromPublicKey(): PublicKey | null {
switch (this.type) {
case 'Create':
case 'CreateWithSeed':
case 'WithdrawNonceAccount':
case 'Transfer':
return this.keys[0].pubkey;
default:
return null;
static decodeAssign(instruction: TransactionInstruction): AssignParams {
this.checkProgramId(instruction.programId);
this.checkKeyLength(instruction.keys, 1);
const {programId} = decodeData(
SYSTEM_INSTRUCTION_LAYOUTS.Assign,
instruction.data,
);
return {
fromPubkey: instruction.keys[0].pubkey,
programId: new PublicKey(programId),
};
}
/**
* Decode a create account with seed system instruction and retrieve the instruction params.
*/
static decodeCreateWithSeed(
instruction: TransactionInstruction,
): CreateAccountWithSeedParams {
this.checkProgramId(instruction.programId);
this.checkKeyLength(instruction.keys, 2);
const {base, seed, lamports, space, programId} = decodeData(
SYSTEM_INSTRUCTION_LAYOUTS.CreateWithSeed,
instruction.data,
);
return {
fromPubkey: instruction.keys[0].pubkey,
newAccountPubkey: instruction.keys[1].pubkey,
basePubkey: new PublicKey(base),
seed,
lamports,
space,
programId: new PublicKey(programId),
};
}
/**
* Decode a nonce initialize system instruction and retrieve the instruction params.
*/
static decodeNonceInitialize(
instruction: TransactionInstruction,
): InitializeNonceParams {
this.checkProgramId(instruction.programId);
this.checkKeyLength(instruction.keys, 3);
const {authorized} = decodeData(
SYSTEM_INSTRUCTION_LAYOUTS.InitializeNonceAccount,
instruction.data,
);
return {
noncePubkey: instruction.keys[0].pubkey,
authorizedPubkey: new PublicKey(authorized),
};
}
/**
* Decode a nonce advance system instruction and retrieve the instruction params.
*/
static decodeNonceAdvance(
instruction: TransactionInstruction,
): AdvanceNonceParams {
this.checkProgramId(instruction.programId);
this.checkKeyLength(instruction.keys, 3);
decodeData(
SYSTEM_INSTRUCTION_LAYOUTS.AdvanceNonceAccount,
instruction.data,
);
return {
noncePubkey: instruction.keys[0].pubkey,
authorizedPubkey: instruction.keys[2].pubkey,
};
}
/**
* Decode a nonce withdraw system instruction and retrieve the instruction params.
*/
static decodeNonceWithdraw(
instruction: TransactionInstruction,
): WithdrawNonceParams {
this.checkProgramId(instruction.programId);
this.checkKeyLength(instruction.keys, 5);
const {lamports} = decodeData(
SYSTEM_INSTRUCTION_LAYOUTS.WithdrawNonceAccount,
instruction.data,
);
return {
noncePubkey: instruction.keys[0].pubkey,
toPubkey: instruction.keys[1].pubkey,
authorizedPubkey: instruction.keys[4].pubkey,
lamports,
};
}
/**
* Decode a nonce authorize system instruction and retrieve the instruction params.
*/
static decodeNonceAuthorize(
instruction: TransactionInstruction,
): AuthorizeNonceParams {
this.checkProgramId(instruction.programId);
this.checkKeyLength(instruction.keys, 2);
const {authorized} = decodeData(
SYSTEM_INSTRUCTION_LAYOUTS.AuthorizeNonceAccount,
instruction.data,
);
return {
noncePubkey: instruction.keys[0].pubkey,
authorizedPubkey: instruction.keys[1].pubkey,
newAuthorizedPubkey: new PublicKey(authorized),
};
}
/**
* @private
*/
static checkProgramId(programId: PublicKey) {
if (!programId.equals(SystemProgram.programId)) {
throw new Error('invalid instruction; programId is not SystemProgram');
}
}
/**
* The `to` public key of the instruction;
* returns null if SystemInstructionType does not support this field
* @private
*/
get toPublicKey(): PublicKey | null {
switch (this.type) {
case 'Create':
case 'CreateWithSeed':
case 'WithdrawNonceAccount':
case 'Transfer':
return this.keys[1].pubkey;
default:
return null;
}
}
/**
* The `amount` or `lamports` of the instruction;
* returns null if SystemInstructionType does not support this field
*/
get amount(): number | null {
const data = SYSTEM_INSTRUCTION_LAYOUTS[this.type].layout.decode(this.data);
switch (this.type) {
case 'Create':
case 'CreateWithSeed':
case 'WithdrawNonceAccount':
case 'Transfer':
return data.lamports;
default:
return null;
static checkKeyLength(keys: Array<any>, expectedLength: number) {
if (keys.length !== expectedLength) {
throw new Error(
`invalid instruction; key length mismatch ${keys.length} != ${expectedLength}`,
);
}
}
}
@ -212,24 +447,18 @@ export class SystemProgram {
/**
* Generate a Transaction that creates a new account
*/
static createAccount(
from: PublicKey,
newAccount: PublicKey,
lamports: number,
space: number,
programId: PublicKey,
): Transaction {
static createAccount(params: CreateAccountParams): Transaction {
const type = SYSTEM_INSTRUCTION_LAYOUTS.Create;
const data = encodeData(type, {
lamports,
space,
programId: programId.toBuffer(),
lamports: params.lamports,
space: params.space,
programId: params.programId.toBuffer(),
});
return new Transaction().add({
keys: [
{pubkey: from, isSigner: true, isWritable: true},
{pubkey: newAccount, isSigner: true, isWritable: true},
{pubkey: params.fromPubkey, isSigner: true, isWritable: true},
{pubkey: params.newAccountPubkey, isSigner: true, isWritable: true},
],
programId: this.programId,
data,
@ -239,18 +468,14 @@ export class SystemProgram {
/**
* Generate a Transaction that transfers lamports from one account to another
*/
static transfer(
from: PublicKey,
to: PublicKey,
lamports: number,
): Transaction {
static transfer(params: TransferParams): Transaction {
const type = SYSTEM_INSTRUCTION_LAYOUTS.Transfer;
const data = encodeData(type, {lamports});
const data = encodeData(type, {lamports: params.lamports});
return new Transaction().add({
keys: [
{pubkey: from, isSigner: true, isWritable: true},
{pubkey: to, isSigner: false, isWritable: true},
{pubkey: params.fromPubkey, isSigner: true, isWritable: true},
{pubkey: params.toPubkey, isSigner: false, isWritable: true},
],
programId: this.programId,
data,
@ -260,12 +485,12 @@ export class SystemProgram {
/**
* Generate a Transaction that assigns an account to a program
*/
static assign(from: PublicKey, programId: PublicKey): Transaction {
static assign(params: AssignParams): Transaction {
const type = SYSTEM_INSTRUCTION_LAYOUTS.Assign;
const data = encodeData(type, {programId: programId.toBuffer()});
const data = encodeData(type, {programId: params.programId.toBuffer()});
return new Transaction().add({
keys: [{pubkey: from, isSigner: true, isWritable: true}],
keys: [{pubkey: params.fromPubkey, isSigner: true, isWritable: true}],
programId: this.programId,
data,
});
@ -276,27 +501,21 @@ export class SystemProgram {
* an address generated with `from`, a seed, and programId
*/
static createAccountWithSeed(
from: PublicKey,
newAccount: PublicKey,
base: PublicKey,
seed: string,
lamports: number,
space: number,
programId: PublicKey,
params: CreateAccountWithSeedParams,
): Transaction {
const type = SYSTEM_INSTRUCTION_LAYOUTS.CreateWithSeed;
const data = encodeData(type, {
base: base.toBuffer(),
seed,
lamports,
space,
programId: programId.toBuffer(),
base: params.basePubkey.toBuffer(),
seed: params.seed,
lamports: params.lamports,
space: params.space,
programId: params.programId.toBuffer(),
});
return new Transaction().add({
keys: [
{pubkey: from, isSigner: true, isWritable: true},
{pubkey: newAccount, isSigner: false, isWritable: true},
{pubkey: params.fromPubkey, isSigner: true, isWritable: true},
{pubkey: params.newAccountPubkey, isSigner: false, isWritable: true},
],
programId: this.programId,
data,
@ -306,28 +525,37 @@ export class SystemProgram {
/**
* Generate a Transaction that creates a new Nonce account
*/
static createNonceAccount(
from: PublicKey,
nonceAccount: PublicKey,
authorizedPubkey: PublicKey,
lamports: number,
): Transaction {
let transaction = SystemProgram.createAccount(
from,
nonceAccount,
lamports,
this.nonceSpace,
this.programId,
);
const type = SYSTEM_INSTRUCTION_LAYOUTS.InitializeNonceAccount;
const data = encodeData(type, {
authorized: authorizedPubkey.toBuffer(),
static createNonceAccount(params: CreateNonceAccountParams): Transaction {
let transaction = SystemProgram.createAccount({
fromPubkey: params.fromPubkey,
newAccountPubkey: params.noncePubkey,
lamports: params.lamports,
space: this.nonceSpace,
programId: this.programId,
});
return transaction.add({
const initParams = {
noncePubkey: params.noncePubkey,
authorizedPubkey: params.authorizedPubkey,
};
transaction.add(this.nonceInitialize(initParams));
return transaction;
}
/**
* Generate an instruction to initialize a Nonce account
*/
static nonceInitialize(
params: InitializeNonceParams,
): TransactionInstruction {
const type = SYSTEM_INSTRUCTION_LAYOUTS.InitializeNonceAccount;
const data = encodeData(type, {
authorized: params.authorizedPubkey.toBuffer(),
});
const instructionData = {
keys: [
{pubkey: nonceAccount, isSigner: false, isWritable: true},
{pubkey: params.noncePubkey, isSigner: false, isWritable: true},
{
pubkey: SYSVAR_RECENT_BLOCKHASHES_PUBKEY,
isSigner: false,
@ -337,27 +565,25 @@ export class SystemProgram {
],
programId: this.programId,
data,
});
};
return new TransactionInstruction(instructionData);
}
/**
* Generate an instruction to advance the nonce in a Nonce account
*/
static nonceAdvance(
nonceAccount: PublicKey,
authorizedPubkey: PublicKey,
): TransactionInstruction {
static nonceAdvance(params: AdvanceNonceParams): TransactionInstruction {
const type = SYSTEM_INSTRUCTION_LAYOUTS.AdvanceNonceAccount;
const data = encodeData(type);
const instructionData = {
keys: [
{pubkey: nonceAccount, isSigner: false, isWritable: true},
{pubkey: params.noncePubkey, isSigner: false, isWritable: true},
{
pubkey: SYSVAR_RECENT_BLOCKHASHES_PUBKEY,
isSigner: false,
isWritable: false,
},
{pubkey: authorizedPubkey, isSigner: true, isWritable: false},
{pubkey: params.authorizedPubkey, isSigner: true, isWritable: false},
],
programId: this.programId,
data,
@ -368,19 +594,14 @@ export class SystemProgram {
/**
* Generate a Transaction that withdraws lamports from a Nonce account
*/
static nonceWithdraw(
nonceAccount: PublicKey,
authorizedPubkey: PublicKey,
to: PublicKey,
lamports: number,
): Transaction {
static nonceWithdraw(params: WithdrawNonceParams): Transaction {
const type = SYSTEM_INSTRUCTION_LAYOUTS.WithdrawNonceAccount;
const data = encodeData(type, {lamports});
const data = encodeData(type, {lamports: params.lamports});
return new Transaction().add({
keys: [
{pubkey: nonceAccount, isSigner: false, isWritable: true},
{pubkey: to, isSigner: false, isWritable: true},
{pubkey: params.noncePubkey, isSigner: false, isWritable: true},
{pubkey: params.toPubkey, isSigner: false, isWritable: true},
{
pubkey: SYSVAR_RECENT_BLOCKHASHES_PUBKEY,
isSigner: false,
@ -391,7 +612,7 @@ export class SystemProgram {
isSigner: false,
isWritable: false,
},
{pubkey: authorizedPubkey, isSigner: true, isWritable: false},
{pubkey: params.authorizedPubkey, isSigner: true, isWritable: false},
],
programId: this.programId,
data,
@ -402,20 +623,16 @@ export class SystemProgram {
* Generate a Transaction that authorizes a new PublicKey as the authority
* on a Nonce account.
*/
static nonceAuthorize(
nonceAccount: PublicKey,
authorizedPubkey: PublicKey,
newAuthorized: PublicKey,
): Transaction {
static nonceAuthorize(params: AuthorizeNonceParams): Transaction {
const type = SYSTEM_INSTRUCTION_LAYOUTS.AuthorizeNonceAccount;
const data = encodeData(type, {
newAuthorized: newAuthorized.toBuffer(),
authorized: params.newAuthorizedPubkey.toBuffer(),
});
return new Transaction().add({
keys: [
{pubkey: nonceAccount, isSigner: false, isWritable: true},
{pubkey: authorizedPubkey, isSigner: true, isWritable: false},
{pubkey: params.noncePubkey, isSigner: false, isWritable: true},
{pubkey: params.authorizedPubkey, isSigner: true, isWritable: false},
],
programId: this.programId,
data,