Skip to content
Draft
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
53 changes: 15 additions & 38 deletions cmd/generate-bindings/solana/anchor-go/generator/instructions.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,40 +99,21 @@ func (g *Generator) gen_instructions() (*OutputFile, error) {
returnsCode.Qual(PkgSolanaGo, "Instruction")
returnsCode.Error()
}).BlockFunc(func(body *Group) {
if len(instruction.Args) > 0 {
body.Id("buf__").Op(":=").New(Qual("bytes", "Buffer"))
body.Id("enc__").Op(":=").Qual(PkgBinary, "NewBorshEncoder").Call(Id("buf__"))
body.Id("buf__").Op(":=").New(Qual("bytes", "Buffer"))
body.Id("enc__").Op(":=").Qual(PkgBinary, "NewBorshEncoder").Call(Id("buf__"))

{
// write the discriminator
body.Line().Comment("Encode the instruction discriminator.")
discriminatorName := FormatInstructionDiscriminatorName(instruction.Name)
body.Err().Op(":=").Id("enc__").Dot("WriteBytes").Call(Id(discriminatorName).Index(Op(":")), False())
body.If(Err().Op("!=").Nil()).Block(
Return(
Nil(),
Qual("fmt", "Errorf").Call(Lit("failed to write instruction discriminator: %w"), Err()),
),
)
}
// for _, param := range instruction.Args {
// paramName := formatParamName(param.Name)
// isComplexEnum(param.Ty)

// body.Line().Commentf("Encode the parameter: %s", paramName)
// body.Block(
// Err().Op(":=").Id("enc__").Dot("Encode").Call(Id(paramName)),
// If(Err().Op("!=").Nil()).Block(
// Return(
// Nil(),
// Qual(PkgAnchorGoErrors, "NewField").Call(
// Lit(paramName),
// Err(),
// ),
// ),
// ),
// )
// }
{
body.Line().Comment("Encode the instruction discriminator.")
discriminatorName := FormatInstructionDiscriminatorName(instruction.Name)
body.Err().Op(":=").Id("enc__").Dot("WriteBytes").Call(Id(discriminatorName).Index(Op(":")), False())
body.If(Err().Op("!=").Nil()).Block(
Return(
Nil(),
Qual("fmt", "Errorf").Call(Lit("failed to write instruction discriminator: %w"), Err()),
),
)
}
if len(instruction.Args) > 0 {
checkNil := true
body.BlockFunc(func(g *Group) {
gen_marshal_DefinedFieldsNamed(
Expand Down Expand Up @@ -223,11 +204,7 @@ func (g *Generator) gen_instructions() (*OutputFile, error) {
ListMultiline(func(gg *Group) {
gg.Id("ProgramID")
gg.Id("accounts__")
if len(instruction.Args) > 0 {
gg.Id("buf__").Dot("Bytes").Call()
} else {
gg.Nil() // No arguments to encode.
}
gg.Id("buf__").Dot("Bytes").Call()
}),
)
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
//nolint:all // Forked from anchor-go generator, maintaining original code structure
package generator

import (
"strings"
"testing"

"github.com/gagliardetto/anchor-go/idl"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestGenInstructionsZeroArgsAlwaysWritesDiscriminator(t *testing.T) {
idlData := &idl.Idl{
Instructions: []idl.IdlInstruction{
{
Name: "ping",
Discriminator: idl.IdlDiscriminator{1, 2, 3, 4, 5, 6, 7, 8},
Args: []idl.IdlField{},
},
},
}

gen := &Generator{
idl: idlData,
options: &GeneratorOptions{Package: "test"},
}

outputFile, err := gen.gen_instructions()
require.NoError(t, err)
require.NotNil(t, outputFile)

code := outputFile.File.GoString()

assert.Contains(t, code, "NewBorshEncoder",
"zero-arg instruction builder must allocate an encoder for the discriminator")
assert.Contains(t, code, "WriteBytes",
"zero-arg instruction builder must write the discriminator bytes")
assert.NotContains(t, code, "nil, // No arguments to encode",
"zero-arg instruction must not pass nil as instruction data")

// The generated code must reference buf__.Bytes() so the discriminator is sent.
builderIdx := strings.Index(code, "func NewPingInstruction")
require.Greater(t, builderIdx, 0, "expected to find NewPingInstruction in generated code")
builderSnippet := code[builderIdx:]
assert.Contains(t, builderSnippet, "buf__.Bytes()",
"zero-arg instruction must pass buf__.Bytes() (containing the discriminator) as instruction data")
}
39 changes: 39 additions & 0 deletions cmd/generate-bindings/solana/bindings_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,45 @@ func TestWriteReportMethods(t *testing.T) {
}, response), "Response should match expected WriteReportReply")
}

func TestZeroArgInstructionRoundTrip(t *testing.T) {
zeroArgInstructions := []struct {
name string
buildFn func() (solana.Instruction, error)
expectedTyp datastorage.Instruction
}{
{
name: "get_multiple_reserves",
buildFn: datastorage.NewGetMultipleReservesInstruction,
expectedTyp: &datastorage.GetMultipleReservesInstruction{},
},
{
name: "get_reserves",
buildFn: datastorage.NewGetReservesInstruction,
expectedTyp: &datastorage.GetReservesInstruction{},
},
{
name: "get_tuple_reserves",
buildFn: datastorage.NewGetTupleReservesInstruction,
expectedTyp: &datastorage.GetTupleReservesInstruction{},
},
}

for _, tc := range zeroArgInstructions {
t.Run(tc.name, func(t *testing.T) {
ix, err := tc.buildFn()
require.NoError(t, err, "building instruction should succeed")

data, err := ix.Data()
require.NoError(t, err)
require.Len(t, data, 8, "zero-arg instruction data must be exactly the 8-byte discriminator")

parsed, err := datastorage.ParseInstructionWithoutAccounts(data)
require.NoError(t, err, "ParseInstruction must accept the discriminator-only data produced by the builder")
require.IsType(t, tc.expectedTyp, parsed)
})
}
}

func TestEncodeStruct(t *testing.T) {
client := &solanasdk.Client{ChainSelector: anyChainSelector}
ds, err := datastorage.NewDataStorage(client)
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.