Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Transaction failed to sanitize accounts offsets correctly #199

Open
lucasoares opened this issue Apr 23, 2024 · 1 comment
Open

Transaction failed to sanitize accounts offsets correctly #199

lucasoares opened this issue Apr 23, 2024 · 1 comment

Comments

@lucasoares
Copy link

lucasoares commented Apr 23, 2024

Maybe it's related to #134, but idk if it is the same issue.

I'm trying to port raydium-sdk to golang using solana-go and everything is going well, but I can't get the Liquidity.fetchInfo working on golang.

Raydium's SDK implementation for the fetchInfo function is: creates an instruction, simulates it, and then parses the logs from the simulation to get liquidity pool information.

The function to create the instruction: https://github.com/raydium-io/raydium-sdk/blob/c44123e6df72c589a55eca6a595fd1153a176546/src/liquidity/liquidity.ts#L1318

Then this transaction instruction is included in a transaction and sent to RPC using the simulateMultipleInstruction function: https://github.com/raydium-io/raydium-sdk/blob/c44123e6df72c589a55eca6a595fd1153a176546/src/common/web3.ts#L235.

I made a dummy code with the raw implementation for both ts and golang using a RANDOM (no idea who owns it) liquidity pool as an example:

ts implementation:

import { AccountMetaReadonly, struct, u8 } from "@raydium-io/raydium-sdk";
import { Connection, PublicKey, Transaction, TransactionInstruction } from "@solana/web3.js";

export const testFetchInfo = async () => {
    const connection = new Connection("https://api.mainnet-beta.solana.com", {
        commitment: 'confirmed',
    });

    const LAYOUT = struct([u8('instruction'), u8('simulateType')]);
    const data = Buffer.alloc(LAYOUT.span)
    LAYOUT.encode(
        {
            instruction: 12,
            simulateType: 0,
        },
        data,
    );

    const keys = [
        // amm
        AccountMetaReadonly(new PublicKey('Gatxv6KgbmvWHsAScveUPyjRPZ72eyFzGy8s9YFTiXg7'), false),
        AccountMetaReadonly(new PublicKey('5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1'), false),
        AccountMetaReadonly(new PublicKey('2CjSHRgusrrh7Ke4PFwnJnBq83pRMNAVgffZsDspoRmi'), false),
        AccountMetaReadonly(new PublicKey('G2ZwmtSEZMD9E9oJfXG7rnVLZ7gadAJuBpabYLDCS5Gi'), false),
        AccountMetaReadonly(new PublicKey('2vkJSGSirnLVeUCn81FCNgwndPuCSYE79v4fUei1hB6X'), false),
        AccountMetaReadonly(new PublicKey('CFH6nuBREVstWc3MYgf2SEZw7wh9F7bLGNK3cKesSBM6'), false),
        // serum
        AccountMetaReadonly(new PublicKey('F534RAiGy6EikSHBNes2uxCeHdJD8YALY7c1PpHhKtL6'), false),
        AccountMetaReadonly(new PublicKey('54aRTv111QAv2KwbAFYK6QsMNmyw6sftN4PgSVP7c2cW'), false),
    ];

    var instruction = new TransactionInstruction({
        programId: new PublicKey('675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8'),
        keys,
        data,
    });

    const feePayer = new PublicKey('RaydiumSimuLateTransaction11111111111111111');
    let transaction = new Transaction();
    transaction.feePayer = feePayer;
    transaction.add(instruction);


    const hash = await connection.getLatestBlockhash();

    transaction.lastValidBlockHeight = hash.lastValidBlockHeight;
    transaction.recentBlockhash = hash.blockhash;

    var result = await connection.simulateTransaction(transaction);
    console.log("LOGS", result.value.logs);
};

Output:

LOGS [
  'Program 675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8 invoke [1]',
  'Program log: calc_exact len:0',
  'Program consumption: 190159 units remaining',
  'Program consumption: 190053 units remaining',
  'Program log: GetPoolData: {"status":6,"coin_decimals":6,"pc_decimals":9,"lp_decimals":6,"pool_pc_amount":7151137742,"pool_coin_amount":751087799653977,"pnl_pc_amount":0,"pnl_coin_amount":0,"pool_lp_supply":2202271554554,"pool_open_time":0,"amm_id":"Gatxv6KgbmvWHsAScveUPyjRPZ72eyFzGy8s9YFTiXg7"}',
  'Program 675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8 consumed 29401 of 200000 compute units',
  'Program 675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8 success'
]

golang implementation:

import (
	"bytes"
	"context"
	"fmt"
	"testing"

	bin "github.com/gagliardetto/binary"
	solgo "github.com/gagliardetto/solana-go"
	solrpc "github.com/gagliardetto/solana-go/rpc"
	"github.com/gagliardetto/solana-go/rpc/jsonrpc"
	"github.com/stretchr/testify/require"
)

func TestFetchInfoImplementationIntegration(t *testing.T) {
	if testing.Short() {
		return
	}

	url := "https://api.mainnet-beta.solana.com"

	jsonRpcClient := jsonrpc.NewClient(url)
	client := solrpc.NewWithCustomRPCClient(jsonRpcClient)

	out, err := client.GetHealth(context.Background())
	require.NoError(t, err)
	require.Equal(t, "ok", out)

	keys := solgo.AccountMetaSlice{
		// Amm
		// Pool ID
		solgo.Meta(solgo.MPK("Gatxv6KgbmvWHsAScveUPyjRPZ72eyFzGy8s9YFTiXg7")),
		// Program Authority
		solgo.Meta(solgo.MPK("5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1")),
		// Open Orders
		solgo.Meta(solgo.MPK("2CjSHRgusrrh7Ke4PFwnJnBq83pRMNAVgffZsDspoRmi")),
		// Base Vault
		solgo.Meta(solgo.MPK("G2ZwmtSEZMD9E9oJfXG7rnVLZ7gadAJuBpabYLDCS5Gi")),
		// Quote Vault
		solgo.Meta(solgo.MPK("2vkJSGSirnLVeUCn81FCNgwndPuCSYE79v4fUei1hB6X")),
		// Lp Mint
		solgo.Meta(solgo.MPK("CFH6nuBREVstWc3MYgf2SEZw7wh9F7bLGNK3cKesSBM6")),
		// Serum
		// Market ID
		solgo.Meta(solgo.MPK("F534RAiGy6EikSHBNes2uxCeHdJD8YALY7c1PpHhKtL6")),
		// Event Queue
		solgo.Meta(solgo.MPK("54aRTv111QAv2KwbAFYK6QsMNmyw6sftN4PgSVP7c2cW")),
	}

	programId := solgo.MPK("675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8")

	type simulateData struct {
		Instruction  uint8
		SimulateType uint8
	}
	data := simulateData{
		Instruction:  12,
		SimulateType: 0,
	}

	buf := new(bytes.Buffer)
	if err := bin.NewBinEncoder(buf).Encode(data); err != nil {
		require.Fail(t, "unable to encode instruction: %v", err)
	}

	instruction := solgo.NewInstruction(programId, keys, buf.Bytes())

        // `fetchInfo` implementation uses RaydiumSimuLateTransaction11111111111111111 as signer
	transactionBuilder := solgo.NewTransactionBuilder().
		SetFeePayer(solgo.MPK("RaydiumSimuLateTransaction11111111111111111")).
		AddInstruction(instruction)

	hash, err := client.GetLatestBlockhash(context.Background(), solrpc.CommitmentConfirmed)
	require.NoError(t, err)

	tx, err := transactionBuilder.SetRecentBlockHash(hash.Value.Blockhash).Build()
	require.NoError(t, err)

	simulation, err := client.SimulateTransactionWithOpts(context.Background(), tx, &solrpc.SimulateTransactionOpts{
		SigVerify:              false,
		Commitment:             solrpc.CommitmentConfirmed,
		ReplaceRecentBlockhash: false,
	})
	require.NoError(t, err)

	fmt.Println(simulation.Value.Logs)
}

Output:

(*jsonrpc.RPCError)(0xc000161200)({
 Code: (int) -32602,
 Message: (string) (len=78) "invalid transaction: Transaction failed to sanitize accounts offsets correctly",
 Data: (interface {}) <nil>
})

I'm trying to debug why this is happening, but because of my lack of understanding about solana-go internals, it is going pretty slowly...

Thanks.

@lucasoares
Copy link
Author

lucasoares commented Apr 23, 2024

I think it may be related to the transaction version or transaction encoding... The resulting bytes of the transaction serialization are very different from the ts code... Needs more debugging...

Edit: doing a little more debugging, if I sign the transaction, the resulting bytes will be parsed correctly and the simulation will run "correctly". I think the current marshal algorithm can't work for transactions without signatures.

The problem is: signing the transaction will not work for me, since I need the signer to be RaydiumSimuLateTransaction11111111111111111 to the raydium simulation to return the result I need...

Any idea about what needs to be done to be able to simulate transactions without signing?

Edit2: I made it work by adding a random signature in the tx.Signatures, but that should not be necessary, it is a bug..

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant