feat: add Nonce instructions to system program
This commit is contained in:
committed by
Michael Vines
parent
05491f7fb7
commit
c9cc44ae4f
@ -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,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -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',
|
||||||
);
|
);
|
||||||
|
@ -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();
|
||||||
|
Reference in New Issue
Block a user