feat: add `TransactionMessage` class (#27526)
feat: implement message v0 decompilation
This commit is contained in:
parent
3374f41201
commit
9f81d27db8
|
@ -13,6 +13,9 @@ import {
|
|||
MessageAddressTableLookup,
|
||||
MessageCompiledInstruction,
|
||||
} from './index';
|
||||
import {TransactionInstruction} from '../transaction';
|
||||
import {CompiledKeys} from './compiled-keys';
|
||||
import {MessageAccountKeys} from './account-keys';
|
||||
|
||||
/**
|
||||
* An instruction to execute by a program
|
||||
|
@ -37,13 +40,19 @@ export type MessageArgs = {
|
|||
/** The message header, identifying signed and read-only `accountKeys` */
|
||||
header: MessageHeader;
|
||||
/** All the account keys used by this transaction */
|
||||
accountKeys: string[];
|
||||
accountKeys: string[] | PublicKey[];
|
||||
/** The hash of a recent ledger block */
|
||||
recentBlockhash: Blockhash;
|
||||
/** Instructions that will be executed in sequence and committed in one atomic transaction if all succeed. */
|
||||
instructions: CompiledInstruction[];
|
||||
};
|
||||
|
||||
export type CompileLegacyArgs = {
|
||||
payerKey: PublicKey;
|
||||
instructions: Array<TransactionInstruction>;
|
||||
recentBlockhash: Blockhash;
|
||||
};
|
||||
|
||||
/**
|
||||
* List of instructions to be processed atomically
|
||||
*/
|
||||
|
@ -93,6 +102,29 @@ export class Message {
|
|||
return [];
|
||||
}
|
||||
|
||||
getAccountKeys(): MessageAccountKeys {
|
||||
return new MessageAccountKeys(this.staticAccountKeys);
|
||||
}
|
||||
|
||||
static compile(args: CompileLegacyArgs): Message {
|
||||
const compiledKeys = CompiledKeys.compile(args.instructions, args.payerKey);
|
||||
const [header, staticAccountKeys] = compiledKeys.getMessageComponents();
|
||||
const accountKeys = new MessageAccountKeys(staticAccountKeys);
|
||||
const instructions = accountKeys.compileInstructions(args.instructions).map(
|
||||
(ix: MessageCompiledInstruction): CompiledInstruction => ({
|
||||
programIdIndex: ix.programIdIndex,
|
||||
accounts: ix.accountKeyIndexes,
|
||||
data: bs58.encode(ix.data),
|
||||
}),
|
||||
);
|
||||
return new Message({
|
||||
header,
|
||||
accountKeys: staticAccountKeys,
|
||||
recentBlockhash: args.recentBlockhash,
|
||||
instructions,
|
||||
});
|
||||
}
|
||||
|
||||
isAccountSigner(index: number): boolean {
|
||||
return index < this.header.numRequiredSignatures;
|
||||
}
|
||||
|
@ -250,7 +282,7 @@ export class Message {
|
|||
for (let i = 0; i < accountCount; i++) {
|
||||
const account = byteArray.slice(0, PUBLIC_KEY_LENGTH);
|
||||
byteArray = byteArray.slice(PUBLIC_KEY_LENGTH);
|
||||
accountKeys.push(bs58.encode(Buffer.from(account)));
|
||||
accountKeys.push(new PublicKey(Buffer.from(account)));
|
||||
}
|
||||
|
||||
const recentBlockhash = byteArray.slice(0, PUBLIC_KEY_LENGTH);
|
||||
|
|
|
@ -40,6 +40,14 @@ export type CompileV0Args = {
|
|||
addressLookupTableAccounts?: Array<AddressLookupTableAccount>;
|
||||
};
|
||||
|
||||
export type GetAccountKeysArgs =
|
||||
| {
|
||||
accountKeysFromLookups: AccountKeysFromLookups;
|
||||
}
|
||||
| {
|
||||
addressLookupTableAccounts: AddressLookupTableAccount[];
|
||||
};
|
||||
|
||||
export class MessageV0 {
|
||||
header: MessageHeader;
|
||||
staticAccountKeys: Array<PublicKey>;
|
||||
|
@ -59,6 +67,88 @@ export class MessageV0 {
|
|||
return 0;
|
||||
}
|
||||
|
||||
get numAccountKeysFromLookups(): number {
|
||||
let count = 0;
|
||||
for (const lookup of this.addressTableLookups) {
|
||||
count += lookup.readonlyIndexes.length + lookup.writableIndexes.length;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
getAccountKeys(args?: GetAccountKeysArgs): MessageAccountKeys {
|
||||
let accountKeysFromLookups: AccountKeysFromLookups | undefined;
|
||||
if (args && 'accountKeysFromLookups' in args) {
|
||||
if (
|
||||
this.numAccountKeysFromLookups !=
|
||||
args.accountKeysFromLookups.writable.length +
|
||||
args.accountKeysFromLookups.readonly.length
|
||||
) {
|
||||
throw new Error(
|
||||
'Failed to get account keys because of a mismatch in the number of account keys from lookups',
|
||||
);
|
||||
}
|
||||
accountKeysFromLookups = args.accountKeysFromLookups;
|
||||
} else if (args && 'addressLookupTableAccounts' in args) {
|
||||
accountKeysFromLookups = this.resolveAddressTableLookups(
|
||||
args.addressLookupTableAccounts,
|
||||
);
|
||||
} else if (this.addressTableLookups.length > 0) {
|
||||
throw new Error(
|
||||
'Failed to get account keys because address table lookups were not resolved',
|
||||
);
|
||||
}
|
||||
return new MessageAccountKeys(
|
||||
this.staticAccountKeys,
|
||||
accountKeysFromLookups,
|
||||
);
|
||||
}
|
||||
|
||||
resolveAddressTableLookups(
|
||||
addressLookupTableAccounts: AddressLookupTableAccount[],
|
||||
): AccountKeysFromLookups {
|
||||
const accountKeysFromLookups: AccountKeysFromLookups = {
|
||||
writable: [],
|
||||
readonly: [],
|
||||
};
|
||||
|
||||
for (const tableLookup of this.addressTableLookups) {
|
||||
const tableAccount = addressLookupTableAccounts.find(account =>
|
||||
account.key.equals(tableLookup.accountKey),
|
||||
);
|
||||
if (!tableAccount) {
|
||||
throw new Error(
|
||||
`Failed to find address lookup table account for table key ${tableLookup.accountKey.toBase58()}`,
|
||||
);
|
||||
}
|
||||
|
||||
for (const index of tableLookup.writableIndexes) {
|
||||
if (index < tableAccount.state.addresses.length) {
|
||||
accountKeysFromLookups.writable.push(
|
||||
tableAccount.state.addresses[index],
|
||||
);
|
||||
} else {
|
||||
throw new Error(
|
||||
`Failed to find address for index ${index} in address lookup table ${tableLookup.accountKey.toBase58()}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
for (const index of tableLookup.readonlyIndexes) {
|
||||
if (index < tableAccount.state.addresses.length) {
|
||||
accountKeysFromLookups.readonly.push(
|
||||
tableAccount.state.addresses[index],
|
||||
);
|
||||
} else {
|
||||
throw new Error(
|
||||
`Failed to find address for index ${index} in address lookup table ${tableLookup.accountKey.toBase58()}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return accountKeysFromLookups;
|
||||
}
|
||||
|
||||
static compile(args: CompileV0Args): MessageV0 {
|
||||
const compiledKeys = CompiledKeys.compile(args.instructions, args.payerKey);
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
export * from './constants';
|
||||
export * from './expiry-custom-errors';
|
||||
export * from './legacy';
|
||||
export * from './message';
|
||||
export * from './versioned';
|
||||
|
|
|
@ -0,0 +1,147 @@
|
|||
import {
|
||||
AccountKeysFromLookups,
|
||||
MessageAccountKeys,
|
||||
} from '../message/account-keys';
|
||||
import assert from '../utils/assert';
|
||||
import {toBuffer} from '../utils/to-buffer';
|
||||
import {Blockhash} from '../blockhash';
|
||||
import {Message, MessageV0, VersionedMessage} from '../message';
|
||||
import {AddressLookupTableAccount} from '../programs';
|
||||
import {AccountMeta, TransactionInstruction} from './legacy';
|
||||
|
||||
export type TransactionMessageArgs = {
|
||||
accountKeys: MessageAccountKeys;
|
||||
instructions: Array<TransactionInstruction>;
|
||||
recentBlockhash: Blockhash;
|
||||
};
|
||||
|
||||
export type DecompileArgs =
|
||||
| {
|
||||
accountKeysFromLookups: AccountKeysFromLookups;
|
||||
}
|
||||
| {
|
||||
addressLookupTableAccounts: AddressLookupTableAccount[];
|
||||
};
|
||||
|
||||
export class TransactionMessage {
|
||||
accountKeys: MessageAccountKeys;
|
||||
instructions: Array<TransactionInstruction>;
|
||||
recentBlockhash: Blockhash;
|
||||
|
||||
constructor(args: TransactionMessageArgs) {
|
||||
this.accountKeys = args.accountKeys;
|
||||
this.instructions = args.instructions;
|
||||
this.recentBlockhash = args.recentBlockhash;
|
||||
}
|
||||
|
||||
static decompile(
|
||||
message: VersionedMessage,
|
||||
args?: DecompileArgs,
|
||||
): TransactionMessage {
|
||||
const {header, compiledInstructions, recentBlockhash} = message;
|
||||
|
||||
const {
|
||||
numRequiredSignatures,
|
||||
numReadonlySignedAccounts,
|
||||
numReadonlyUnsignedAccounts,
|
||||
} = header;
|
||||
|
||||
const numWritableSignedAccounts =
|
||||
numRequiredSignatures - numReadonlySignedAccounts;
|
||||
assert(numWritableSignedAccounts > 0, 'Message header is invalid');
|
||||
|
||||
const numWritableUnsignedAccounts =
|
||||
message.staticAccountKeys.length - numReadonlyUnsignedAccounts;
|
||||
assert(numWritableUnsignedAccounts >= 0, 'Message header is invalid');
|
||||
|
||||
const accountKeys = message.getAccountKeys(args);
|
||||
const instructions: TransactionInstruction[] = [];
|
||||
for (const compiledIx of compiledInstructions) {
|
||||
const keys: AccountMeta[] = [];
|
||||
|
||||
for (const keyIndex of compiledIx.accountKeyIndexes) {
|
||||
const pubkey = accountKeys.get(keyIndex);
|
||||
if (pubkey === undefined) {
|
||||
throw new Error(
|
||||
`Failed to find key for account key index ${keyIndex}`,
|
||||
);
|
||||
}
|
||||
|
||||
const isSigner = keyIndex < numRequiredSignatures;
|
||||
|
||||
let isWritable;
|
||||
if (isSigner) {
|
||||
isWritable = keyIndex < numWritableSignedAccounts;
|
||||
} else if (keyIndex < accountKeys.staticAccountKeys.length) {
|
||||
isWritable =
|
||||
keyIndex - numRequiredSignatures < numWritableUnsignedAccounts;
|
||||
} else {
|
||||
isWritable =
|
||||
keyIndex - accountKeys.staticAccountKeys.length <
|
||||
// accountKeysFromLookups cannot be undefined because we already found a pubkey for this index above
|
||||
accountKeys.accountKeysFromLookups!.writable.length;
|
||||
}
|
||||
|
||||
keys.push({
|
||||
pubkey,
|
||||
isSigner: keyIndex < header.numRequiredSignatures,
|
||||
isWritable,
|
||||
});
|
||||
}
|
||||
|
||||
const programId = accountKeys.get(compiledIx.programIdIndex);
|
||||
if (programId === undefined) {
|
||||
throw new Error(
|
||||
`Failed to find program id for program id index ${compiledIx.programIdIndex}`,
|
||||
);
|
||||
}
|
||||
|
||||
instructions.push(
|
||||
new TransactionInstruction({
|
||||
programId,
|
||||
data: toBuffer(compiledIx.data),
|
||||
keys,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
return new TransactionMessage({
|
||||
accountKeys,
|
||||
instructions,
|
||||
recentBlockhash,
|
||||
});
|
||||
}
|
||||
|
||||
compileToLegacyMessage(): Message {
|
||||
const payerKey = this.accountKeys.get(0);
|
||||
if (payerKey === undefined) {
|
||||
throw new Error(
|
||||
'Failed to compile message because no account keys were found',
|
||||
);
|
||||
}
|
||||
|
||||
return Message.compile({
|
||||
payerKey,
|
||||
recentBlockhash: this.recentBlockhash,
|
||||
instructions: this.instructions,
|
||||
});
|
||||
}
|
||||
|
||||
compileToV0Message(
|
||||
addressLookupTableAccounts?: AddressLookupTableAccount[],
|
||||
): MessageV0 {
|
||||
const payerKey = this.accountKeys.get(0);
|
||||
if (payerKey === undefined) {
|
||||
throw new Error(
|
||||
'Failed to compile message because no account keys were found',
|
||||
);
|
||||
}
|
||||
|
||||
return MessageV0.compile({
|
||||
payerKey,
|
||||
recentBlockhash: this.recentBlockhash,
|
||||
instructions: this.instructions,
|
||||
addressLookupTableAccounts,
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,91 @@
|
|||
import bs58 from 'bs58';
|
||||
import {expect} from 'chai';
|
||||
import {sha256} from '@noble/hashes/sha256';
|
||||
|
||||
import {Message} from '../../src/message';
|
||||
import {TransactionInstruction} from '../../src/transaction';
|
||||
import {PublicKey} from '../../src/publickey';
|
||||
|
||||
function createTestKeys(count: number): Array<PublicKey> {
|
||||
return new Array(count).fill(0).map(() => PublicKey.unique());
|
||||
}
|
||||
|
||||
describe('Message', () => {
|
||||
it('compile', () => {
|
||||
const keys = createTestKeys(5);
|
||||
const recentBlockhash = bs58.encode(sha256('test'));
|
||||
const payerKey = keys[0];
|
||||
const instructions = [
|
||||
new TransactionInstruction({
|
||||
programId: keys[4],
|
||||
keys: [
|
||||
{pubkey: keys[1], isSigner: true, isWritable: true},
|
||||
{pubkey: keys[2], isSigner: false, isWritable: false},
|
||||
{pubkey: keys[3], isSigner: false, isWritable: false},
|
||||
],
|
||||
data: Buffer.alloc(1),
|
||||
}),
|
||||
new TransactionInstruction({
|
||||
programId: keys[1],
|
||||
keys: [
|
||||
{pubkey: keys[2], isSigner: true, isWritable: false},
|
||||
{pubkey: keys[3], isSigner: false, isWritable: true},
|
||||
],
|
||||
data: Buffer.alloc(2),
|
||||
}),
|
||||
];
|
||||
|
||||
const message = Message.compile({
|
||||
payerKey,
|
||||
recentBlockhash,
|
||||
instructions,
|
||||
});
|
||||
|
||||
expect(message.accountKeys).to.eql([
|
||||
payerKey, // payer is first
|
||||
keys[1], // other writable signer
|
||||
keys[2], // sole readonly signer
|
||||
keys[3], // sole writable non-signer
|
||||
keys[4], // sole readonly non-signer
|
||||
]);
|
||||
expect(message.header).to.eql({
|
||||
numRequiredSignatures: 3,
|
||||
numReadonlySignedAccounts: 1,
|
||||
numReadonlyUnsignedAccounts: 1,
|
||||
});
|
||||
expect(message.addressTableLookups.length).to.eq(0);
|
||||
expect(message.instructions).to.eql([
|
||||
{
|
||||
programIdIndex: 4,
|
||||
accounts: [1, 2, 3],
|
||||
data: bs58.encode(Buffer.alloc(1)),
|
||||
},
|
||||
{
|
||||
programIdIndex: 1,
|
||||
accounts: [2, 3],
|
||||
data: bs58.encode(Buffer.alloc(2)),
|
||||
},
|
||||
]);
|
||||
expect(message.recentBlockhash).to.eq(recentBlockhash);
|
||||
});
|
||||
|
||||
it('compile without instructions', () => {
|
||||
const payerKey = PublicKey.unique();
|
||||
const recentBlockhash = bs58.encode(sha256('test'));
|
||||
const message = Message.compile({
|
||||
payerKey,
|
||||
instructions: [],
|
||||
recentBlockhash,
|
||||
});
|
||||
|
||||
expect(message.accountKeys).to.eql([payerKey]);
|
||||
expect(message.header).to.eql({
|
||||
numRequiredSignatures: 1,
|
||||
numReadonlySignedAccounts: 0,
|
||||
numReadonlyUnsignedAccounts: 0,
|
||||
});
|
||||
expect(message.addressTableLookups.length).to.eq(0);
|
||||
expect(message.instructions.length).to.eq(0);
|
||||
expect(message.recentBlockhash).to.eq(recentBlockhash);
|
||||
});
|
||||
});
|
|
@ -2,7 +2,11 @@ import bs58 from 'bs58';
|
|||
import {expect} from 'chai';
|
||||
import {sha256} from '@noble/hashes/sha256';
|
||||
|
||||
import {MessageV0} from '../../src/message';
|
||||
import {
|
||||
MessageAccountKeys,
|
||||
MessageAddressTableLookup,
|
||||
MessageV0,
|
||||
} from '../../src/message';
|
||||
import {TransactionInstruction} from '../../src/transaction';
|
||||
import {PublicKey} from '../../src/publickey';
|
||||
import {AddressLookupTableAccount} from '../../src/programs';
|
||||
|
@ -28,6 +32,142 @@ function createTestLookupTable(
|
|||
}
|
||||
|
||||
describe('MessageV0', () => {
|
||||
it('numAccountKeysFromLookups', () => {
|
||||
const message = MessageV0.compile({
|
||||
payerKey: PublicKey.unique(),
|
||||
recentBlockhash: '',
|
||||
instructions: [],
|
||||
});
|
||||
expect(message.numAccountKeysFromLookups).to.eq(0);
|
||||
|
||||
message.addressTableLookups = [
|
||||
{
|
||||
accountKey: PublicKey.unique(),
|
||||
writableIndexes: [0],
|
||||
readonlyIndexes: [1],
|
||||
},
|
||||
{
|
||||
accountKey: PublicKey.unique(),
|
||||
writableIndexes: [0, 2],
|
||||
readonlyIndexes: [],
|
||||
},
|
||||
];
|
||||
expect(message.numAccountKeysFromLookups).to.eq(4);
|
||||
});
|
||||
|
||||
it('getAccountKeys', () => {
|
||||
const staticAccountKeys = createTestKeys(3);
|
||||
const lookupTable = createTestLookupTable(createTestKeys(2));
|
||||
const message = new MessageV0({
|
||||
header: {
|
||||
numRequiredSignatures: 1,
|
||||
numReadonlySignedAccounts: 0,
|
||||
numReadonlyUnsignedAccounts: 0,
|
||||
},
|
||||
recentBlockhash: 'test',
|
||||
staticAccountKeys,
|
||||
compiledInstructions: [],
|
||||
addressTableLookups: [
|
||||
{
|
||||
accountKey: lookupTable.key,
|
||||
writableIndexes: [0],
|
||||
readonlyIndexes: [1],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
expect(() => message.getAccountKeys()).to.throw(
|
||||
'Failed to get account keys because address table lookups were not resolved',
|
||||
);
|
||||
expect(() =>
|
||||
message.getAccountKeys({
|
||||
accountKeysFromLookups: {writable: [PublicKey.unique()], readonly: []},
|
||||
}),
|
||||
).to.throw(
|
||||
'Failed to get account keys because of a mismatch in the number of account keys from lookups',
|
||||
);
|
||||
|
||||
const accountKeysFromLookups = message.resolveAddressTableLookups([
|
||||
lookupTable,
|
||||
]);
|
||||
const expectedAccountKeys = new MessageAccountKeys(
|
||||
staticAccountKeys,
|
||||
accountKeysFromLookups,
|
||||
);
|
||||
|
||||
expect(
|
||||
message.getAccountKeys({
|
||||
accountKeysFromLookups,
|
||||
}),
|
||||
).to.eql(expectedAccountKeys);
|
||||
|
||||
expect(
|
||||
message.getAccountKeys({
|
||||
addressLookupTableAccounts: [lookupTable],
|
||||
}),
|
||||
).to.eql(expectedAccountKeys);
|
||||
});
|
||||
|
||||
it('resolveAddressTableLookups', () => {
|
||||
const keys = createTestKeys(7);
|
||||
const lookupTable = createTestLookupTable(keys);
|
||||
const createTestMessage = (
|
||||
addressTableLookups: MessageAddressTableLookup[],
|
||||
): MessageV0 => {
|
||||
return new MessageV0({
|
||||
header: {
|
||||
numRequiredSignatures: 1,
|
||||
numReadonlySignedAccounts: 0,
|
||||
numReadonlyUnsignedAccounts: 0,
|
||||
},
|
||||
recentBlockhash: 'test',
|
||||
staticAccountKeys: [],
|
||||
compiledInstructions: [],
|
||||
addressTableLookups,
|
||||
});
|
||||
};
|
||||
|
||||
expect(
|
||||
createTestMessage([]).resolveAddressTableLookups([lookupTable]),
|
||||
).to.eql({
|
||||
writable: [],
|
||||
readonly: [],
|
||||
});
|
||||
|
||||
expect(() =>
|
||||
createTestMessage([
|
||||
{
|
||||
accountKey: PublicKey.unique(),
|
||||
writableIndexes: [1, 3, 5],
|
||||
readonlyIndexes: [0, 2, 4],
|
||||
},
|
||||
]).resolveAddressTableLookups([lookupTable]),
|
||||
).to.throw('Failed to find address lookup table account for table key');
|
||||
|
||||
expect(() =>
|
||||
createTestMessage([
|
||||
{
|
||||
accountKey: lookupTable.key,
|
||||
writableIndexes: [10],
|
||||
readonlyIndexes: [],
|
||||
},
|
||||
]).resolveAddressTableLookups([lookupTable]),
|
||||
).to.throw('Failed to find address for index');
|
||||
|
||||
expect(
|
||||
createTestMessage([
|
||||
{
|
||||
accountKey: lookupTable.key,
|
||||
writableIndexes: [1, 3, 5],
|
||||
readonlyIndexes: [0, 2, 4],
|
||||
},
|
||||
]).resolveAddressTableLookups([lookupTable]),
|
||||
).to.eql({
|
||||
writable: [keys[1], keys[3], keys[5]],
|
||||
readonly: [keys[0], keys[2], keys[4]],
|
||||
});
|
||||
});
|
||||
|
||||
it('compile', () => {
|
||||
const keys = createTestKeys(7);
|
||||
const recentBlockhash = bs58.encode(sha256('test'));
|
||||
|
|
|
@ -0,0 +1,89 @@
|
|||
import bs58 from 'bs58';
|
||||
import {expect} from 'chai';
|
||||
import {sha256} from '@noble/hashes/sha256';
|
||||
|
||||
import {
|
||||
TransactionInstruction,
|
||||
TransactionMessage,
|
||||
} from '../../src/transaction';
|
||||
import {PublicKey} from '../../src/publickey';
|
||||
import {AddressLookupTableAccount} from '../../src/programs';
|
||||
import {MessageV0} from '../../src/message';
|
||||
|
||||
function createTestKeys(count: number): Array<PublicKey> {
|
||||
return new Array(count).fill(0).map(() => PublicKey.unique());
|
||||
}
|
||||
|
||||
function createTestLookupTable(
|
||||
addresses: Array<PublicKey>,
|
||||
): AddressLookupTableAccount {
|
||||
const U64_MAX = 2n ** 64n - 1n;
|
||||
return new AddressLookupTableAccount({
|
||||
key: PublicKey.unique(),
|
||||
state: {
|
||||
lastExtendedSlot: 0,
|
||||
lastExtendedSlotStartIndex: 0,
|
||||
deactivationSlot: U64_MAX,
|
||||
authority: PublicKey.unique(),
|
||||
addresses,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
describe('TransactionMessage', () => {
|
||||
it('decompile', () => {
|
||||
const keys = createTestKeys(7);
|
||||
const recentBlockhash = bs58.encode(sha256('test'));
|
||||
const payerKey = keys[0];
|
||||
const instructions = [
|
||||
new TransactionInstruction({
|
||||
programId: keys[4],
|
||||
keys: [
|
||||
{pubkey: keys[1], isSigner: true, isWritable: true},
|
||||
{pubkey: keys[2], isSigner: true, isWritable: false},
|
||||
{pubkey: keys[3], isSigner: false, isWritable: true},
|
||||
{pubkey: keys[5], isSigner: false, isWritable: true},
|
||||
{pubkey: keys[6], isSigner: false, isWritable: false},
|
||||
],
|
||||
data: Buffer.alloc(1),
|
||||
}),
|
||||
new TransactionInstruction({
|
||||
programId: keys[1],
|
||||
keys: [],
|
||||
data: Buffer.alloc(2),
|
||||
}),
|
||||
new TransactionInstruction({
|
||||
programId: keys[3],
|
||||
keys: [],
|
||||
data: Buffer.alloc(3),
|
||||
}),
|
||||
];
|
||||
|
||||
const addressLookupTableAccounts = [createTestLookupTable(keys)];
|
||||
const message = MessageV0.compile({
|
||||
payerKey,
|
||||
recentBlockhash,
|
||||
instructions,
|
||||
addressLookupTableAccounts,
|
||||
});
|
||||
|
||||
expect(() => TransactionMessage.decompile(message)).to.throw(
|
||||
'Failed to get account keys because address table lookups were not resolved',
|
||||
);
|
||||
|
||||
const accountKeys = message.getAccountKeys({addressLookupTableAccounts});
|
||||
const decompiledMessage = TransactionMessage.decompile(message, {
|
||||
addressLookupTableAccounts,
|
||||
});
|
||||
|
||||
expect(decompiledMessage.accountKeys).to.eql(accountKeys);
|
||||
expect(decompiledMessage.recentBlockhash).to.eq(recentBlockhash);
|
||||
expect(decompiledMessage.instructions).to.eql(instructions);
|
||||
|
||||
expect(decompiledMessage).to.eql(
|
||||
TransactionMessage.decompile(message, {
|
||||
accountKeysFromLookups: accountKeys.accountKeysFromLookups!,
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue