Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 12 additions & 15 deletions message.go
Original file line number Diff line number Diff line change
Expand Up @@ -349,22 +349,19 @@ func (mx *Message) MarshalV0() ([]byte, error) {
}
buf = append([]byte{byte(versionNum + 127)}, buf...)

if mx.AddressTableLookups != nil && len(mx.AddressTableLookups) > 0 {
// wite length of address table lookups as u8
buf = append(buf, byte(len(mx.AddressTableLookups)))
for _, lookup := range mx.AddressTableLookups {
// write account pubkey
buf = append(buf, lookup.AccountKey[:]...)
// write writable indexes
bin.EncodeCompactU16Length(&buf, len(lookup.WritableIndexes))
buf = append(buf, lookup.WritableIndexes...)
// write readonly indexes
bin.EncodeCompactU16Length(&buf, len(lookup.ReadonlyIndexes))
buf = append(buf, lookup.ReadonlyIndexes...)
}
} else {
buf = append(buf, 0)
// wite length of address table lookups as u8
buf = append(buf, byte(len(mx.AddressTableLookups)))
for _, lookup := range mx.AddressTableLookups {
Comment on lines +353 to +354
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice clean up

// write account pubkey
buf = append(buf, lookup.AccountKey[:]...)
// write writable indexes
bin.EncodeCompactU16Length(&buf, len(lookup.WritableIndexes))
buf = append(buf, lookup.WritableIndexes...)
// write readonly indexes
bin.EncodeCompactU16Length(&buf, len(lookup.ReadonlyIndexes))
buf = append(buf, lookup.ReadonlyIndexes...)
}

return buf, nil
}

Expand Down
46 changes: 31 additions & 15 deletions programs/associated-token-account/Create.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,6 @@ type Create struct {
//
// [5] = [] TokenProgram
// ··········· SPL token program ID
//
// [6] = [] SysVarRent
// ··········· SysVarRentPubkey
Comment on lines -49 to -51
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm so confused why this was here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah it wasn't clear to me either 😅

solana.AccountMetaSlice `bin:"-" borsh_skip:"true"`
}

Expand All @@ -73,8 +70,18 @@ func (inst *Create) SetMint(mint solana.PublicKey) *Create {
return inst
}

func (inst Create) Build() *Instruction {
func (inst *Create) SetAccounts(accounts []*solana.AccountMeta) error {
inst.AccountMetaSlice = accounts
if len(accounts) < 6 {
return fmt.Errorf("insufficient accounts, Create requires at-least 6 accounts not %d", len(accounts))
}
inst.Payer = accounts[0].PublicKey
inst.Wallet = accounts[2].PublicKey
inst.Mint = accounts[3].PublicKey
return nil
}

func (inst Create) Build() *Instruction {
// Find the associatedTokenAddress;
associatedTokenAddress, _, _ := solana.FindAssociatedTokenAddress(
inst.Wallet,
Expand Down Expand Up @@ -112,11 +119,6 @@ func (inst Create) Build() *Instruction {
IsSigner: false,
IsWritable: false,
},
{
PublicKey: solana.SysVarRentPubkey,
IsSigner: false,
IsWritable: false,
},
}

inst.AccountMetaSlice = keys
Expand All @@ -139,13 +141,13 @@ func (inst Create) ValidateAndBuild() (*Instruction, error) {

func (inst *Create) Validate() error {
if inst.Payer.IsZero() {
return errors.New("Payer not set")
return errors.New("payer not set")
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ty for thinking of go

}
if inst.Wallet.IsZero() {
return errors.New("Wallet not set")
return errors.New("wallet not set")
}
if inst.Mint.IsZero() {
return errors.New("Mint not set")
return errors.New("mint not set")
}
_, _, err := solana.FindAssociatedTokenAddress(
inst.Wallet,
Expand All @@ -164,19 +166,17 @@ func (inst *Create) EncodeToTree(parent treeout.Branches) {
programBranch.Child(format.Instruction("Create")).
//
ParentFunc(func(instructionBranch treeout.Branches) {

// Parameters of the instruction:
instructionBranch.Child("Params[len=0]").ParentFunc(func(paramsBranch treeout.Branches) {})

// Accounts of the instruction:
instructionBranch.Child("Accounts[len=7").ParentFunc(func(accountsBranch treeout.Branches) {
instructionBranch.Child("Accounts[len=6").ParentFunc(func(accountsBranch treeout.Branches) {
accountsBranch.Child(format.Meta(" payer", inst.AccountMetaSlice.Get(0)))
accountsBranch.Child(format.Meta("associatedTokenAddress", inst.AccountMetaSlice.Get(1)))
accountsBranch.Child(format.Meta(" wallet", inst.AccountMetaSlice.Get(2)))
accountsBranch.Child(format.Meta(" tokenMint", inst.AccountMetaSlice.Get(3)))
accountsBranch.Child(format.Meta(" systemProgram", inst.AccountMetaSlice.Get(4)))
accountsBranch.Child(format.Meta(" tokenProgram", inst.AccountMetaSlice.Get(5)))
accountsBranch.Child(format.Meta(" sysVarRent", inst.AccountMetaSlice.Get(6)))
})
})
})
Expand All @@ -200,3 +200,19 @@ func NewCreateInstruction(
SetWallet(walletAddress).
SetMint(splTokenMintAddress)
}

func (inst *Create) GetPayerAccount() *solana.AccountMeta {
return inst.AccountMetaSlice.Get(0)
}

func (inst *Create) GetAssociatedTokenAddressAccount() *solana.AccountMeta {
return inst.AccountMetaSlice.Get(1)
}

func (inst *Create) GetWalletAccount() *solana.AccountMeta {
return inst.AccountMetaSlice.Get(2)
}

func (inst *Create) GetMintAccount() *solana.AccountMeta {
return inst.AccountMetaSlice.Get(3)
}
132 changes: 132 additions & 0 deletions programs/associated-token-account/instruction_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
// Copyright 2021 github.com/gagliardetto
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package associatedtokenaccount

import (
"encoding/hex"
"testing"

bin "github.com/gagliardetto/binary"
solana "github.com/gagliardetto/solana-go"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestEncodingInstruction(t *testing.T) {
t.Run("should encode", func(t *testing.T) {
t.Run("Create", func(t *testing.T) {
// Build an instruction and ensure current encoding matches implementation
payer := solana.NewWallet().PublicKey()
wallet := solana.NewWallet().PublicKey()
mint := solana.NewWallet().PublicKey()
ix := NewCreateInstructionBuilder().
SetPayer(payer).
SetWallet(wallet).
SetMint(mint).
Build()
data, err := ix.Data()
require.NoError(t, err)
encodedHex := hex.EncodeToString(data)
// Current ATA Create encodes no payload bytes
require.Equal(t, "", encodedHex)
})
})

tests := []struct {
name string
hexData string
expectInstruction *Instruction
}{
{
name: "Create",
hexData: "",
expectInstruction: &Instruction{
BaseVariant: bin.BaseVariant{
TypeID: bin.TypeIDFromUint8(0),
Impl: &Create{},
},
},
},
}

t.Run("should encode", func(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
data, err := test.expectInstruction.Data()
require.NoError(t, err)
encodedHex := hex.EncodeToString(data)
require.Equal(t, test.hexData, encodedHex)
})
}
})

t.Run("should decode", func(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
data, err := hex.DecodeString(test.hexData)
require.NoError(t, err)
var instruction *Instruction
err = bin.NewBinDecoder(data).Decode(&instruction)
require.NoError(t, err)
assert.Equal(t, test.expectInstruction, instruction)
})
}
})
}

func TestDecodeSetsAccountsAndGetters(t *testing.T) {
payer := solana.NewWallet().PublicKey()
wallet := solana.NewWallet().PublicKey()
mint := solana.NewWallet().PublicKey()

// Build an instruction to obtain correctly ordered accounts and data
ix := NewCreateInstructionBuilder().
SetPayer(payer).
SetWallet(wallet).
SetMint(mint).
Build()

accounts := ix.Accounts()
data, err := ix.Data()
require.NoError(t, err)

decoded, err := DecodeInstruction(accounts, data)
require.NoError(t, err)

create, ok := decoded.Impl.(*Create)
require.True(t, ok)

// Check decoded fields populated via SetAccounts
assert.Equal(t, payer, create.Payer)
assert.Equal(t, wallet, create.Wallet)
assert.Equal(t, mint, create.Mint)

// Check getters return expected account metas
require.NotNil(t, create.GetPayerAccount())
require.NotNil(t, create.GetAssociatedTokenAddressAccount())
require.NotNil(t, create.GetWalletAccount())
require.NotNil(t, create.GetMintAccount())

assert.True(t, create.GetPayerAccount().IsSigner)
assert.True(t, create.GetPayerAccount().IsWritable)
assert.Equal(t, payer, create.GetPayerAccount().PublicKey)
assert.Equal(t, wallet, create.GetWalletAccount().PublicKey)
assert.Equal(t, mint, create.GetMintAccount().PublicKey)

// Verify associated token address is correctly derived and placed at index 1
ata, _, err := solana.FindAssociatedTokenAddress(wallet, mint)
require.NoError(t, err)
assert.Equal(t, ata, create.GetAssociatedTokenAddressAccount().PublicKey)
}
24 changes: 16 additions & 8 deletions transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -484,16 +484,24 @@ func (tx *Transaction) MarshalBinary() ([]byte, error) {
return nil, fmt.Errorf("failed to encode tx.Message to binary: %w", err)
}

var signatureCount []byte
bin.EncodeCompactU16Length(&signatureCount, len(tx.Signatures))
output := make([]byte, 0, len(signatureCount)+len(signatureCount)*64+len(messageContent))
output = append(output, signatureCount...)
for _, sig := range tx.Signatures {
output = append(output, sig[:]...)
signatures := tx.Signatures
for i := len(signatures); i < int(tx.Message.Header.NumRequiredSignatures); i++ {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: If len(tx.Signatures) > tx.Message.Header.NumRequiredSignatures, you’ll serialize too many signatures below in the binaryTx. The wire format is expected to match the header exactly. You can return an error early to let the user know:

if len(signatures) > tx.Message.Header.NumRequiredSignatures {
    return nil, fmt.Errorf("invalid signature length for NumRequiredSignatures header value")
}

// append dummy signatures to the transaction, without it serialized transaction will be invalid
// reference: https://github.com/solana-labs/solana-web3.js/blob/4e9988cfc561f3ed11f4c5016a29090a61d129a8/src/transaction/versioned.ts#L36
signatures = append(signatures, SignatureFromBytes(make([]byte, SignatureLength)))
}
output = append(output, messageContent...)

return output, nil
var signaturesCountBytes []byte
bin.EncodeCompactU16Length(&signaturesCountBytes, len(signatures))

binaryTx := make([]byte, 0, len(signaturesCountBytes)+len(signatures)*64+len(messageContent))
binaryTx = append(binaryTx, signaturesCountBytes...)
for _, sig := range signatures {
binaryTx = append(binaryTx, sig[:]...)
}

binaryTx = append(binaryTx, messageContent...)
return binaryTx, nil
}

func (tx Transaction) MarshalWithEncoder(encoder *bin.Encoder) error {
Expand Down
49 changes: 49 additions & 0 deletions transaction_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"github.com/mr-tron/base58"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.uber.org/zap"
)

type testTransactionInstructions struct {
Expand Down Expand Up @@ -315,6 +316,54 @@ func TestTransactionVerifySignatures(t *testing.T) {
}
}

func TestTransactionSerializeExisting(t *testing.T) {
// random pump amm swap transaction (3HWKcTbnAMXt3TZDi8LitZCAT5ht7tYqXCroQgNkEnRuvjWCAhmx4UAFnKTWzxS2JXxhbfTiKEdXeU3VHWKzEkNY)
trueEncoded := "AXJEirR5ePYXWIemCsSrRB3kTOxBQvJ8pZ4Of+vs75/Lw4NgNf0jr+eyI+2CAZZcwEQ54v/tcIh0p5qisd6ZfQWAAQAHDuIP6DQ7XgVvZzx4ZyZS5BjxS0s5JIGa893M/2foLj/N9tJulKNTEJ+CcURWZpXddGLJc0niyvBs8fADaZXWBStZSYulGfGwKCcEVhAem2vYiJnZnDQHW8EbXuli2pgFPcs4OJAtX+MJFvqNDoxBApNOXVmhOPi+s+Xj5G6dipCzZIG9Det/kYMpmklt7LaKvckpmIhAC+cygPT/H7L6uDUm47unlLqsWvR0JhT3lhSFUnSWfDsT92IHGjplDB07Bwa4Mppngp8gB0rx3o6jx3q5zZqJ7jaF//YA5f9pM0rWAwZGb+UhFzL/7K26csOb57yM5bvF9xJrLEObOkAAAACMlyWPTiSJ8bs9ECkUjg2DC1oTmdr/EIQEjnvY2+n4WQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABt324ddloZPZy+FGzut5rBy0he1fWzeROoz1hX7/AKkMFN78gl7GdpQlCBi7ZUBl9CmNMVbVcbTU+AkMGOmoY2CQL4wWkL0iw2m16tD4VGZ2ZSCn9Rr5uDY8UTDVNj2KSwXRBHtnMBytk20Zd1LAlKbsLUEcQ8RupHeXS0aTLV9S0zqFMcANe7dmXMUOgO/qYKWak7hGKVjQNpnUxyaEPQgHAAUCkHwBAAcACQNkjocBAAAAAAgGAAEAEAkKAQELEQwADg0QAgEDBBEPCgoJCBILGDPmhaQBf4OtOLwvMTcAAABscskIAAAAAAoDAQAAAQkKAwIAAAEJCQIABQwCAAAAQEtMAAAAAAAJAgAGDAIAAABAQg8AAAAAAAEcQpjY9205kUMXxtRgI8+U286AVT/u2xYmbclxYfAs+QJDZQMS0kc="

tx, err := TransactionFromBase64(trueEncoded)
require.NoError(t, err)

encoded, err := tx.ToBase64()
require.NoError(t, err)
require.Equal(t, trueEncoded, encoded)
}

func TestTransactionSerializePumpFunSwap(t *testing.T) {
expectedEncoded := "AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAcMC8/60geEHarnEMtcmE3t7lADNe2/gxa7NAhBe5Ufe5mtEeak/ClEpPqCUb74FUJuG/soxrZkZndgfGrZ9WamRvj7gB4Ax7akKlldX2HW0ZpscDlcG0xgSGrm4qPYLjpXha3ZyoEhdb0urZWzhcxyIVTkHNfJDlRkaaaZumtUuJid6LnvUOa256MT0Ym0MG/y6Uqt2PX3ijrL9vC9eTaGKzqGXmnuD1SAyrz2Y1fk3C8Y1Y1Fwep0ifs3I9l5PHKm6c4nvkyiO9V3lgShoznE+kfvld5CoS4qMMKpnUOErY8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAbd9uHXZaGT2cvhRs7reawctIXtX1s3kTqM9YV+/wCpBqfVFxksXFEhjMlMPUrxf1ja7gibof1E49vZigAAAACs8TbrAfwcTog9I8i1hEq1mjf2at1XxemsO1PgWdNcZAFW4PaTZlrPRNsVaL8XW6pRicuX9dL/O2VdK7b9bRiw0WlzNBoKArNIcmjqJI0o+XoQGnMjSEA/HZqyYrSJgLkBCwwFAQYCAwQABwgJCgsYZgY9EgHa6+pMR9bEaRkAAHg1HjQAAAAA"
instruction := &testTransactionInstructions{
programID: MPK("6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P"),
accounts: []*AccountMeta{
{PublicKey: MPK("4wTV1YmiEkRvAtNtsSGPtUrqRYQMe5SKy2uB4Jjaxnjf"), IsSigner: false, IsWritable: false},
{PublicKey: MPK("CebN5WGQ4jvEPvsVU4EoHEpgzq1VV7AbicfhtW4xC9iM"), IsSigner: false, IsWritable: true},
{PublicKey: MPK("GjgKTqtzDei5E3uZyA2CN29KQgugF564K1hoc1jHpump"), IsSigner: false, IsWritable: false},
{PublicKey: MPK("HkvYAZV1Mg6kt5KMaA5YBQazZECg21zaZdQEMUiLrjKc"), IsSigner: false, IsWritable: true},
{PublicKey: MPK("9zpyjwrYdRWNMyqicoiuL3gUcrbvrkd5Kq9nxui1znw1"), IsSigner: false, IsWritable: true},
{PublicKey: MPK("BdQqJnuqqFhNZUNYGEEsuhBidpf8qHqfjDQvcjDN3nti"), IsSigner: false, IsWritable: true},
{PublicKey: MPK("o7RY6P2vQMuGSu1TrLM81weuzgDjaCRTXYRaXJwWcvc"), IsSigner: true, IsWritable: true},
{PublicKey: MPK("11111111111111111111111111111111"), IsSigner: false, IsWritable: false},
{PublicKey: MPK("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"), IsSigner: false, IsWritable: false},
{PublicKey: MPK("SysvarRent111111111111111111111111111111111"), IsSigner: false, IsWritable: false},
{PublicKey: MPK("Ce6TQqeHC9p8KetsN6JsjHK7UTZk7nasjjnr7XxXp9F1"), IsSigner: false, IsWritable: false},
{PublicKey: MPK("6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P"), IsSigner: false, IsWritable: false},
},
data: []byte{102, 6, 61, 18, 1, 218, 235, 234, 76, 71, 214, 196, 105, 25, 0, 0, 120, 53, 30, 52, 0, 0, 0, 0},
}

tx, err := NewTransactionBuilder().
AddInstruction(instruction).
SetFeePayer(MPK("o7RY6P2vQMuGSu1TrLM81weuzgDjaCRTXYRaXJwWcvc")).
SetRecentBlockHash(MustHashFromBase58("F6TUDvYPMwDLP1MW4BUWTNm6S94XR1UZ2nGVyubqo6oi")).
Build()
require.NoError(t, err)
require.NotNil(t, tx)

encoded, err := tx.ToBase64()
require.NoError(t, err)

zlog.Debug("encoded", zap.String("encoded", encoded))
require.Equal(t, expectedEncoded, encoded)
}

func BenchmarkTransactionFromDecoder(b *testing.B) {
txString := "Ak8jvC3ch5hq3lhOHPkACoFepIUON2zEN4KRcw4lDS6GBsQfnSdzNGPETm/yi0hPKk75/i2VXFj0FLUWnGR64ADyUbqnirFjFtaSNgcGi02+Tm7siT4CPpcaTq0jxfYQK/h9FdxXXPnLry74J+RE8yji/BtJ/Cjxbx+TIHigeIYJAgEBBByE1Y6EqCJKsr7iEupU6lsBHtBdtI4SK3yWMCFA0iEKeFPgnGmtp+1SIX1Ak+sN65iBaR7v4Iim5m1OEuFQTgi9N57UnhNpCNuUePaTt7HJaFBmyeZB3deXeKWVudpY3gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWVECK/n3a7QR6OKWYR4DuAVjS6FXgZj82W0dJpSIPnEBAwQAAgEDDAIAAABAQg8AAAAAAA=="
txBin, err := base64.StdEncoding.DecodeString(txString)
Expand Down
Loading