feat: add Nonce instructions to system program

This commit is contained in:
Tyera Eulberg
2020-01-02 17:58:54 -07:00
committed by Michael Vines
parent 05491f7fb7
commit c9cc44ae4f
3 changed files with 247 additions and 12 deletions

View File

@ -6,6 +6,7 @@ import {encodeData} from './instruction';
import type {InstructionType} from './instruction'; import type {InstructionType} from './instruction';
import * as Layout from './layout'; import * as Layout from './layout';
import {PublicKey} from './publickey'; import {PublicKey} from './publickey';
import {SYSVAR_RECENT_BLOCKHASHES_PUBKEY, SYSVAR_RENT_PUBKEY} from './sysvar';
import {Transaction, TransactionInstruction} from './transaction'; import {Transaction, TransactionInstruction} from './transaction';
import type {TransactionInstructionCtorFields} from './transaction'; import type {TransactionInstructionCtorFields} from './transaction';
@ -66,6 +67,7 @@ export class SystemInstruction extends TransactionInstruction {
if ( if (
this.type == SystemInstructionLayout.Create || this.type == SystemInstructionLayout.Create ||
this.type == SystemInstructionLayout.CreateWithSeed || this.type == SystemInstructionLayout.CreateWithSeed ||
this.type == SystemInstructionLayout.NonceWithdraw ||
this.type == SystemInstructionLayout.Transfer this.type == SystemInstructionLayout.Transfer
) { ) {
return this.keys[0].pubkey; return this.keys[0].pubkey;
@ -81,6 +83,7 @@ export class SystemInstruction extends TransactionInstruction {
if ( if (
this.type == SystemInstructionLayout.Create || this.type == SystemInstructionLayout.Create ||
this.type == SystemInstructionLayout.CreateWithSeed || this.type == SystemInstructionLayout.CreateWithSeed ||
this.type == SystemInstructionLayout.NonceWithdraw ||
this.type == SystemInstructionLayout.Transfer this.type == SystemInstructionLayout.Transfer
) { ) {
return this.keys[1].pubkey; return this.keys[1].pubkey;
@ -94,11 +97,11 @@ export class SystemInstruction extends TransactionInstruction {
*/ */
get amount(): number | null { get amount(): number | null {
const data = this.type.layout.decode(this.data); const data = this.type.layout.decode(this.data);
if (this.type == SystemInstructionLayout.Transfer) { if (
return data.amount;
} else if (
this.type == SystemInstructionLayout.Create || this.type == SystemInstructionLayout.Create ||
this.type == SystemInstructionLayout.CreateWithSeed this.type == SystemInstructionLayout.CreateWithSeed ||
this.type == SystemInstructionLayout.NonceWithdraw ||
this.type == SystemInstructionLayout.Transfer
) { ) {
return data.lamports; return data.lamports;
} }
@ -130,7 +133,7 @@ const SystemInstructionLayout = Object.freeze({
index: 2, index: 2,
layout: BufferLayout.struct([ layout: BufferLayout.struct([
BufferLayout.u32('instruction'), BufferLayout.u32('instruction'),
BufferLayout.ns64('amount'), BufferLayout.ns64('lamports'),
]), ]),
}, },
CreateWithSeed: { CreateWithSeed: {
@ -144,6 +147,31 @@ const SystemInstructionLayout = Object.freeze({
Layout.publicKey('programId'), Layout.publicKey('programId'),
]), ]),
}, },
NonceAdvance: {
index: 4,
layout: BufferLayout.struct([BufferLayout.u32('instruction')]),
},
NonceWithdraw: {
index: 5,
layout: BufferLayout.struct([
BufferLayout.u32('instruction'),
BufferLayout.ns64('lamports'),
]),
},
NonceInitialize: {
index: 6,
layout: BufferLayout.struct([
BufferLayout.u32('instruction'),
Layout.publicKey('authorized'),
]),
},
NonceAuthorize: {
index: 7,
layout: BufferLayout.struct([
BufferLayout.u32('instruction'),
Layout.publicKey('authorized'),
]),
},
}); });
/** /**
@ -159,6 +187,13 @@ export class SystemProgram {
); );
} }
/**
* Max space of a Nonce account
*/
static get nonceSpace(): number {
return 68;
}
/** /**
* Generate a Transaction that creates a new account * Generate a Transaction that creates a new account
*/ */
@ -181,7 +216,7 @@ export class SystemProgram {
{pubkey: from, isSigner: true, isWritable: true}, {pubkey: from, isSigner: true, isWritable: true},
{pubkey: newAccount, isSigner: true, isWritable: true}, {pubkey: newAccount, isSigner: true, isWritable: true},
], ],
programId: SystemProgram.programId, programId: this.programId,
data, data,
}); });
} }
@ -189,16 +224,20 @@ 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,
lamports: number,
): Transaction {
const type = SystemInstructionLayout.Transfer; const type = SystemInstructionLayout.Transfer;
const data = encodeData(type, {amount}); const data = encodeData(type, {lamports});
return new Transaction().add({ return new Transaction().add({
keys: [ keys: [
{pubkey: from, isSigner: true, isWritable: true}, {pubkey: from, isSigner: true, isWritable: true},
{pubkey: to, isSigner: false, isWritable: true}, {pubkey: to, isSigner: false, isWritable: true},
], ],
programId: SystemProgram.programId, programId: this.programId,
data, data,
}); });
} }
@ -212,7 +251,7 @@ export class SystemProgram {
return new Transaction().add({ return new Transaction().add({
keys: [{pubkey: from, isSigner: true, isWritable: true}], keys: [{pubkey: from, isSigner: true, isWritable: true}],
programId: SystemProgram.programId, programId: this.programId,
data, data,
}); });
} }
@ -244,7 +283,126 @@ export class SystemProgram {
{pubkey: from, isSigner: true, isWritable: true}, {pubkey: from, isSigner: true, isWritable: true},
{pubkey: newAccount, isSigner: false, isWritable: true}, {pubkey: newAccount, isSigner: false, isWritable: true},
], ],
programId: SystemProgram.programId, programId: this.programId,
data,
});
}
/**
* 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 = SystemInstructionLayout.NonceInitialize;
const data = encodeData(type, {
authorized: authorizedPubkey.toBuffer(),
});
return transaction.add({
keys: [
{pubkey: nonceAccount, isSigner: false, isWritable: true},
{
pubkey: SYSVAR_RECENT_BLOCKHASHES_PUBKEY,
isSigner: false,
isWritable: false,
},
{pubkey: SYSVAR_RENT_PUBKEY, isSigner: false, isWritable: false},
],
programId: this.programId,
data,
});
}
/**
* Generate an instruction to advance the nonce in a Nonce account
*/
static nonceAdvance(
nonceAccount: PublicKey,
authorizedPubkey: PublicKey,
): TransactionInstruction {
const type = SystemInstructionLayout.NonceAdvance;
const data = encodeData(type);
const instructionData = {
keys: [
{pubkey: nonceAccount, isSigner: false, isWritable: true},
{
pubkey: SYSVAR_RECENT_BLOCKHASHES_PUBKEY,
isSigner: false,
isWritable: false,
},
{pubkey: authorizedPubkey, isSigner: true, isWritable: false},
],
programId: this.programId,
data,
};
return new TransactionInstruction(instructionData);
}
/**
* Generate a Transaction that withdraws lamports from a Nonce account
*/
static nonceWithdraw(
nonceAccount: PublicKey,
authorizedPubkey: PublicKey,
to: PublicKey,
lamports: number,
): Transaction {
const type = SystemInstructionLayout.NonceWithdraw;
const data = encodeData(type, {lamports});
return new Transaction().add({
keys: [
{pubkey: nonceAccount, isSigner: false, isWritable: true},
{pubkey: to, isSigner: false, isWritable: true},
{
pubkey: SYSVAR_RECENT_BLOCKHASHES_PUBKEY,
isSigner: false,
isWritable: false,
},
{
pubkey: SYSVAR_RENT_PUBKEY,
isSigner: false,
isWritable: false,
},
{pubkey: authorizedPubkey, isSigner: true, isWritable: false},
],
programId: this.programId,
data,
});
}
/**
* Generate a Transaction that authorizes a new PublicKey as the authority
* on a Nonce account.
*/
static nonceAuthorize(
nonceAccount: PublicKey,
authorizedPubkey: PublicKey,
newAuthorized: PublicKey,
): Transaction {
const type = SystemInstructionLayout.NonceAuthorize;
const data = encodeData(type, {
newAuthorized: newAuthorized.toBuffer(),
});
return new Transaction().add({
keys: [
{pubkey: nonceAccount, isSigner: false, isWritable: true},
{pubkey: authorizedPubkey, isSigner: true, isWritable: false},
],
programId: this.programId,
data, data,
}); });
} }

View File

@ -5,6 +5,10 @@ export const SYSVAR_CLOCK_PUBKEY = new PublicKey(
'SysvarC1ock11111111111111111111111111111111', 'SysvarC1ock11111111111111111111111111111111',
); );
export const SYSVAR_RECENT_BLOCKHASHES_PUBKEY = new PublicKey(
'SysvarRecentB1ockHashes11111111111111111111',
);
export const SYSVAR_RENT_PUBKEY = new PublicKey( export const SYSVAR_RENT_PUBKEY = new PublicKey(
'SysvarRent111111111111111111111111111111111', 'SysvarRent111111111111111111111111111111111',
); );

View File

@ -70,6 +70,58 @@ test('createAccountWithSeed', () => {
// TODO: Validate transaction contents more // TODO: Validate transaction contents more
}); });
test('createNonceAccount', () => {
const from = new Account();
const nonceAccount = new Account();
const transaction = SystemProgram.createNonceAccount(
from.publicKey,
nonceAccount.publicKey,
from.publicKey,
123,
);
expect(transaction.instructions).toHaveLength(2);
expect(transaction.instructions[0].programId).toEqual(
SystemProgram.programId,
);
expect(transaction.instructions[1].programId).toEqual(SystemProgram.programId);
// TODO: Validate transaction contents more
});
test('nonceWithdraw', () => {
const from = new Account();
const nonceAccount = new Account();
const to = new Account();
const transaction = SystemProgram.nonceWithdraw(
nonceAccount.publicKey,
from.publicKey,
to.publicKey,
123,
);
expect(transaction.keys).toHaveLength(5);
expect(transaction.programId).toEqual(SystemProgram.programId);
// TODO: Validate transaction contents more
});
test('nonceAuthorize', () => {
const nonceAccount = new Account();
const authorized = new Account();
const newAuthorized = new Account();
const transaction = SystemProgram.nonceAuthorize(
nonceAccount.publicKey,
authorized.publicKey,
newAuthorized.publicKey,
);
expect(transaction.keys).toHaveLength(2);
expect(transaction.programId).toEqual(SystemProgram.programId);
// TODO: Validate transaction contents more
});
test('SystemInstruction create', () => { test('SystemInstruction create', () => {
const from = new Account(); const from = new Account();
const to = new Account(); const to = new Account();
@ -149,6 +201,27 @@ test('SystemInstruction createWithSeed', () => {
expect(systemInstruction.programId).toEqual(SystemProgram.programId); expect(systemInstruction.programId).toEqual(SystemProgram.programId);
}); });
test('SystemInstruction nonceWithdraw', () => {
const nonceAccount = new Account();
const authorized = new Account();
const to = new Account();
const amount = 42;
const recentBlockhash = 'EETubP5AKHgjPAhzPAFcb8BAY1hMH639CWCFTqi3hq1k'; // Arbitrary known recentBlockhash
const nonceWithdraw = SystemProgram.nonceWithdraw(
nonceAccount.publicKey,
authorized.publicKey,
to.publicKey,
amount,
);
const transaction = new Transaction({recentBlockhash}).add(nonceWithdraw);
const systemInstruction = SystemInstruction.from(transaction.instructions[0]);
expect(systemInstruction.fromPublicKey).toEqual(nonceAccount.publicKey);
expect(systemInstruction.toPublicKey).toEqual(to.publicKey);
expect(systemInstruction.amount).toEqual(amount);
expect(systemInstruction.programId).toEqual(SystemProgram.programId);
});
test('non-SystemInstruction error', () => { test('non-SystemInstruction error', () => {
const from = new Account(); const from = new Account();
const program = new Account(); const program = new Account();
@ -181,7 +254,7 @@ test('non-SystemInstruction error', () => {
SystemInstruction.from(transaction.instructions[1]); SystemInstruction.from(transaction.instructions[1]);
}).toThrow(); }).toThrow();
transaction.instructions[0].data[0] = 4; transaction.instructions[0].data[0] = 11;
expect(() => { expect(() => {
SystemInstruction.from(transaction.instructions[0]); SystemInstruction.from(transaction.instructions[0]);
}).toThrow(); }).toThrow();