fix: add tx instruction->transfer data functionality
This commit is contained in:
committed by
Michael Vines
parent
ee3acbf1ba
commit
daba1a7856
@ -5,7 +5,7 @@ export {BudgetProgram} from './budget-program';
|
|||||||
export {Connection} from './connection';
|
export {Connection} from './connection';
|
||||||
export {Loader} from './loader';
|
export {Loader} from './loader';
|
||||||
export {PublicKey} from './publickey';
|
export {PublicKey} from './publickey';
|
||||||
export {SystemProgram} from './system-program';
|
export {SystemInstruction, SystemProgram} from './system-program';
|
||||||
export {Token, TokenAmount} from './token-program';
|
export {Token, TokenAmount} from './token-program';
|
||||||
export {Transaction, TransactionInstruction} from './transaction';
|
export {Transaction, TransactionInstruction} from './transaction';
|
||||||
export {VALIDATOR_INFO_KEY, ValidatorInfo} from './validator-info';
|
export {VALIDATOR_INFO_KEY, ValidatorInfo} from './validator-info';
|
||||||
|
@ -2,9 +2,154 @@
|
|||||||
|
|
||||||
import * as BufferLayout from 'buffer-layout';
|
import * as BufferLayout from 'buffer-layout';
|
||||||
|
|
||||||
import {Transaction} from './transaction';
|
import {Transaction, TransactionInstruction} from './transaction';
|
||||||
import {PublicKey} from './publickey';
|
import {PublicKey} from './publickey';
|
||||||
import * as Layout from './layout';
|
import * as Layout from './layout';
|
||||||
|
import type {TransactionInstructionCtorFields} from './transaction';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* System Instruction class
|
||||||
|
*/
|
||||||
|
export class SystemInstruction extends TransactionInstruction {
|
||||||
|
/**
|
||||||
|
* Type of SystemInstruction
|
||||||
|
*/
|
||||||
|
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);
|
||||||
|
if (type) {
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static from(instruction: TransactionInstruction): SystemInstruction {
|
||||||
|
if (!instruction.programId.equals(SystemProgram.programId)) {
|
||||||
|
throw new Error('programId incorrect; not SystemProgram');
|
||||||
|
}
|
||||||
|
|
||||||
|
const instructionTypeLayout = BufferLayout.u32('instruction');
|
||||||
|
const typeIndex = instructionTypeLayout.decode(instruction.data);
|
||||||
|
let type;
|
||||||
|
for (const t in SystemInstructionEnum) {
|
||||||
|
if (SystemInstructionEnum[t].index == typeIndex) {
|
||||||
|
type = SystemInstructionEnum[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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The `from` public key of the instruction;
|
||||||
|
* returns null if SystemInstructionType does not support this field
|
||||||
|
*/
|
||||||
|
get From(): PublicKey | null {
|
||||||
|
if (
|
||||||
|
this.type == SystemInstructionEnum.CREATE ||
|
||||||
|
this.type == SystemInstructionEnum.TRANSFER
|
||||||
|
) {
|
||||||
|
return this.keys[0].pubkey;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The `to` public key of the instruction;
|
||||||
|
* returns null if SystemInstructionType does not support this field
|
||||||
|
*/
|
||||||
|
get To(): PublicKey | null {
|
||||||
|
if (
|
||||||
|
this.type == SystemInstructionEnum.CREATE ||
|
||||||
|
this.type == SystemInstructionEnum.TRANSFER
|
||||||
|
) {
|
||||||
|
return this.keys[1].pubkey;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The `amount` or `lamports` of the instruction;
|
||||||
|
* returns null if SystemInstructionType does not support this field
|
||||||
|
*/
|
||||||
|
get Amount(): number | null {
|
||||||
|
const data = this.type.layout.decode(this.data);
|
||||||
|
if (this.type == SystemInstructionEnum.TRANSFER) {
|
||||||
|
return data.amount;
|
||||||
|
} else if (this.type == SystemInstructionEnum.CREATE) {
|
||||||
|
return data.lamports;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} SystemInstructionType
|
||||||
|
* @property (index} The System Instruction index (from solana-sdk)
|
||||||
|
* @property (BufferLayout} The BufferLayout to use to build data
|
||||||
|
*/
|
||||||
|
type SystemInstructionType = {|
|
||||||
|
index: number,
|
||||||
|
layout: typeof BufferLayout,
|
||||||
|
|};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An enumeration of valid SystemInstructionTypes
|
||||||
|
*/
|
||||||
|
const SystemInstructionEnum = Object.freeze({
|
||||||
|
CREATE: {
|
||||||
|
index: 0,
|
||||||
|
layout: BufferLayout.struct([
|
||||||
|
BufferLayout.u32('instruction'),
|
||||||
|
BufferLayout.ns64('lamports'),
|
||||||
|
BufferLayout.ns64('space'),
|
||||||
|
Layout.publicKey('programId'),
|
||||||
|
]),
|
||||||
|
},
|
||||||
|
ASSIGN: {
|
||||||
|
index: 1,
|
||||||
|
layout: BufferLayout.struct([
|
||||||
|
BufferLayout.u32('instruction'),
|
||||||
|
Layout.publicKey('programId'),
|
||||||
|
]),
|
||||||
|
},
|
||||||
|
TRANSFER: {
|
||||||
|
index: 2,
|
||||||
|
layout: BufferLayout.struct([
|
||||||
|
BufferLayout.u32('instruction'),
|
||||||
|
BufferLayout.ns64('amount'),
|
||||||
|
]),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Populate a buffer of instruction data using the SystemInstructionType
|
||||||
|
*/
|
||||||
|
function encodeData(type: SystemInstructionType, fields: Object): Buffer {
|
||||||
|
const data = Buffer.alloc(type.layout.span);
|
||||||
|
const layoutFields = Object.assign({instruction: type.index}, fields);
|
||||||
|
type.layout.encode(layoutFields, data);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Factory class for transactions to interact with the System program
|
* Factory class for transactions to interact with the System program
|
||||||
@ -29,23 +174,12 @@ export class SystemProgram {
|
|||||||
space: number,
|
space: number,
|
||||||
programId: PublicKey,
|
programId: PublicKey,
|
||||||
): Transaction {
|
): Transaction {
|
||||||
const dataLayout = BufferLayout.struct([
|
const type = SystemInstructionEnum.CREATE;
|
||||||
BufferLayout.u32('instruction'),
|
const data = encodeData(type, {
|
||||||
BufferLayout.ns64('lamports'),
|
|
||||||
BufferLayout.ns64('space'),
|
|
||||||
Layout.publicKey('programId'),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const data = Buffer.alloc(dataLayout.span);
|
|
||||||
dataLayout.encode(
|
|
||||||
{
|
|
||||||
instruction: 0, // Create Account instruction
|
|
||||||
lamports,
|
lamports,
|
||||||
space,
|
space,
|
||||||
programId: programId.toBuffer(),
|
programId: programId.toBuffer(),
|
||||||
},
|
});
|
||||||
data,
|
|
||||||
);
|
|
||||||
|
|
||||||
return new Transaction().add({
|
return new Transaction().add({
|
||||||
keys: [
|
keys: [
|
||||||
@ -61,19 +195,8 @@ export class SystemProgram {
|
|||||||
* Generate a Transaction that transfers lamports from one account to another
|
* Generate a Transaction that transfers lamports from one account to another
|
||||||
*/
|
*/
|
||||||
static transfer(from: PublicKey, to: PublicKey, amount: number): Transaction {
|
static transfer(from: PublicKey, to: PublicKey, amount: number): Transaction {
|
||||||
const dataLayout = BufferLayout.struct([
|
const type = SystemInstructionEnum.TRANSFER;
|
||||||
BufferLayout.u32('instruction'),
|
const data = encodeData(type, {amount});
|
||||||
BufferLayout.ns64('amount'),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const data = Buffer.alloc(dataLayout.span);
|
|
||||||
dataLayout.encode(
|
|
||||||
{
|
|
||||||
instruction: 2, // Move instruction
|
|
||||||
amount,
|
|
||||||
},
|
|
||||||
data,
|
|
||||||
);
|
|
||||||
|
|
||||||
return new Transaction().add({
|
return new Transaction().add({
|
||||||
keys: [
|
keys: [
|
||||||
@ -89,19 +212,8 @@ export class SystemProgram {
|
|||||||
* Generate a Transaction that assigns an account to a program
|
* Generate a Transaction that assigns an account to a program
|
||||||
*/
|
*/
|
||||||
static assign(from: PublicKey, programId: PublicKey): Transaction {
|
static assign(from: PublicKey, programId: PublicKey): Transaction {
|
||||||
const dataLayout = BufferLayout.struct([
|
const type = SystemInstructionEnum.ASSIGN;
|
||||||
BufferLayout.u32('instruction'),
|
const data = encodeData(type, {programId: programId.toBuffer()});
|
||||||
Layout.publicKey('programId'),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const data = Buffer.alloc(dataLayout.span);
|
|
||||||
dataLayout.encode(
|
|
||||||
{
|
|
||||||
instruction: 1, // Assign instruction
|
|
||||||
programId: programId.toBuffer(),
|
|
||||||
},
|
|
||||||
data,
|
|
||||||
);
|
|
||||||
|
|
||||||
return new Transaction().add({
|
return new Transaction().add({
|
||||||
keys: [{pubkey: from, isSigner: true, isDebitable: true}],
|
keys: [{pubkey: from, isSigner: true, isDebitable: true}],
|
||||||
|
@ -40,7 +40,7 @@ export const PACKET_DATA_SIZE = 1280 - 40 - 8;
|
|||||||
* @property {?PublicKey} programId
|
* @property {?PublicKey} programId
|
||||||
* @property {?Buffer} data
|
* @property {?Buffer} data
|
||||||
*/
|
*/
|
||||||
type TransactionInstructionCtorFields = {|
|
export type TransactionInstructionCtorFields = {|
|
||||||
keys?: Array<{pubkey: PublicKey, isSigner: boolean, isDebitable: boolean}>,
|
keys?: Array<{pubkey: PublicKey, isSigner: boolean, isDebitable: boolean}>,
|
||||||
programId?: PublicKey,
|
programId?: PublicKey,
|
||||||
data?: Buffer,
|
data?: Buffer,
|
||||||
|
@ -1,6 +1,12 @@
|
|||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
import {Account, BudgetProgram, SystemProgram} from '../src';
|
import {
|
||||||
|
Account,
|
||||||
|
BudgetProgram,
|
||||||
|
SystemInstruction,
|
||||||
|
SystemProgram,
|
||||||
|
Transaction,
|
||||||
|
} from '../src';
|
||||||
|
|
||||||
test('createAccount', () => {
|
test('createAccount', () => {
|
||||||
const from = new Account();
|
const from = new Account();
|
||||||
@ -43,3 +49,95 @@ test('assign', () => {
|
|||||||
expect(transaction.programId).toEqual(SystemProgram.programId);
|
expect(transaction.programId).toEqual(SystemProgram.programId);
|
||||||
// TODO: Validate transaction contents more
|
// TODO: Validate transaction contents more
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('SystemInstruction create', () => {
|
||||||
|
const from = new Account();
|
||||||
|
const to = new Account();
|
||||||
|
const program = new Account();
|
||||||
|
const amount = 42;
|
||||||
|
const space = 100;
|
||||||
|
const recentBlockhash = 'EETubP5AKHgjPAhzPAFcb8BAY1hMH639CWCFTqi3hq1k'; // Arbitrary known recentBlockhash
|
||||||
|
const create = SystemProgram.createAccount(
|
||||||
|
from.publicKey,
|
||||||
|
to.publicKey,
|
||||||
|
amount,
|
||||||
|
space,
|
||||||
|
program.publicKey,
|
||||||
|
);
|
||||||
|
const transaction = new Transaction({recentBlockhash}).add(create);
|
||||||
|
|
||||||
|
const systemInstruction = SystemInstruction.from(transaction.instructions[0]);
|
||||||
|
expect(systemInstruction.From).toEqual(from.publicKey);
|
||||||
|
expect(systemInstruction.To).toEqual(to.publicKey);
|
||||||
|
expect(systemInstruction.Amount).toEqual(amount);
|
||||||
|
expect(systemInstruction.programId).toEqual(SystemProgram.programId);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('SystemInstruction transfer', () => {
|
||||||
|
const from = new Account();
|
||||||
|
const to = new Account();
|
||||||
|
const amount = 42;
|
||||||
|
const recentBlockhash = 'EETubP5AKHgjPAhzPAFcb8BAY1hMH639CWCFTqi3hq1k'; // Arbitrary known recentBlockhash
|
||||||
|
const transfer = SystemProgram.transfer(from.publicKey, to.publicKey, amount);
|
||||||
|
const transaction = new Transaction({recentBlockhash}).add(transfer);
|
||||||
|
transaction.sign(from);
|
||||||
|
|
||||||
|
const systemInstruction = SystemInstruction.from(transaction.instructions[0]);
|
||||||
|
expect(systemInstruction.From).toEqual(from.publicKey);
|
||||||
|
expect(systemInstruction.To).toEqual(to.publicKey);
|
||||||
|
expect(systemInstruction.Amount).toEqual(amount);
|
||||||
|
expect(systemInstruction.programId).toEqual(SystemProgram.programId);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('SystemInstruction assign', () => {
|
||||||
|
const from = new Account();
|
||||||
|
const program = new Account();
|
||||||
|
const recentBlockhash = 'EETubP5AKHgjPAhzPAFcb8BAY1hMH639CWCFTqi3hq1k'; // Arbitrary known recentBlockhash
|
||||||
|
const assign = SystemProgram.assign(from.publicKey, program.publicKey);
|
||||||
|
const transaction = new Transaction({recentBlockhash}).add(assign);
|
||||||
|
transaction.sign(from);
|
||||||
|
|
||||||
|
const systemInstruction = SystemInstruction.from(transaction.instructions[0]);
|
||||||
|
expect(systemInstruction.From).toBeNull();
|
||||||
|
expect(systemInstruction.To).toBeNull();
|
||||||
|
expect(systemInstruction.Amount).toBeNull();
|
||||||
|
expect(systemInstruction.programId).toEqual(SystemProgram.programId);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('non-SystemInstruction error', () => {
|
||||||
|
const from = new Account();
|
||||||
|
const program = new Account();
|
||||||
|
const to = new Account();
|
||||||
|
|
||||||
|
const badProgramId = {
|
||||||
|
keys: [
|
||||||
|
{pubkey: from.publicKey, isSigner: true, isDebitable: true},
|
||||||
|
{pubkey: to.publicKey, isSigner: false, isDebitable: false},
|
||||||
|
],
|
||||||
|
programId: BudgetProgram.programId,
|
||||||
|
data: Buffer.from([2, 0, 0, 0]),
|
||||||
|
};
|
||||||
|
expect(() => {
|
||||||
|
new SystemInstruction(badProgramId);
|
||||||
|
}).toThrow();
|
||||||
|
|
||||||
|
const amount = 123;
|
||||||
|
const recentBlockhash = 'EETubP5AKHgjPAhzPAFcb8BAY1hMH639CWCFTqi3hq1k'; // Arbitrary known recentBlockhash
|
||||||
|
const budgetPay = BudgetProgram.pay(
|
||||||
|
from.publicKey,
|
||||||
|
program.publicKey,
|
||||||
|
to.publicKey,
|
||||||
|
amount,
|
||||||
|
);
|
||||||
|
const transaction = new Transaction({recentBlockhash}).add(budgetPay);
|
||||||
|
transaction.sign(from);
|
||||||
|
|
||||||
|
expect(() => {
|
||||||
|
SystemInstruction.from(transaction.instructions[1]);
|
||||||
|
}).toThrow();
|
||||||
|
|
||||||
|
transaction.instructions[0].data[0] = 4;
|
||||||
|
expect(() => {
|
||||||
|
SystemInstruction.from(transaction.instructions[0]);
|
||||||
|
}).toThrow();
|
||||||
|
});
|
||||||
|
Reference in New Issue
Block a user