gagliardetto / solana-go

Go SDK library and RPC client for the Solana Blockchain
Apache License 2.0
818 stars 243 forks source link

panic while decoding Instruction in spl transfer transaction #207

Closed BillInUK closed 4 months ago

BillInUK commented 4 months ago

i use the latest version of solana-go to decode spl transfer transaction on mainnet with the following codee:

func TestQueryTokenTx(t *testing.T) {
    client := rpc.New(RpcMainNet)
    txSig := solana.MustSignatureFromBase58("2kX9yRqsFH6tqDuSkM8tEApXYygvfDK6t2gvaqrAK9whg8Z3fmLdMi3MjZqfFgiubDkGQDLKGH6Fr9a3yBCkgFXY")
    {
        out, err := client.GetTransaction(
            context.TODO(),
            txSig,
            &rpc.GetTransactionOpts{
                Encoding: solana.EncodingBase64,
            },
        )
        if err != nil {
            panic(err)
        }

        tx, err := solana.TransactionFromDecoder(bin.NewBinDecoder(out.Transaction.GetBinary()))
        if err != nil {
            panic(err)
        }

        decodeSystemTransfer(tx)
    }
}

func decodeSystemTransfer(tx *solana.Transaction) {
    spew.Dump(tx)

    // Get (for example) the first instruction of this transaction
    // which we know is a `system` program instruction:
    i0 := tx.Message.Instructions[0]

    // Find the program address of this instruction:
    progKey, err := tx.ResolveProgramIDIndex(i0.ProgramIDIndex)
    if err != nil {
        panic(err)
    }

    // Find the accounts of this instruction:
    accounts, err := i0.ResolveInstructionAccounts(&tx.Message)
    if err != nil {
        panic(err)
    }

    // Feed the accounts and data to the system program parser
    // OR see below for alternative parsing when you DON'T know
    // what program the instruction is for / you don't have a parser.
    inst, err := system.DecodeInstruction(accounts, i0.Data)
    if err != nil {
        panic(err)
    }

    // inst.Impl contains the specific instruction type (in this case, `inst.Impl` is a `*system.Transfer`)
    spew.Dump(inst)
    if _, ok := inst.Impl.(*system.Transfer); !ok {
        panic("the instruction is not a *system.Transfer")
    }

    // OR
    {
        // There is a more general instruction decoder: `solana.DecodeInstruction`.
        // But before you can use `solana.DecodeInstruction`,
        // you must register a decoder for each program ID beforehand
        // by using `solana.RegisterInstructionDecoder` (all solana-go program clients do it automatically with the default program IDs).
        decodedInstruction, err := solana.DecodeInstruction(
            progKey,
            accounts,
            i0.Data,
        )
        if err != nil {
            panic(err)
        }
        // The returned `decodedInstruction` is the decoded instruction.
        spew.Dump(decodedInstruction)

        // decodedInstruction == inst
        if !reflect.DeepEqual(inst, decodedInstruction) {
            panic("they are NOT equal (this would never happen)")
        }

        // To register other (not yet registered decoders), you can add them with
        // `solana.RegisterInstructionDecoder` function.
    }

    {
        // pretty-print whole transaction:
        _, err := tx.EncodeTree(text.NewTreeEncoder(os.Stdout, text.Bold("TEST TRANSACTION")))
        if err != nil {
            panic(err)
        }
    }
}

when i run this test case it panic,and the output in console:

=== RUN   TestQueryTokenTx
(*solana.Transaction)(0xc0001ab360)(   
   ├─ Signatures[len=1]
   │    └─ 2kX9yRqsFH6tqDuSkM8tEApXYygvfDK6t2gvaqrAK9whg8Z3fmLdMi3MjZqfFgiubDkGQDLKGH6Fr9a3yBCkgFXY
   ├─ Message
   │    ├─ Version: legacy
   │    ├─ RecentBlockhash: 8CrERd4GrEYCAF1tsmUiZA33PRy9QHGcps1soesQJoHn
   │    ├─ AccountKeys[len=6]
   │    │    ├─ GmKsGRytiVoeMZGmBVCWPcUzJGHVqcvzhP5K9cstdr3E
   │    │    ├─ 3XRNhFEXkV7itJEJMDBN8iRHBgwhgk83XB5K3s3re3Ew
   │    │    ├─ 65h8ExrJjVXo65VyP1NWGTYfBgVVPS1e3nBnt6K1EkWA
   │    │    ├─ ComputeBudget111111111111111111111111111111
   │    │    ├─ H2UEokEri4Lv18Re3XCBXw3wMS38nWFmRdrymyML8ffv
   │    │    └─ TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA
   │    └─ Header
   │       ├─ NumRequiredSignatures: 1
   │       ├─ NumReadonlySignedAccounts: 0
   │       └─ NumReadonlyUnsignedAccounts: 3
   └─ Instructions[len=3]
      ├─ cannot decode instruction for ComputeBudget111111111111111111111111111111 program: instruction decoder not found
      │    └─ Program: <unknown> ComputeBudget111111111111111111111111111111
      │       └─ Instruction: <unknown>
      │          ├─ data[len=9 bytes]
      │          │    └─ {3, 160, 134, 1, 0, 0, 0, 0, 0}(len=9)
      │          └─ accounts[len=0]
      ├─ cannot decode instruction for ComputeBudget111111111111111111111111111111 program: instruction decoder not found
      │    └─ Program: <unknown> ComputeBudget111111111111111111111111111111
      │       └─ Instruction: <unknown>
      │          ├─ data[len=5 bytes]
      │          │    └─ {2, 64, 13, 3, 0}(len=5)
      │          └─ accounts[len=0]
      └─ Program: Token TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA
         └─ Instruction: TransferChecked
            ├─ Params
            │    ├─   Amount: (uint64) 1300000000
            │    └─ Decimals: (uint8) 9
            └─ Accounts
               ├─      source: 3XRNhFEXkV7itJEJMDBN8iRHBgwhgk83XB5K3s3re3Ew [WRITE] 
               ├─        mint: H2UEokEri4Lv18Re3XCBXw3wMS38nWFmRdrymyML8ffv [] 
               ├─ destination: 65h8ExrJjVXo65VyP1NWGTYfBgVVPS1e3nBnt6K1EkWA [WRITE] 
               ├─       owner: GmKsGRytiVoeMZGmBVCWPcUzJGHVqcvzhP5K9cstdr3E [WRITE, SIGN] 
               └─ signers[len=1]
                  └─ [0]: GmKsGRytiVoeMZGmBVCWPcUzJGHVqcvzhP5K9cstdr3E [WRITE, SIGN] 
)
--- FAIL: TestQueryTokenTx (3.83s)
panic: unable to decode instruction: no known type for type [3 160 134 1 0 0 0 0] [recovered]
    panic: unable to decode instruction: no known type for type [3 160 134 1 0 0 0 0]

goroutine 7 [running]:
testing.tRunner.func1.2({0x10083dc80, 0xc00007d1e0})
    /Users/mac/go/go1.21.0/src/testing/testing.go:1545 +0x238
testing.tRunner.func1()
    /Users/mac/go/go1.21.0/src/testing/testing.go:1548 +0x397
panic({0x10083dc80?, 0xc00007d1e0?})
    /Users/mac/go/go1.21.0/src/runtime/panic.go:914 +0x21f
fiber-go-inscription/utils.decodeSystemTransfer(0xc0001ab360)
    /Users/mac/go/src/meme-go/utils/solana_util_test.go:249 +0x487
fiber-go-inscription/utils.TestQueryTokenTx(0x0?)
    /Users/mac/go/src/meme-go/utils/solana_util_test.go:221 +0x1c5
testing.tRunner(0xc000582ea0, 0x10095eaa8)
    /Users/mac/go/go1.21.0/src/testing/testing.go:1595 +0xff
created by testing.(*T).Run in goroutine 1
    /Users/mac/go/go1.21.0/src/testing/testing.go:1648 +0x3ad

it panic at

    inst, err := system.DecodeInstruction(accounts, i0.Data)
    if err != nil {
        panic(err)
    }
Matthew17-21 commented 4 months ago

Pumpfun was built with anchor so you have to generate the appropriate bindings to decode

BillInUK commented 4 months ago

thanks for help , i resloved with following code:

// 查询token有关的交易
func TestQueryTokenTx(t *testing.T) {
    client := rpc.New(DevRpc)
    txSig := solana.MustSignatureFromBase58("5aVh3XHbD7TP4gPzFcybPmHp59en64KYdJzehZfishgQUoJ7AZpzbFsumVN1JKKVSzHx1WZSZ6VANjG9zzQHnujF")
    {
        out, err := client.GetTransaction(
            context.TODO(),
            txSig,
            &rpc.GetTransactionOpts{
                Encoding: solana.EncodingBase64,
            },
        )
        if err != nil {
            panic(err)
        }

        tx, err := solana.TransactionFromDecoder(bin.NewBinDecoder(out.Transaction.GetBinary()))
        if err != nil {
            panic(err)
        }

        decodeSPLTokenTransfer(tx)
    }
}

func decodeSPLTokenTransfer(tx *solana.Transaction) {
    // 依次解析命令,解析出的TransferChecked指令为止
    for _, inst := range tx.Message.Instructions {
        progKey, err := tx.ResolveProgramIDIndex(inst.ProgramIDIndex)
        if err != nil {
            break
        }
        fmt.Printf("progKey is %v \n", progKey)

        accounts, err := inst.ResolveInstructionAccounts(&tx.Message)
        if err != nil {
            break
        }
        decodedInst, err := token.DecodeInstruction(accounts, inst.Data)
        if err != nil {
            continue
        }
        if transferInfo, ok := decodedInst.Impl.(*token.TransferChecked); ok {
            fmt.Printf("from %v\n", transferInfo.Accounts.Get(0).PublicKey.String())
            fmt.Printf("to %v\n", transferInfo.Accounts.Get(2).PublicKey.String())
            fmt.Printf("token %v\n", transferInfo.Accounts.Get(1).PublicKey.String())
            fmt.Printf("amount %d\n", *transferInfo.Amount)
            fmt.Printf("decimals %d\n", *transferInfo.Decimals)
        }
    }
}
ygcool commented 3 months ago

@BillInUK hi,

if transferInfo, ok := decodedInst.Impl.(*token.TransferChecked); ok {
    fmt.Printf("from %v\n", transferInfo.Accounts.Get(0).PublicKey.String())
    fmt.Printf("to %v\n", transferInfo.Accounts.Get(2).PublicKey.String()) // 65h8ExrJjVXo65VyP1NWGTYfBgVVPS1e3nBnt6K1EkWA
    fmt.Printf("token %v\n", transferInfo.Accounts.Get(1).PublicKey.String())
    fmt.Printf("amount %d\n", *transferInfo.Amount)
    fmt.Printf("decimals %d\n", *transferInfo.Decimals)
}

your tx: 2kX9yRqsFH6tqDuSkM8tEApXYygvfDK6t2gvaqrAK9whg8Z3fmLdMi3MjZqfFgiubDkGQDLKGH6Fr9a3yBCkgFXY

transferInfo.Accounts.Get(2).PublicKey.String() = 65h8ExrJjVXo65VyP1NWGTYfBgVVPS1e3nBnt6K1EkWA

But the actual receiving address is:5D4MWh35wxUcY1hBsm5GwuippPL2UBmfDnfkC8MeqxcN

How can we obtain the real token receiving address?

thanks.