Unleashing the Power of Instruction Chaining with Instruction Introspection!

LE◎
Turbin3 Research
Published in
7 min readDec 30, 2023

This guide is designed as a comprehensive overview of Introspecting Instruction; a technique that, though highly effective, remains underutilised in various Solana applications.

The code for this guide can be found in this repository. It’s advisable to follow along and run the examples locally to get a better grasp of Instruction Introspection.

Application for Instruction Introspection

Before we dive deep into Instruction Introspection, it’s important to understand that this method provides an opportunity to link more instruction in a single transaction while respecting atomicity.

This opened up a range of use case that were not possible before:

  • Expanding Account Size in Instructions: Instruction Introspection enables the creation of supplementary instructions that complement existing ones, using different accounts to extend beyond these limits.
  • Incorporating Instructions from Other Programs: Instruction Introspection opens the door to integrate instructions from other programs with your own, including those written in Typescript. This approach not only maintains atomicity, but also paves the way for optimizing space usage and reducing deployment costs by reusing existing instructions, whether they’re part of our program or native to Solana.

An Introduction to Instruction Introspection

The core concept of Instruction Introspection is to analyze a determined instruction happening in the same transaction of the one being constructed.

Transaction

In our exploration of the concept, it’s useful to have a basic understanding of transactions in the Solana ecosystem. We won’t dive into all the technical details, but a high-level view is essential.

Transactions are “containers” that can hold multiple instructions. During execution, the Solana runtime processes each of these instructions in the order they appear, and it does so atomically. This means the instructions within a transaction are executed as a cohesive unit.

The atomic nature of transactions is a critical aspect. If any single instruction within a transaction encounters an issue or fails, the entire transaction is deemed unsuccessful. This feature ensures that operations on the blockchain are executed consistently and reliably, maintaining the integrity of the transactional process

Sysvar Accounts:

Sysvars play a pivotal role in this concept, and they are specialized accounts that hold dynamically updated information about the network cluster, blockchain history, and the executing transaction.

In the context of our program, we will primarily utilise two functions from the Sysvar accounts:

  1. load_current_index_checked: This function retrieves the current index of the instruction in the transaction being executed.
  2. load_instruction_at_checked: This function fetches a specific instruction in the currently executing transaction at a designated index.

Instruction

Instruction delineates the target program, the accounts it may access or modify, and supplementary data that acts as input for the program.

Note: Data & Accounts are arranged in the same sequence as they were during the construction of the instruction.

The function ‘load_instruction_at_checked’ from Sysvar offers a deserialized version of a determined instruction, which includes:

  • Program_Id: This is the identifier of the program to which the instruction is directed.
  • Accounts: A vector detailing the accounts involved in the instruction.
  • Data: This includes the discriminator followed by variables.

How to Use Instruction Introspection

  1. Identify the Current Instruction Index: Start by determining the current index of the instruction being executed. Note that this instruction might not always be in the first position (0) of the examined transaction.
  2. Load the Target Instruction: After identifying the current instruction, load the subsequent instruction or the specific instruction you wish to analyze. This step is crucial for verifying the correctness of the input for the intended operation.
  3. Develop Constraint to Mitigate the Threat of a Malicious Attack: Considering Potential Security Threats and Reflecting on possible malicious attacks that could target this operation, developing constraints to mitigate these threats is a vital part of the process.

The constraint usually used are:

  • Instruction Verification: To ensure the integrity of the instruction, verify its discriminator & the Program ID. The discriminator, typically the first 1, 4, or 8 bytes of the transaction data, varies depending on the source. This check confirms that the instruction being executed is indeed the correct one.
  • Variable Validation: Identify and validate a key variable essential to the instruction’s intended function. These variables, passed as data following the discriminator, must be scrutinized to guarantee they align with the transaction’s purpose.
  • Account Verification: Similarly, ensure the accounts involved in the transaction are correct and relevant. Just as with the variables, these accounts are an integral part of the transaction and require careful examination to maintain the transaction’s integrity and security.

Live Example: Escrow using Instruction Introspection

In this section, we’ll explore a practical application of Instruction Introspection through the development of an Escrow Program using Anchor.
Our focus will be on a scenario involving a maker and a taker. The maker offers X amount of token Y in exchange for Z amount of token K, and this offer can be taken up by any taker who agrees to these conditions.

It’s important to note that while the creation of the ‘make’ instruction is a crucial part of this process, it will be covered in detail in a separate paper dedicated to escrow mechanisms. Here, our attention is centered on three distinct ‘take’ instructions that exemplify the versatility and power of Instruction Introspection in practical applications.

A “Normal” Take Instruction

Taken from https://github.com/deanmlittle/anchor-escrow-2024 lib.rs

In the context of an Escrow program, a normal ‘Take’ instruction has these key actions:

  1. Deposit: This involves the taker depositing the agreed amount from their associated token account (ATA) into the maker’s ATA.
  2. Withdraw: Following the deposit, the amount held in escrow is transferred to the taker’s ATA.
  3. Close Vault: Finally, the vault ATA is closed, as it’s no longer required.

When designing these instructions, it’s crucial to contemplate the constraints that will ensure a satisfactory outcome for all parties involved.

  • Adhering to the amount requested by the maker
  • Ensuring these funds are directed to the correct ATA, as this automatically verifies the appropriateness of the token and the intended recipient.

The other accounts involved are less critical in this context, given that the taker, who initiates the transaction, is responsible for the funds. Should the taker wish to utilize funds from another wallet, they would need to have control over both wallets.

Note: In our examples of Instruction Introspection, the ‘Deposit’ and ‘Close Vault’ instructions will be the ones undergoing modification to demonstrate the versatility and application of this technique.

Take Instruction Using another Instruction from the current Program

To identify our program, we simply use the ID variable. This straightforward method ensures we are referencing the correct program context.

To obtain the Discriminator of a specific instruction within our program, which is 8 bytes long, we call instruction::[instruction_in_program]::DISCRIMINATOR. Remember, to utilize this Discriminator variable effectively, we need to include the import statement anchor_lang::Discriminator in our code.

After the Discriminator, the next 8 bytes represent the amount, as it’s defined as a u64.

Finally, a key part of this process involves identifying the position where the maker_ata account is referenced in the instruction. Once we determine this, we can compare it to the maker_ata we've established on our end.

Take Instruction Using the Transfer_Spl Instruction

This time we need to provide the token_program key, which confirms that we are working within the correct program context.

The discriminator of the transfer_spl is only 1 byte long and is represented by the number 3, which should be passed as a u8
Note: iIf you’re unsure about the size of the instruction or its Discriminator, you can execute the instruction and then view the raw transaction data on a blockchain explorer (such as Solana Explorer) to get these details.

Similar to our previous example, the amount in this instruction follows the Discriminator, occupying the subsequent 8 bytes.

The account arrangement for the transfer_spl is structured with the ‘from’ account is designated as account(0), the 'to' account is account(1) and the ‘authority’ account is account(2) . Threfore, in our introspection, we specifically verify the account(1) to ensure it matches the correct ATA.

Take Instruction Using the System_program Transfer Instruction

In case the maker wanted a payment in sol, we could just use the simple transfer directly from the system_program.

As with our previous examples, the first step involves verifying the system_program key. This ensures that the instruction is indeed interacting with the correct system program. Following this, we examine the transaction to identify the correct amount, which, as usual, comes right after the Discriminator

However, there are a couple of notable adjustments in this case:

  1. Verification Against the ‘Maker’ Key: Since we’re dealing with a Lamport transfer, the check is conducted against the ‘maker’ key rather than the ATA.
  2. Discriminator Details: For this particular transfer, the Discriminator is 4 bytes in length, and in this instance, it is represented by the number 2 (that always should be passed as a u8)

Congratulations! You have now written your first Instruction Introspection! I hope this tutorial has been helpful and that you learned something insightful!

A special thanks to Dean that taught me this concept (you can find his example HERE) and to the Web3 Builder Alliance Institute that always teaches mecutting edge concept on Solana.

If you want to follow my journey and you don’t want to miss out my next tutorial, follow me on Twitter!

--

--