diff --git a/programs/stake/Authorized.go b/programs/stake/Authorized.go new file mode 100644 index 00000000..d40162f0 --- /dev/null +++ b/programs/stake/Authorized.go @@ -0,0 +1,71 @@ +// Copyright 2024 github.com/cordialsys +// +// 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 stake + +import ( + "errors" + + bin "github.com/gagliardetto/binary" + ag_solanago "github.com/gagliardetto/solana-go" +) + +type Authorized struct { + // Address that will own the stake account + Staker *ag_solanago.PublicKey + // Address that is permitted to with from the stake account + Withdrawer *ag_solanago.PublicKey +} + +func (auth *Authorized) UnmarshalWithDecoder(dec *bin.Decoder) error { + { + err := dec.Decode(&auth.Staker) + if err != nil { + return err + } + } + { + err := dec.Decode(&auth.Withdrawer) + if err != nil { + return err + } + } + return nil +} + +func (auth *Authorized) MarshalWithEncoder(encoder *bin.Encoder) error { + { + err := encoder.Encode(*auth.Staker) + if err != nil { + return err + } + } + { + err := encoder.Encode(*auth.Withdrawer) + if err != nil { + return err + } + } + return nil +} + +func (auth *Authorized) Validate() error { + if auth.Staker == nil { + return errors.New("staker parameter is not set") + } + if auth.Withdrawer == nil { + return errors.New("withdrawer parameter is not set") + } + return nil +} diff --git a/programs/stake/Deactivate.go b/programs/stake/Deactivate.go new file mode 100644 index 00000000..6cbf54fc --- /dev/null +++ b/programs/stake/Deactivate.go @@ -0,0 +1,120 @@ +// Copyright 2024 github.com/cordialsys +// +// 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 stake + +import ( + "fmt" + + bin "github.com/gagliardetto/binary" + "github.com/gagliardetto/solana-go" + "github.com/gagliardetto/solana-go/text/format" + "github.com/gagliardetto/treeout" +) + +type Deactivate struct { + + // [0] = [WRITE] Stake Account + // ··········· Delegated stake account to be deactivated + // + // [1] = [] Clock Sysvar + // ··········· The Clock Sysvar Account + // + // [2] = [SIGNER] Stake Authority + // ··········· Stake authority + // + solana.AccountMetaSlice `bin:"-" borsh_skip:"true"` +} + +func (inst *Deactivate) Validate() error { + + // Check whether all accounts are set: + for accIndex, acc := range inst.AccountMetaSlice { + if acc == nil { + return fmt.Errorf("ins.AccountMetaSlice[%v] is not set", accIndex) + } + } + return nil +} +func (inst *Deactivate) SetStakeAccount(stakeAccount solana.PublicKey) *Deactivate { + inst.AccountMetaSlice[0] = solana.Meta(stakeAccount).WRITE() + return inst +} +func (inst *Deactivate) SetClockSysvar(clockSysvar solana.PublicKey) *Deactivate { + inst.AccountMetaSlice[1] = solana.Meta(clockSysvar) + return inst +} +func (inst *Deactivate) SetStakeAuthority(stakeAuthority solana.PublicKey) *Deactivate { + inst.AccountMetaSlice[2] = solana.Meta(stakeAuthority).SIGNER() + return inst +} + +func (inst *Deactivate) GetStakeAccount() *solana.AccountMeta { + return inst.AccountMetaSlice[0] +} +func (inst *Deactivate) GetClockSysvar() *solana.AccountMeta { + return inst.AccountMetaSlice[1] +} +func (inst *Deactivate) GetStakeAuthority() *solana.AccountMeta { + return inst.AccountMetaSlice[2] +} + +func (inst Deactivate) Build() *Instruction { + return &Instruction{BaseVariant: bin.BaseVariant{ + Impl: inst, + TypeID: bin.TypeIDFromUint32(Instruction_Deactivate, bin.LE), + }} +} + +func (inst *Deactivate) EncodeToTree(parent treeout.Branches) { + parent.Child(format.Program(ProgramName, ProgramID)). + // + ParentFunc(func(programBranch treeout.Branches) { + programBranch.Child(format.Instruction("Deactivate")). + // + ParentFunc(func(instructionBranch treeout.Branches) { + // Parameters of the instruction: + instructionBranch.Child("Params").ParentFunc(func(paramsBranch treeout.Branches) { + }) + + // Accounts of the instruction: + instructionBranch.Child("Accounts").ParentFunc(func(accountsBranch treeout.Branches) { + accountsBranch.Child(format.Meta(" StakeAccount", inst.AccountMetaSlice.Get(0))) + accountsBranch.Child(format.Meta(" ClockSysvar", inst.AccountMetaSlice.Get(1))) + accountsBranch.Child(format.Meta(" StakeAuthoriy", inst.AccountMetaSlice.Get(2))) + }) + }) + }) +} + +// NewDeactivateInstructionBuilder creates a new `Deactivate` instruction builder. +func NewDeactivateInstructionBuilder() *Deactivate { + nd := &Deactivate{ + AccountMetaSlice: make(solana.AccountMetaSlice, 3), + } + return nd +} + +// NewDeactivateInstruction declares a new Deactivate instruction with the provided parameters and accounts. +func NewDeactivateInstruction( + // Params: + // Accounts: + stakeAccount solana.PublicKey, + stakeAuthority solana.PublicKey, +) *Deactivate { + return NewDeactivateInstructionBuilder(). + SetStakeAccount(stakeAccount). + SetClockSysvar(solana.SysVarClockPubkey). + SetStakeAuthority(stakeAuthority) +} diff --git a/programs/stake/DelegateStake.go b/programs/stake/DelegateStake.go new file mode 100644 index 00000000..e2983058 --- /dev/null +++ b/programs/stake/DelegateStake.go @@ -0,0 +1,143 @@ +// Copyright 2024 github.com/cordialsys +// +// 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 stake + +import ( + "fmt" + + bin "github.com/gagliardetto/binary" + "github.com/gagliardetto/solana-go" + "github.com/gagliardetto/solana-go/text/format" + "github.com/gagliardetto/treeout" +) + +type DelegateStake struct { + // [0] = [WRITE SIGNER] StakeAccount + // ··········· Stake account getting initialized + // + // [1] = [] Vote Account + // ··········· The validator vote account being delegated to + // + // [2] = [] Clock Sysvar + // ··········· The Clock Sysvar Account + // + // [3] = [] Stake History Sysvar + // ··········· The Stake History Sysvar Account + // + // [4] = [] Stake Config Account + // ··········· The Stake Config Account + // + // [5] = [WRITE SIGNER] Stake Authoriy + // ··········· The Stake Authority + // + solana.AccountMetaSlice `bin:"-" borsh_skip:"true"` +} + +func (inst *DelegateStake) Validate() error { + // Check whether all accounts are set: + for accIndex, acc := range inst.AccountMetaSlice { + if acc == nil { + return fmt.Errorf("ins.AccountMetaSlice[%v] is not set", accIndex) + } + } + return nil +} +func (inst *DelegateStake) SetStakeAccount(stakeAccount solana.PublicKey) *DelegateStake { + inst.AccountMetaSlice[0] = solana.Meta(stakeAccount).WRITE().SIGNER() + return inst +} +func (inst *DelegateStake) SetVoteAccount(voteAcc solana.PublicKey) *DelegateStake { + inst.AccountMetaSlice[1] = solana.Meta(voteAcc) + return inst +} +func (inst *DelegateStake) SetClockSysvar(clockSysVarAcc solana.PublicKey) *DelegateStake { + inst.AccountMetaSlice[2] = solana.Meta(clockSysVarAcc) + return inst +} +func (inst *DelegateStake) SetStakeHistorySysvar(stakeHistorySysVarAcc solana.PublicKey) *DelegateStake { + inst.AccountMetaSlice[3] = solana.Meta(stakeHistorySysVarAcc) + return inst +} +func (inst *DelegateStake) SetConfigAccount(stakeConfigAcc solana.PublicKey) *DelegateStake { + inst.AccountMetaSlice[4] = solana.Meta(stakeConfigAcc) + return inst +} +func (inst *DelegateStake) SetStakeAuthority(stakeAuthority solana.PublicKey) *DelegateStake { + inst.AccountMetaSlice[5] = solana.Meta(stakeAuthority).WRITE().SIGNER() + return inst +} +func (inst *DelegateStake) GetStakeAccount() *solana.AccountMeta { return inst.AccountMetaSlice[0] } +func (inst *DelegateStake) GetVoteAccount() *solana.AccountMeta { return inst.AccountMetaSlice[1] } +func (inst *DelegateStake) GetClockSysvar() *solana.AccountMeta { return inst.AccountMetaSlice[2] } +func (inst *DelegateStake) GetStakeHistorySysvar() *solana.AccountMeta { + return inst.AccountMetaSlice[3] +} +func (inst *DelegateStake) GetConfigAccount() *solana.AccountMeta { return inst.AccountMetaSlice[4] } +func (inst *DelegateStake) GetStakeAuthority() *solana.AccountMeta { return inst.AccountMetaSlice[5] } + +func (inst DelegateStake) Build() *Instruction { + return &Instruction{BaseVariant: bin.BaseVariant{ + Impl: inst, + TypeID: bin.TypeIDFromUint32(Instruction_DelegateStake, bin.LE), + }} +} + +func (inst *DelegateStake) EncodeToTree(parent treeout.Branches) { + parent.Child(format.Program(ProgramName, ProgramID)). + // + ParentFunc(func(programBranch treeout.Branches) { + programBranch.Child(format.Instruction("DelegateStake")). + // + ParentFunc(func(instructionBranch treeout.Branches) { + // Parameters of the instruction: + instructionBranch.Child("Params").ParentFunc(func(paramsBranch treeout.Branches) { + }) + + // Accounts of the instruction: + instructionBranch.Child("Accounts").ParentFunc(func(accountsBranch treeout.Branches) { + accountsBranch.Child(format.Meta(" StakeAccount", inst.AccountMetaSlice.Get(0))) + accountsBranch.Child(format.Meta(" VoteAccount", inst.AccountMetaSlice.Get(1))) + accountsBranch.Child(format.Meta(" ClockSysvar", inst.AccountMetaSlice.Get(2))) + accountsBranch.Child(format.Meta(" StakeHistorySysvar", inst.AccountMetaSlice.Get(3))) + accountsBranch.Child(format.Meta(" StakeConfigAccount", inst.AccountMetaSlice.Get(4))) + accountsBranch.Child(format.Meta(" StakeAuthoriy", inst.AccountMetaSlice.Get(5))) + }) + }) + }) +} + +// NewDelegateStakeInstructionBuilder creates a new `DelegateStake` instruction builder. +func NewDelegateStakeInstructionBuilder() *DelegateStake { + nd := &DelegateStake{ + AccountMetaSlice: make(solana.AccountMetaSlice, 6), + } + return nd +} + +// NewDelegateStakeInstruction declares a new DelegateStake instruction with the provided parameters and accounts. +func NewDelegateStakeInstruction( + // Accounts: + validatorVoteAccount solana.PublicKey, + stakeAuthority solana.PublicKey, + stakeAccount solana.PublicKey, +) *DelegateStake { + return NewDelegateStakeInstructionBuilder(). + SetStakeAccount(stakeAccount). + SetVoteAccount(validatorVoteAccount). + SetClockSysvar(solana.SysVarClockPubkey). + SetStakeHistorySysvar(solana.SysVarStakeHistoryPubkey). + SetConfigAccount(solana.SysVarStakeConfigPubkey). + SetStakeAuthority(stakeAuthority) +} diff --git a/programs/stake/Initialize.go b/programs/stake/Initialize.go new file mode 100644 index 00000000..4f67ff94 --- /dev/null +++ b/programs/stake/Initialize.go @@ -0,0 +1,206 @@ +// Copyright 2024 github.com/cordialsys +// +// 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 stake + +import ( + "errors" + "fmt" + + bin "github.com/gagliardetto/binary" + "github.com/gagliardetto/solana-go" + "github.com/gagliardetto/solana-go/text/format" + "github.com/gagliardetto/treeout" +) + +type Initialize struct { + // Authorization settings for stake account + Authorized *Authorized + + // Lockup settings for stake account + Lockup *Lockup + + // [0] = [WRITE SIGNER] StakeAccount + // ··········· Stake account getting initialized + // + // [1] = [] RentSysvar + // ··········· RentSysvar account + // + solana.AccountMetaSlice `bin:"-" borsh_skip:"true"` +} + +func (inst *Initialize) UnmarshalWithDecoder(dec *bin.Decoder) error { + { + err := dec.Decode(&inst.Authorized) + if err != nil { + return err + } + } + { + err := dec.Decode(&inst.Lockup) + if err != nil { + return err + } + } + return nil +} + +func (inst *Initialize) MarshalWithEncoder(encoder *bin.Encoder) error { + { + err := encoder.Encode(*inst.Authorized) + if err != nil { + return err + } + } + { + err := encoder.Encode(*inst.Lockup) + if err != nil { + return err + } + } + return nil +} + +func (inst *Initialize) Validate() error { + // Check whether all (required) parameters are set: + { + if inst.Authorized == nil { + return errors.New("authorized parameter is not set") + } + err := inst.Authorized.Validate() + if err != nil { + return err + } + } + { + if inst.Lockup == nil { + return errors.New("lockup parameter is not set") + } + err := inst.Lockup.Validate() + if err != nil { + return err + } + } + + // Check whether all accounts are set: + for accIndex, acc := range inst.AccountMetaSlice { + if acc == nil { + return fmt.Errorf("ins.AccountMetaSlice[%v] is not set", accIndex) + } + } + return nil +} + +// Stake account account +func (inst *Initialize) SetStakeAccount(stakeAccount solana.PublicKey) *Initialize { + inst.AccountMetaSlice[0] = solana.Meta(stakeAccount).WRITE().SIGNER() + return inst +} + +// Rent sysvar account +func (inst *Initialize) SetRentSysvarAccount(rentSysvar solana.PublicKey) *Initialize { + inst.AccountMetaSlice[1] = solana.Meta(rentSysvar) + return inst +} +func (inst *Initialize) GetStakeAccount() *solana.AccountMeta { return inst.AccountMetaSlice[0] } +func (inst *Initialize) GetRentSysvarAccount() *solana.AccountMeta { return inst.AccountMetaSlice[1] } + +func (inst *Initialize) SetStaker(staker solana.PublicKey) *Initialize { + inst.Authorized.Staker = &staker + return inst +} + +func (inst *Initialize) SetWithdrawer(withdrawer solana.PublicKey) *Initialize { + inst.Authorized.Withdrawer = &withdrawer + return inst +} + +func (inst *Initialize) SetLockupTimestamp(unixTimestamp int64) *Initialize { + inst.Lockup.UnixTimestamp = &unixTimestamp + return inst +} + +func (inst *Initialize) SetLockupEpoch(epoch uint64) *Initialize { + inst.Lockup.Epoch = &epoch + return inst +} + +func (inst *Initialize) SetCustodian(custodian solana.PublicKey) *Initialize { + inst.Lockup.Custodian = &custodian + return inst +} + +func (inst Initialize) Build() *Instruction { + return &Instruction{BaseVariant: bin.BaseVariant{ + Impl: inst, + TypeID: bin.TypeIDFromUint32(Instruction_Initialize, bin.LE), + }} +} + +func (inst *Initialize) EncodeToTree(parent treeout.Branches) { + parent.Child(format.Program(ProgramName, ProgramID)). + // + ParentFunc(func(programBranch treeout.Branches) { + programBranch.Child(format.Instruction("Initialize")). + // + ParentFunc(func(instructionBranch treeout.Branches) { + // Parameters of the instruction: + instructionBranch.Child("Params").ParentFunc(func(paramsBranch treeout.Branches) { + paramsBranch.Child("Authorized").ParentFunc(func(authBranch treeout.Branches) { + authBranch.Child(format.Account(" Staker", *inst.Authorized.Staker)) + authBranch.Child(format.Account("Withdrawer", *inst.Authorized.Withdrawer)) + }) + paramsBranch.Child("Lockup").ParentFunc(func(authBranch treeout.Branches) { + authBranch.Child(format.Param("UnixTimestamp", inst.Lockup.UnixTimestamp)) + authBranch.Child(format.Param(" Epoch", inst.Lockup.Epoch)) + authBranch.Child(format.Account(" Custodian", *inst.Lockup.Custodian)) + }) + }) + + // Accounts of the instruction: + instructionBranch.Child("Accounts").ParentFunc(func(accountsBranch treeout.Branches) { + accountsBranch.Child(format.Meta(" StakeAccount", inst.AccountMetaSlice.Get(0))) + accountsBranch.Child(format.Meta(" RentSysvar", inst.AccountMetaSlice.Get(1))) + }) + }) + }) +} + +// NewInitializeInstructionBuilder creates a new `Initialize` instruction builder. +func NewInitializeInstructionBuilder() *Initialize { + nd := &Initialize{ + AccountMetaSlice: make(solana.AccountMetaSlice, 2), + Authorized: &Authorized{}, + Lockup: &Lockup{}, + } + return nd +} + +// NewInitializeInstruction declares a new Initialize instruction with the provided parameters and accounts. +func NewInitializeInstruction( + // parameters: + staker solana.PublicKey, + withdrawer solana.PublicKey, + // Accounts: + stakeAccount solana.PublicKey, +) *Initialize { + return NewInitializeInstructionBuilder(). + SetStakeAccount(stakeAccount). + SetRentSysvarAccount(solana.SysVarRentPubkey). + SetStaker(staker). + SetWithdrawer(withdrawer). + SetLockupEpoch(0). + SetLockupTimestamp(0). + SetCustodian(solana.SystemProgramID) +} diff --git a/programs/stake/Lockup.go b/programs/stake/Lockup.go new file mode 100644 index 00000000..204667ec --- /dev/null +++ b/programs/stake/Lockup.go @@ -0,0 +1,88 @@ +// Copyright 2024 github.com/cordialsys +// +// 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 stake + +import ( + "errors" + + bin "github.com/gagliardetto/binary" + ag_solanago "github.com/gagliardetto/solana-go" +) + +type Lockup struct { + // UnixTimestamp at which this stake will allow withdrawal, unless the transaction is signed by the custodian + UnixTimestamp *int64 + // Epoch height at which this stake will allow withdrawal, unless the transaction is signed by the custodian + Epoch *uint64 + // Custodian signature on a transaction exempts the operation from lockup constraints + Custodian *ag_solanago.PublicKey +} + +func (lockup *Lockup) UnmarshalWithDecoder(dec *bin.Decoder) error { + { + err := dec.Decode(&lockup.UnixTimestamp) + if err != nil { + return err + } + } + { + err := dec.Decode(&lockup.Epoch) + if err != nil { + return err + } + } + { + err := dec.Decode(&lockup.Custodian) + if err != nil { + return err + } + } + return nil +} + +func (lockup *Lockup) MarshalWithEncoder(encoder *bin.Encoder) error { + { + err := encoder.Encode(*lockup.UnixTimestamp) + if err != nil { + return err + } + } + { + err := encoder.Encode(*lockup.Epoch) + if err != nil { + return err + } + } + { + err := encoder.Encode(*lockup.Custodian) + if err != nil { + return err + } + } + return nil +} + +func (lockup *Lockup) Validate() error { + if lockup.Custodian == nil { + return errors.New("custodian parameter is not set") + } + if lockup.Epoch == nil { + return errors.New("epoch parameter is not set") + } + if lockup.UnixTimestamp == nil { + return errors.New("unix timestamp parameter is not set") + } + return nil +} diff --git a/programs/stake/Split.go b/programs/stake/Split.go new file mode 100644 index 00000000..7d656f91 --- /dev/null +++ b/programs/stake/Split.go @@ -0,0 +1,150 @@ +// Copyright 2024 github.com/cordialsys +// +// 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 stake + +import ( + "errors" + "fmt" + + bin "github.com/gagliardetto/binary" + "github.com/gagliardetto/solana-go" + "github.com/gagliardetto/solana-go/text/format" + "github.com/gagliardetto/treeout" +) + +type Split struct { + // Amount to split to new stake account + Lamports *uint64 + // [0] = [WRITE] Stake Account + // ··········· Stake account to be split; must be in the Initialized or Stake state + // + // [1] = [WRITE] New Stake Account + // ··········· Uninitialized stake account that will take the split-off amount + // + // [2] = [SIGNER] Stake Authority + // ··········· Stake authority + // + solana.AccountMetaSlice `bin:"-" borsh_skip:"true"` +} + +func (inst *Split) Validate() error { + { + if inst.Lamports == nil { + return errors.New("lamports parameter is not set") + } + } + // Check whether all accounts are set: + for accIndex, acc := range inst.AccountMetaSlice { + if acc == nil { + return fmt.Errorf("ins.AccountMetaSlice[%v] is not set", accIndex) + } + } + return nil +} +func (inst *Split) SetStakeAccount(stakeAccount solana.PublicKey) *Split { + inst.AccountMetaSlice[0] = solana.Meta(stakeAccount).WRITE() + return inst +} +func (inst *Split) SetNewStakeAccount(voteAcc solana.PublicKey) *Split { + inst.AccountMetaSlice[1] = solana.Meta(voteAcc).WRITE() + return inst +} + +func (inst *Split) SetStakeAuthority(stakeAuthority solana.PublicKey) *Split { + inst.AccountMetaSlice[2] = solana.Meta(stakeAuthority).SIGNER() + return inst +} + +func (inst *Split) GetStakeAccount() *solana.AccountMeta { return inst.AccountMetaSlice[0] } +func (inst *Split) GetNewStakeAccount() *solana.AccountMeta { return inst.AccountMetaSlice[1] } +func (inst *Split) GetStakeAuthority() *solana.AccountMeta { return inst.AccountMetaSlice[2] } + +func (inst *Split) SetLamports(lamports uint64) *Split { + inst.Lamports = &lamports + return inst +} + +func (inst *Split) UnmarshalWithDecoder(dec *bin.Decoder) error { + { + err := dec.Decode(&inst.Lamports) + if err != nil { + return err + } + } + return nil +} + +func (inst *Split) MarshalWithEncoder(encoder *bin.Encoder) error { + { + err := encoder.Encode(*inst.Lamports) + if err != nil { + return err + } + } + return nil +} + +func (inst Split) Build() *Instruction { + return &Instruction{BaseVariant: bin.BaseVariant{ + Impl: inst, + TypeID: bin.TypeIDFromUint32(Instruction_Split, bin.LE), + }} +} + +func (inst *Split) EncodeToTree(parent treeout.Branches) { + parent.Child(format.Program(ProgramName, ProgramID)). + // + ParentFunc(func(programBranch treeout.Branches) { + programBranch.Child(format.Instruction("Split")). + // + ParentFunc(func(instructionBranch treeout.Branches) { + // Parameters of the instruction: + instructionBranch.Child("Params").ParentFunc(func(paramsBranch treeout.Branches) { + paramsBranch.Child(format.Param("Lamports", inst.Lamports)) + }) + + // Accounts of the instruction: + instructionBranch.Child("Accounts").ParentFunc(func(accountsBranch treeout.Branches) { + accountsBranch.Child(format.Meta(" StakeAccount", inst.AccountMetaSlice.Get(0))) + accountsBranch.Child(format.Meta(" NewStakeAccount", inst.AccountMetaSlice.Get(1))) + accountsBranch.Child(format.Meta(" StakeAuthoriy", inst.AccountMetaSlice.Get(2))) + }) + }) + }) +} + +// NewSplitInstructionBuilder creates a new `Split` instruction builder. +func NewSplitInstructionBuilder() *Split { + nd := &Split{ + AccountMetaSlice: make(solana.AccountMetaSlice, 3), + } + return nd +} + +// NewSplitInstruction declares a new Split instruction with the provided parameters and accounts. +func NewSplitInstruction( + // Params: + lamports uint64, + // Accounts: + stakeAccount solana.PublicKey, + newStakeAccount solana.PublicKey, + stakeAuthority solana.PublicKey, +) *Split { + return NewSplitInstructionBuilder(). + SetLamports(lamports). + SetStakeAccount(stakeAccount). + SetNewStakeAccount(newStakeAccount). + SetStakeAuthority(stakeAuthority) +} diff --git a/programs/stake/Withdraw.go b/programs/stake/Withdraw.go new file mode 100644 index 00000000..85e1756d --- /dev/null +++ b/programs/stake/Withdraw.go @@ -0,0 +1,173 @@ +// Copyright 2024 github.com/cordialsys +// +// 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 stake + +import ( + "errors" + "fmt" + + bin "github.com/gagliardetto/binary" + "github.com/gagliardetto/solana-go" + "github.com/gagliardetto/solana-go/text/format" + "github.com/gagliardetto/treeout" +) + +type Withdraw struct { + // Withdraw unstaked lamports from the stake account + Lamports *uint64 + // [0] = [WRITE] Stake Account + // ··········· Stake account from which to withdraw + // + // [1] = [WRITE] Recipient Account + // ··········· Recipient account + // + // [2] = [] Clock Sysvar + // ··········· The Clock Sysvar Account + // + // [3] = [] Stake History Sysvar + // ··········· The Stake History Sysvar Account + // + // [4] = [SIGNER] Withdraw Authority + // ··········· Withdraw authority + // + // OPTIONAL: + // [5] = [SIGNER] Lockup authority + // ··········· If before lockup expiration + solana.AccountMetaSlice `bin:"-" borsh_skip:"true"` +} + +func (inst *Withdraw) Validate() error { + { + if inst.Lamports == nil { + return errors.New("lamports parameter is not set") + } + } + // Check whether all accounts are set: + for accIndex, acc := range inst.AccountMetaSlice { + if acc == nil { + return fmt.Errorf("ins.AccountMetaSlice[%v] is not set", accIndex) + } + } + return nil +} +func (inst *Withdraw) SetStakeAccount(stakeAccount solana.PublicKey) *Withdraw { + inst.AccountMetaSlice[0] = solana.Meta(stakeAccount).WRITE() + return inst +} +func (inst *Withdraw) SetRecipientAccount(recipient solana.PublicKey) *Withdraw { + inst.AccountMetaSlice[1] = solana.Meta(recipient).WRITE() + return inst +} +func (inst *Withdraw) SetClockSysvar(clockSysvar solana.PublicKey) *Withdraw { + inst.AccountMetaSlice[2] = solana.Meta(clockSysvar) + return inst +} +func (inst *Withdraw) SetStakeHistorySysvar(historySysvar solana.PublicKey) *Withdraw { + inst.AccountMetaSlice[3] = solana.Meta(historySysvar) + return inst +} + +func (inst *Withdraw) SetWithdrawAuthority(withdrawAuthority solana.PublicKey) *Withdraw { + inst.AccountMetaSlice[4] = solana.Meta(withdrawAuthority).SIGNER() + return inst +} + +func (inst *Withdraw) GetStakeAccount() *solana.AccountMeta { return inst.AccountMetaSlice[0] } +func (inst *Withdraw) GetRecipientAccount() *solana.AccountMeta { return inst.AccountMetaSlice[1] } +func (inst *Withdraw) GetClockSysvar() *solana.AccountMeta { return inst.AccountMetaSlice[2] } +func (inst *Withdraw) GetStakeHistorySysvar() *solana.AccountMeta { return inst.AccountMetaSlice[3] } +func (inst *Withdraw) GetWithdrawAuthority() *solana.AccountMeta { return inst.AccountMetaSlice[4] } + +func (inst *Withdraw) SetLamports(lamports uint64) *Withdraw { + inst.Lamports = &lamports + return inst +} + +func (inst *Withdraw) UnmarshalWithDecoder(dec *bin.Decoder) error { + { + err := dec.Decode(&inst.Lamports) + if err != nil { + return err + } + } + return nil +} + +func (inst *Withdraw) MarshalWithEncoder(encoder *bin.Encoder) error { + { + err := encoder.Encode(*inst.Lamports) + if err != nil { + return err + } + } + return nil +} + +func (inst Withdraw) Build() *Instruction { + return &Instruction{BaseVariant: bin.BaseVariant{ + Impl: inst, + TypeID: bin.TypeIDFromUint32(Instruction_Withdraw, bin.LE), + }} +} + +func (inst *Withdraw) EncodeToTree(parent treeout.Branches) { + parent.Child(format.Program(ProgramName, ProgramID)). + // + ParentFunc(func(programBranch treeout.Branches) { + programBranch.Child(format.Instruction("Withdraw")). + // + ParentFunc(func(instructionBranch treeout.Branches) { + // Parameters of the instruction: + instructionBranch.Child("Params").ParentFunc(func(paramsBranch treeout.Branches) { + paramsBranch.Child(format.Param("Lamports", inst.Lamports)) + }) + + // Accounts of the instruction: + instructionBranch.Child("Accounts").ParentFunc(func(accountsBranch treeout.Branches) { + accountsBranch.Child(format.Meta(" StakeAccount", inst.AccountMetaSlice.Get(0))) + accountsBranch.Child(format.Meta(" RecipientAccount", inst.AccountMetaSlice.Get(1))) + accountsBranch.Child(format.Meta(" ClockSysvar", inst.AccountMetaSlice.Get(2))) + accountsBranch.Child(format.Meta(" StakeHistorySysvar", inst.AccountMetaSlice.Get(3))) + accountsBranch.Child(format.Meta(" WithdrawAuthority", inst.AccountMetaSlice.Get(4))) + }) + }) + }) +} + +// NewWithdrawInstructionBuilder creates a new `Withdraw` instruction builder. +func NewWithdrawInstructionBuilder() *Withdraw { + nd := &Withdraw{ + AccountMetaSlice: make(solana.AccountMetaSlice, 5), + } + return nd +} + +// NewWithdrawInstruction declares a new Withdraw instruction with the provided parameters and accounts. +func NewWithdrawInstruction( + // Params: + lamports uint64, + // Accounts: + stakeAccount solana.PublicKey, + recipient solana.PublicKey, + withdrawAuthority solana.PublicKey, +) *Withdraw { + return NewWithdrawInstructionBuilder(). + SetLamports(lamports). + SetStakeAccount(stakeAccount). + SetRecipientAccount(recipient). + SetClockSysvar(solana.SysVarClockPubkey). + SetStakeHistorySysvar(solana.SysVarStakeHistoryPubkey). + SetWithdrawAuthority(withdrawAuthority) +} diff --git a/programs/stake/instructions.go b/programs/stake/instructions.go new file mode 100644 index 00000000..2f8a2361 --- /dev/null +++ b/programs/stake/instructions.go @@ -0,0 +1,146 @@ +// Copyright 2021 github.com/gagliardetto +// Copyright 2024 github.com/cordialsys +// +// 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 stake + +import ( + "bytes" + "encoding/binary" + "fmt" + + "github.com/davecgh/go-spew/spew" + bin "github.com/gagliardetto/binary" + "github.com/gagliardetto/solana-go" + "github.com/gagliardetto/solana-go/text" + "github.com/gagliardetto/treeout" +) + +var ProgramID solana.PublicKey = solana.StakeProgramID + +func SetProgramID(pubkey solana.PublicKey) { + ProgramID = pubkey + solana.RegisterInstructionDecoder(ProgramID, registryDecodeInstruction) +} + +const ProgramName = "Stake" + +func init() { + solana.RegisterInstructionDecoder(ProgramID, registryDecodeInstruction) +} + +const ( + // Initializes a new stake account + Instruction_Initialize uint32 = iota + // Authorize a key to manage stake or withdrawal + Instruction_Authorize + // Delegate a stake account to a validator vote account + Instruction_DelegateStake + // Split an active stake account into a new stake account + Instruction_Split + // Withdraw unstaked lamports from the stake account + Instruction_Withdraw + // Deactivates the stake in the account + Instruction_Deactivate +) + +type Instruction struct { + bin.BaseVariant +} + +func (inst *Instruction) EncodeToTree(parent treeout.Branches) { + if enToTree, ok := inst.Impl.(text.EncodableToTree); ok { + enToTree.EncodeToTree(parent) + } else { + parent.Child(spew.Sdump(inst)) + } +} + +var InstructionImplDef = bin.NewVariantDefinition( + bin.Uint32TypeIDEncoding, + []bin.VariantType{ + { + "Initialize", (*Initialize)(nil), + }, + { + "Authorize", nil, + }, + { + "DelegateStake", (*DelegateStake)(nil), + }, + { + "Split", (*Split)(nil), + }, + { + "Withdraw", (*Withdraw)(nil), + }, + { + "Deactivate", (*Deactivate)(nil), + }, + }, +) + +func (inst *Instruction) ProgramID() solana.PublicKey { + return ProgramID +} + +func (inst *Instruction) Accounts() (out []*solana.AccountMeta) { + return inst.Impl.(solana.AccountsGettable).GetAccounts() +} + +func (inst *Instruction) Data() ([]byte, error) { + buf := new(bytes.Buffer) + if err := bin.NewBinEncoder(buf).Encode(inst); err != nil { + return nil, fmt.Errorf("unable to encode instruction: %w", err) + } + return buf.Bytes(), nil +} + +func (inst *Instruction) TextEncode(encoder *text.Encoder, option *text.Option) error { + return encoder.Encode(inst.Impl, option) +} + +func (inst *Instruction) UnmarshalWithDecoder(decoder *bin.Decoder) error { + return inst.BaseVariant.UnmarshalBinaryVariant(decoder, InstructionImplDef) +} + +func (inst Instruction) MarshalWithEncoder(encoder *bin.Encoder) error { + err := encoder.WriteUint32(inst.TypeID.Uint32(), binary.LittleEndian) + if err != nil { + return fmt.Errorf("unable to write variant type: %w", err) + } + return encoder.Encode(inst.Impl) +} + +func registryDecodeInstruction(accounts []*solana.AccountMeta, data []byte) (interface{}, error) { + inst, err := DecodeInstruction(accounts, data) + if err != nil { + return nil, err + } + return inst, nil +} + +func DecodeInstruction(accounts []*solana.AccountMeta, data []byte) (*Instruction, error) { + inst := new(Instruction) + if err := bin.NewBinDecoder(data).Decode(inst); err != nil { + return nil, fmt.Errorf("unable to decode instruction: %w", err) + } + if v, ok := inst.Impl.(solana.AccountsSettable); ok { + err := v.SetAccounts(accounts) + if err != nil { + return nil, fmt.Errorf("unable to set accounts for instruction: %w", err) + } + } + return inst, nil +} diff --git a/programs/vote/Withdraw.go b/programs/vote/Withdraw.go index aa7f5087..67517845 100644 --- a/programs/vote/Withdraw.go +++ b/programs/vote/Withdraw.go @@ -98,6 +98,12 @@ func (inst *Withdraw) SetWithdrawAuthorityAccount(withdrawAccount solana.PublicK return inst } +func (inst *Withdraw) GetVoteAccount() *solana.AccountMeta { return inst.AccountMetaSlice[0] } +func (inst *Withdraw) GetRecipientAccount() *solana.AccountMeta { return inst.AccountMetaSlice[1] } +func (inst *Withdraw) GetWithdrawAuthorityAccount() *solana.AccountMeta { + return inst.AccountMetaSlice[2] +} + // Number of lamports to transfer to the recipient account func (inst *Withdraw) SetLamports(lamports uint64) *Withdraw { inst.Lamports = &lamports diff --git a/sysvar.go b/sysvar.go index 99ebfe01..98484431 100644 --- a/sysvar.go +++ b/sysvar.go @@ -62,4 +62,7 @@ var ( // The StakeHistory sysvar contains the history of cluster-wide stake activations and de-activations per epoch. // It is updated at the start of every epoch. SysVarStakeHistoryPubkey = MustPublicKeyFromBase58("SysvarStakeHistory1111111111111111111111111") + + // The stake config account is used to store parameters and settings that govern the behavior of staking on the Solana network. + SysVarStakeConfigPubkey = MustPublicKeyFromBase58("StakeConfig11111111111111111111111111111111") )