Skip to content

Commit

Permalink
feat: implement message decompilation
Browse files Browse the repository at this point in the history
  • Loading branch information
jstarry committed Aug 26, 2022
1 parent 25d2566 commit 5b326b0
Show file tree
Hide file tree
Showing 2 changed files with 181 additions and 0 deletions.
1 change: 1 addition & 0 deletions web3.js/src/transaction/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from './constants';
export * from './expiry-custom-errors';
export * from './legacy';
export * from './message';
export * from './versioned';
180 changes: 180 additions & 0 deletions web3.js/src/transaction/message.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
import {LoadedAddresses} from '../connection';
import {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 {PublicKey} from '../publickey';
import {AccountMeta, TransactionInstruction} from './legacy';

export type TransactionMessageArgs = {
payerKey: PublicKey;
instructions: Array<TransactionInstruction>;
recentBlockhash: Blockhash;
};

export type DecompileArgs =
| {
message: VersionedMessage;
}
| {
message: MessageV0;
loadedAddresses: LoadedAddresses;
}
| {
message: MessageV0;
addressLookupTableAccounts: AddressLookupTableAccount[];
};

export class TransactionMessage {
payerKey: PublicKey;
instructions: Array<TransactionInstruction>;
recentBlockhash: Blockhash;

constructor(args: TransactionMessageArgs) {
this.payerKey = args.payerKey;
this.instructions = args.instructions;
this.recentBlockhash = args.recentBlockhash;
}

static decompile(args: DecompileArgs): TransactionMessage {
const message = args.message;

let loadedAddresses: LoadedAddresses | undefined;
if ('loadedAddresses' in args) {
loadedAddresses = args.loadedAddresses;
} else if ('addressLookupTableAccounts' in args) {
loadedAddresses = {
writable: [],
readonly: [],
};

for (const tableLookup of message.addressTableLookups) {
const tableAccount = args.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) {
const address = tableAccount.state.addresses.at(index);
if (address === undefined) {
throw new Error(
`Failed to find address for index ${index} in address lookup table ${tableLookup.accountKey.toBase58()}`,
);
}
loadedAddresses.writable.push(address);
}

for (const index of tableLookup.readonlyIndexes) {
const address = tableAccount.state.addresses.at(index);
if (address === undefined) {
throw new Error(
`Failed to find address for index ${index} in address lookup table ${tableLookup.accountKey.toBase58()}`,
);
}
loadedAddresses.readonly.push(address);
}
}
}

const accountKeys = new MessageAccountKeys(
message.staticAccountKeys,
loadedAddresses,
);
const {header, compiledInstructions, recentBlockhash} = message;

const {
numRequiredSignatures,
numReadonlySignedAccounts,
numReadonlyUnsignedAccounts,
} = header;

const numWritableSignedAccounts =
numRequiredSignatures - numReadonlySignedAccounts;
assert(numWritableSignedAccounts > 0, 'Message header is invalid');

const numWritableUnsignedAccounts =
accountKeys.staticAccountKeys.length - numReadonlyUnsignedAccounts;
assert(numWritableUnsignedAccounts >= 0, 'Message header is invalid');

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 <
// loadedAddresses cannot be undefined because we already found a pubkey for this index above
accountKeys.loadedAddresses!.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({
payerKey: accountKeys.staticAccountKeys[0],
instructions,
recentBlockhash,
});
}

compileToLegacyMessage(): Message {
return Message.compile({
payerKey: this.payerKey,
recentBlockhash: this.recentBlockhash,
instructions: this.instructions,
});
}

compileToV0Message(
addressLookupTableAccounts?: AddressLookupTableAccount[],
): MessageV0 {
return MessageV0.compile({
payerKey: this.payerKey,
recentBlockhash: this.recentBlockhash,
instructions: this.instructions,
addressLookupTableAccounts,
});
}
}

0 comments on commit 5b326b0

Please sign in to comment.