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
4 changes: 2 additions & 2 deletions message.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import (

bin "github.com/gagliardetto/binary"
"github.com/gagliardetto/treeout"
jsoniter "github.com/json-iterator/go"
gojson "github.com/goccy/go-json"

"github.com/gagliardetto/solana-go/text"
)
Expand Down Expand Up @@ -242,7 +242,7 @@ func (mx *Message) UnmarshalJSON(data []byte) error {
Header MessageHeader `json:"header"`
RecentBlockhash Hash `json:"recentBlockhash"`
Instructions []CompiledInstruction `json:"instructions"`
AddressTableLookups *jsoniter.RawMessage `json:"addressTableLookups"`
AddressTableLookups *gojson.RawMessage `json:"addressTableLookups"`
}{}
if err := json.Unmarshal(data, &aux); err != nil {
return err
Expand Down
2 changes: 2 additions & 0 deletions program_ids.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ var (
// Deploys, upgrades, and executes programs on the chain.
BPFLoaderProgramID = MustPublicKeyFromBase58("BPFLoader2111111111111111111111111111111111")
BPFLoaderUpgradeableProgramID = MustPublicKeyFromBase58("BPFLoaderUpgradeab1e11111111111111111111111")
LoaderV4ProgramID = MustPublicKeyFromBase58("LoaderV411111111111111111111111111111111111")
NativeLoaderID = MustPublicKeyFromBase58("NativeLoader1111111111111111111111111111111")

// Verify secp256k1 public key recovery operations (ecrecover).
Secp256k1ProgramID = MustPublicKeyFromBase58("KeccakSecp256k11111111111111111111111111111")
Expand Down
108 changes: 108 additions & 0 deletions programs/loader-v2/Finalize.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
// 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 loaderv2

import (
"encoding/binary"
"fmt"

ag_binary "github.com/gagliardetto/binary"
ag_solanago "github.com/gagliardetto/solana-go"
ag_format "github.com/gagliardetto/solana-go/text/format"
ag_treeout "github.com/gagliardetto/treeout"
)

// Finalize an account loaded with program data for execution.
//
// Account references:
//
// [0] = [WRITE, SIGNER] Account to prepare for execution
// [1] = [] Rent sysvar
type Finalize struct {
ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"`
}

func NewFinalizeInstructionBuilder() *Finalize {
return &Finalize{
AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 2),
}
}

func (inst *Finalize) SetAccount(account ag_solanago.PublicKey) *Finalize {
inst.AccountMetaSlice[0] = ag_solanago.Meta(account).WRITE().SIGNER()
return inst
}

func (inst *Finalize) GetAccount() *ag_solanago.AccountMeta {
return inst.AccountMetaSlice[0]
}

// SetRentSysvar attaches the rent sysvar account. The helper NewFinalizeInstruction
// uses the canonical sysvar ID; this setter is exposed for callers that wish
// to override it.
func (inst *Finalize) SetRentSysvar(rent ag_solanago.PublicKey) *Finalize {
inst.AccountMetaSlice[1] = ag_solanago.Meta(rent)
return inst
}

func (inst *Finalize) GetRentSysvar() *ag_solanago.AccountMeta {
return inst.AccountMetaSlice[1]
}

func (inst Finalize) Build() *Instruction {
return &Instruction{BaseVariant: ag_binary.BaseVariant{
Impl: inst,
TypeID: ag_binary.TypeIDFromUint32(Instruction_Finalize, binary.LittleEndian),
}}
}

func (inst Finalize) ValidateAndBuild() (*Instruction, error) {
if err := inst.Validate(); err != nil {
return nil, err
}
return inst.Build(), nil
}

func (inst *Finalize) Validate() error {
for i, acc := range inst.AccountMetaSlice {
if acc == nil {
return fmt.Errorf("ins.AccountMetaSlice[%v] is not set", i)
}
}
return nil
}

func (inst *Finalize) EncodeToTree(parent ag_treeout.Branches) {
parent.Child(ag_format.Program(ProgramName, ProgramID)).
ParentFunc(func(programBranch ag_treeout.Branches) {
programBranch.Child(ag_format.Instruction("Finalize")).
ParentFunc(func(instructionBranch ag_treeout.Branches) {
instructionBranch.Child("Accounts").ParentFunc(func(a ag_treeout.Branches) {
a.Child(ag_format.Meta("Account", inst.AccountMetaSlice[0]))
a.Child(ag_format.Meta(" Rent", inst.AccountMetaSlice[1]))
})
})
})
}

// Finalize carries no payload: the discriminant alone is the data.
func (inst Finalize) MarshalWithEncoder(_ *ag_binary.Encoder) error { return nil }
func (inst *Finalize) UnmarshalWithDecoder(_ *ag_binary.Decoder) error { return nil }

func NewFinalizeInstruction(account ag_solanago.PublicKey) *Finalize {
return NewFinalizeInstructionBuilder().
SetAccount(account).
SetRentSysvar(ag_solanago.SysVarRentPubkey)
}
56 changes: 56 additions & 0 deletions programs/loader-v2/Finalize_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// 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 loaderv2

import (
"testing"

ag_solanago "github.com/gagliardetto/solana-go"
"github.com/stretchr/testify/require"
)

// TestFinalize_Instruction_Bincode reproduces:
//
// loader_v2_interface::finalize(&account, &program_id)
//
// which emits just the u32 LE discriminant (1).
func TestFinalize_Instruction_Bincode(t *testing.T) {
account := ag_solanago.MustPublicKeyFromBase58("7QcXLBB23bJ4q5QUXpxLkQBr37g8mNEPSSPyVvU22qUS")

inst, err := NewFinalizeInstruction(account).ValidateAndBuild()
require.NoError(t, err)

data, err := inst.Data()
require.NoError(t, err)

require.Equal(t, []byte{0x01, 0x00, 0x00, 0x00}, data)
}

// TestFinalize_Accounts mirrors the Rust helper: [target: W+S, rent: R].
func TestFinalize_Accounts(t *testing.T) {
account := ag_solanago.MustPublicKeyFromBase58("7QcXLBB23bJ4q5QUXpxLkQBr37g8mNEPSSPyVvU22qUS")
inst := NewFinalizeInstruction(account).Build()

accounts := inst.Accounts()
require.Len(t, accounts, 2)

require.Equal(t, account, accounts[0].PublicKey)
require.True(t, accounts[0].IsWritable)
require.True(t, accounts[0].IsSigner)

require.Equal(t, ag_solanago.SysVarRentPubkey, accounts[1].PublicKey)
require.False(t, accounts[1].IsWritable)
require.False(t, accounts[1].IsSigner)
}
153 changes: 153 additions & 0 deletions programs/loader-v2/Write.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
// 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 loaderv2

import (
"encoding/binary"
"errors"
"fmt"
"slices"

ag_binary "github.com/gagliardetto/binary"
ag_solanago "github.com/gagliardetto/solana-go"
ag_format "github.com/gagliardetto/solana-go/text/format"
ag_treeout "github.com/gagliardetto/treeout"
)

// Write program data into an account.
//
// Account references:
//
// [0] = [WRITE, SIGNER] Account to write to
type Write struct {
Offset *uint32
Bytes []byte

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

func NewWriteInstructionBuilder() *Write {
return &Write{
AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 1),
}
}

func (inst *Write) SetOffset(offset uint32) *Write {
inst.Offset = &offset
return inst
}

func (inst *Write) SetBytes(data []byte) *Write {
inst.Bytes = data
return inst
}

func (inst *Write) SetAccount(account ag_solanago.PublicKey) *Write {
inst.AccountMetaSlice[0] = ag_solanago.Meta(account).WRITE().SIGNER()
return inst
}

func (inst *Write) GetAccount() *ag_solanago.AccountMeta {
return inst.AccountMetaSlice[0]
}

func (inst Write) Build() *Instruction {
return &Instruction{BaseVariant: ag_binary.BaseVariant{
Impl: inst,
TypeID: ag_binary.TypeIDFromUint32(Instruction_Write, binary.LittleEndian),
}}
}

func (inst Write) ValidateAndBuild() (*Instruction, error) {
if err := inst.Validate(); err != nil {
return nil, err
}
return inst.Build(), nil
}

func (inst *Write) Validate() error {
if inst.Offset == nil {
return errors.New("offset parameter is not set")
}
for i, acc := range inst.AccountMetaSlice {
if acc == nil {
return fmt.Errorf("ins.AccountMetaSlice[%v] is not set", i)
}
}
return nil
}

func (inst *Write) EncodeToTree(parent ag_treeout.Branches) {
parent.Child(ag_format.Program(ProgramName, ProgramID)).
ParentFunc(func(programBranch ag_treeout.Branches) {
programBranch.Child(ag_format.Instruction("Write")).
ParentFunc(func(instructionBranch ag_treeout.Branches) {
instructionBranch.Child("Params").ParentFunc(func(p ag_treeout.Branches) {
p.Child(ag_format.Param("Offset", *inst.Offset))
p.Child(ag_format.Param(" Bytes", fmt.Sprintf("%d bytes", len(inst.Bytes))))
})
instructionBranch.Child("Accounts").ParentFunc(func(a ag_treeout.Branches) {
a.Child(ag_format.Meta("Account", inst.AccountMetaSlice[0]))
})
})
})
}

// MarshalWithEncoder emits bincode-compatible bytes:
//
// [offset: u32 LE][len(bytes): u64 LE][bytes...]
//
// ag_binary's default slice encoder uses UVarInt for the length, which does
// not match bincode, so Vec<u8> is serialized manually.
func (inst Write) MarshalWithEncoder(encoder *ag_binary.Encoder) error {
if err := encoder.WriteUint32(*inst.Offset, binary.LittleEndian); err != nil {
return err
}
if err := encoder.WriteUint64(uint64(len(inst.Bytes)), binary.LittleEndian); err != nil {
return err
}
return encoder.WriteBytes(inst.Bytes, false)
}

func (inst *Write) UnmarshalWithDecoder(decoder *ag_binary.Decoder) error {
offset, err := decoder.ReadUint32(binary.LittleEndian)
if err != nil {
return err
}
inst.Offset = &offset
length, err := decoder.ReadUint64(binary.LittleEndian)
if err != nil {
return err
}
bts, err := decoder.ReadNBytes(int(length))
if err != nil {
return err
}
// Clone: ReadNBytes returns a subslice of the decoder's input, so
// retaining it here would alias whatever buffer the caller passed in.
inst.Bytes = slices.Clone(bts)
return nil
}

func NewWriteInstruction(
offset uint32,
data []byte,
account ag_solanago.PublicKey,
) *Write {
return NewWriteInstructionBuilder().
SetOffset(offset).
SetBytes(data).
SetAccount(account)
}
Loading
Loading