fix: add tx instruction->transfer data functionality

This commit is contained in:
Tyera Eulberg 2019-09-13 18:07:13 -06:00 committed by Michael Vines
parent ee3acbf1ba
commit daba1a7856
4 changed files with 257 additions and 47 deletions

View File

@ -5,7 +5,7 @@ export {BudgetProgram} from './budget-program';
export {Connection} from './connection';
export {Loader} from './loader';
export {PublicKey} from './publickey';
export {SystemProgram} from './system-program';
export {SystemInstruction, SystemProgram} from './system-program';
export {Token, TokenAmount} from './token-program';
export {Transaction, TransactionInstruction} from './transaction';
export {VALIDATOR_INFO_KEY, ValidatorInfo} from './validator-info';

View File

@ -2,9 +2,154 @@
import * as BufferLayout from 'buffer-layout';
import {Transaction} from './transaction';
import {Transaction, TransactionInstruction} from './transaction';
import {PublicKey} from './publickey';
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
@ -29,23 +174,12 @@ export class SystemProgram {
space: number,
programId: PublicKey,
): Transaction {
const dataLayout = BufferLayout.struct([
BufferLayout.u32('instruction'),
BufferLayout.ns64('lamports'),
BufferLayout.ns64('space'),
Layout.publicKey('programId'),
]);
const data = Buffer.alloc(dataLayout.span);
dataLayout.encode(
{
instruction: 0, // Create Account instruction
lamports,
space,
programId: programId.toBuffer(),
},
data,
);
const type = SystemInstructionEnum.CREATE;
const data = encodeData(type, {
lamports,
space,
programId: programId.toBuffer(),
});
return new Transaction().add({
keys: [
@ -61,19 +195,8 @@ export class SystemProgram {
* Generate a Transaction that transfers lamports from one account to another
*/
static transfer(from: PublicKey, to: PublicKey, amount: number): Transaction {
const dataLayout = BufferLayout.struct([
BufferLayout.u32('instruction'),
BufferLayout.ns64('amount'),
]);
const data = Buffer.alloc(dataLayout.span);
dataLayout.encode(
{
instruction: 2, // Move instruction
amount,
},
data,
);
const type = SystemInstructionEnum.TRANSFER;
const data = encodeData(type, {amount});
return new Transaction().add({
keys: [
@ -89,19 +212,8 @@ export class SystemProgram {
* Generate a Transaction that assigns an account to a program
*/
static assign(from: PublicKey, programId: PublicKey): Transaction {
const dataLayout = BufferLayout.struct([
BufferLayout.u32('instruction'),
Layout.publicKey('programId'),
]);
const data = Buffer.alloc(dataLayout.span);
dataLayout.encode(
{
instruction: 1, // Assign instruction
programId: programId.toBuffer(),
},
data,
);
const type = SystemInstructionEnum.ASSIGN;
const data = encodeData(type, {programId: programId.toBuffer()});
return new Transaction().add({
keys: [{pubkey: from, isSigner: true, isDebitable: true}],

View File

@ -40,7 +40,7 @@ export const PACKET_DATA_SIZE = 1280 - 40 - 8;
* @property {?PublicKey} programId
* @property {?Buffer} data
*/
type TransactionInstructionCtorFields = {|
export type TransactionInstructionCtorFields = {|
keys?: Array<{pubkey: PublicKey, isSigner: boolean, isDebitable: boolean}>,
programId?: PublicKey,
data?: Buffer,

View File

@ -1,6 +1,12 @@
// @flow
import {Account, BudgetProgram, SystemProgram} from '../src';
import {
Account,
BudgetProgram,
SystemInstruction,
SystemProgram,
Transaction,
} from '../src';
test('createAccount', () => {
const from = new Account();
@ -43,3 +49,95 @@ test('assign', () => {
expect(transaction.programId).toEqual(SystemProgram.programId);
// 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();
});