From 2778e313aba3b2959302d54738973b7185bdd6ea Mon Sep 17 00:00:00 2001 From: Slavomir Date: Thu, 29 Jul 2021 17:19:13 +0200 Subject: [PATCH 01/34] Refactor system program --- account.go | 29 +++- account_test.go | 40 +++++ constants.go | 4 + interface.go | 6 +- programs/serum/instruction.go | 2 +- programs/system/AdvanceNonceAccount.go | 33 ++++ programs/system/Assign.go | 37 ++++ programs/system/CreateAccount.go | 44 +++++ programs/system/CreateAccountWithSeed.go | 70 ++++++++ programs/system/Transfer.go | 36 ++++ programs/system/WithdrawNonceAccount.go | 42 +++++ programs/system/instructions.go | 208 +++++++++-------------- programs/system/sysvar.go | 53 ++++++ programs/token/instructions.go | 2 +- programs/tokenregistry/instruction.go | 2 +- transaction.go | 16 +- 16 files changed, 484 insertions(+), 140 deletions(-) create mode 100644 programs/system/AdvanceNonceAccount.go create mode 100644 programs/system/Assign.go create mode 100644 programs/system/CreateAccount.go create mode 100644 programs/system/CreateAccountWithSeed.go create mode 100644 programs/system/Transfer.go create mode 100644 programs/system/WithdrawNonceAccount.go create mode 100644 programs/system/sysvar.go diff --git a/account.go b/account.go index 4b516164..dc2fb33b 100644 --- a/account.go +++ b/account.go @@ -34,8 +34,20 @@ func (a *Account) PublicKey() PublicKey { type AccountMeta struct { PublicKey PublicKey - IsSigner bool IsWritable bool + IsSigner bool +} + +func NewAccountMeta( + pubKey PublicKey, + WRITE bool, + SIGNER bool, +) *AccountMeta { + return &AccountMeta{ + PublicKey: pubKey, + IsWritable: WRITE, + IsSigner: SIGNER, + } } func (a *AccountMeta) less(act *AccountMeta) bool { @@ -50,3 +62,18 @@ func (a *AccountMeta) less(act *AccountMeta) bool { } return false } + +type AccountMetaSlice []*AccountMeta + +func (slice *AccountMetaSlice) Append(account *AccountMeta) { + *slice = append(*slice, account) +} + +func (slice *AccountMetaSlice) SetAccounts(accounts []*AccountMeta) error { + *slice = accounts + return nil +} + +func (slice AccountMetaSlice) GetAccounts() []*AccountMeta { + return slice +} diff --git a/account_test.go b/account_test.go index d393f090..a628ec43 100644 --- a/account_test.go +++ b/account_test.go @@ -77,5 +77,45 @@ func Test_AccountMeta_less(t *testing.T) { assert.Equal(t, test.expect, test.left.less(test.right)) }) } +} + +func TestAccountMetaSlice(t *testing.T) { + pkey1 := MustPublicKeyFromBase58("SysvarS1otHashes111111111111111111111111111") + + var slice AccountMetaSlice + + setting := []*AccountMeta{ + {PublicKey: pkey1, IsSigner: true, IsWritable: false}, + } + err := slice.SetAccounts(setting) + require.NoError(t, err) + + require.Len(t, slice, 1) + require.Equal(t, setting[0], slice[0]) + require.Equal(t, setting, slice.GetAccounts()) + + { + pkey2 := MustPublicKeyFromBase58("BPFLoaderUpgradeab1e11111111111111111111111") + + meta := NewAccountMeta(pkey2, true, false) + slice.Append(meta) + + require.Len(t, slice, 2) + require.Equal(t, meta, slice[1]) + require.Equal(t, meta, slice.GetAccounts()[1]) + } +} + +func TestNewAccountMeta(t *testing.T) { + pkey := MustPublicKeyFromBase58("SysvarS1otHashes111111111111111111111111111") + + isWritable := false + isSigner := true + + out := NewAccountMeta(pkey, isWritable, isSigner) + + require.NotNil(t, out) + require.Equal(t, isSigner, out.IsSigner) + require.Equal(t, isWritable, out.IsWritable) } diff --git a/constants.go b/constants.go index 7eea24b4..36ff01ae 100644 --- a/constants.go +++ b/constants.go @@ -1,5 +1,9 @@ package solana const ( + // There are 1-billion lamports in one SOL. LAMPORTS_PER_SOL uint64 = 1000000000 + + // Maximum length of derived pubkey seed. + MAX_SEED_LENGTH uint8 = 32 ) diff --git a/interface.go b/interface.go index 0bdfb50a..ae20895a 100644 --- a/interface.go +++ b/interface.go @@ -1,5 +1,9 @@ package solana -type AccountSettable interface { +type AccountsSettable interface { SetAccounts(accounts []*AccountMeta) error } + +type AccountsGettable interface { + GetAccounts() (accounts []*AccountMeta) +} diff --git a/programs/serum/instruction.go b/programs/serum/instruction.go index 6ab0490d..2f7cdee2 100644 --- a/programs/serum/instruction.go +++ b/programs/serum/instruction.go @@ -28,7 +28,7 @@ func DecodeInstruction(accounts []*solana.AccountMeta, data []byte) (*Instructio return nil, fmt.Errorf("unable to decode instruction for serum program: %w", err) } - if v, ok := inst.Impl.(solana.AccountSettable); ok { + 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) diff --git a/programs/system/AdvanceNonceAccount.go b/programs/system/AdvanceNonceAccount.go new file mode 100644 index 00000000..2252bafb --- /dev/null +++ b/programs/system/AdvanceNonceAccount.go @@ -0,0 +1,33 @@ +package system + +import ( + bin "github.com/dfuse-io/binary" + solana "github.com/gagliardetto/solana-go" +) + +func NewAdvanceNonceAccountInstruction( + nonceAccount solana.PublicKey, + nonceAuthority solana.PublicKey, +) *Instruction { + return &Instruction{ + BaseVariant: bin.BaseVariant{ + + TypeID: Instruction_AdvanceNonceAccount, + + Impl: &AdvanceNonceAccount{ + AccountMetaSlice: []*solana.AccountMeta{ + solana.NewAccountMeta(nonceAccount, true, false), + solana.NewAccountMeta(SysVarRecentBlockHashesPubkey, false, false), + solana.NewAccountMeta(nonceAuthority, true, false), + }, + }, + }, + } +} + +type AdvanceNonceAccount struct { + // [0] = [WRITE] Nonce account. + // [1] = [] RecentBlockhashes sysvar. + // [2] = [SIGNER] Nonce authority. + solana.AccountMetaSlice `bin:"-"` +} diff --git a/programs/system/Assign.go b/programs/system/Assign.go new file mode 100644 index 00000000..75a5d401 --- /dev/null +++ b/programs/system/Assign.go @@ -0,0 +1,37 @@ +package system + +import ( + bin "github.com/dfuse-io/binary" + solana "github.com/gagliardetto/solana-go" +) + +func NewAssignInstruction( + // The account that is being assigned. + accountPubkey solana.PublicKey, + // The program that is becoming the new owner of the account. + assignToProgramID solana.PublicKey, +) *Instruction { + return &Instruction{ + BaseVariant: bin.BaseVariant{ + + TypeID: Instruction_Assign, + + Impl: &Assign{ + + NewOwner: assignToProgramID, + + AccountMetaSlice: []*solana.AccountMeta{ + solana.NewAccountMeta(accountPubkey, true, true), + }, + }, + }, + } +} + +type Assign struct { + // Owner program account. + NewOwner solana.PublicKey + + // [0] = [WRITE, SIGNER] Assigned account public key. + solana.AccountMetaSlice `bin:"-"` +} diff --git a/programs/system/CreateAccount.go b/programs/system/CreateAccount.go new file mode 100644 index 00000000..62053cda --- /dev/null +++ b/programs/system/CreateAccount.go @@ -0,0 +1,44 @@ +package system + +import ( + bin "github.com/dfuse-io/binary" + solana "github.com/gagliardetto/solana-go" +) + +func NewCreateAccountInstruction( + lamports uint64, + space uint64, + owner solana.PublicKey, + + fundingAccount solana.PublicKey, + newAccount solana.PublicKey, +) *Instruction { + return &Instruction{ + BaseVariant: bin.BaseVariant{ + + TypeID: Instruction_CreateAccount, + + Impl: &CreateAccount{ + + Lamports: bin.Uint64(lamports), + Space: bin.Uint64(space), + Owner: owner, + + AccountMetaSlice: []*solana.AccountMeta{ + solana.NewAccountMeta(fundingAccount, true, true), + solana.NewAccountMeta(newAccount, true, true), + }, + }, + }, + } +} + +type CreateAccount struct { + Lamports bin.Uint64 + Space bin.Uint64 + Owner solana.PublicKey + + // [0] = [WRITE, SIGNER] Funding account. + // [1] = [WRITE, SIGNER] New account. + solana.AccountMetaSlice `bin:"-"` +} diff --git a/programs/system/CreateAccountWithSeed.go b/programs/system/CreateAccountWithSeed.go new file mode 100644 index 00000000..98b08a7f --- /dev/null +++ b/programs/system/CreateAccountWithSeed.go @@ -0,0 +1,70 @@ +package system + +import ( + bin "github.com/dfuse-io/binary" + solana "github.com/gagliardetto/solana-go" +) + +func NewCreateAccountWithSeedInstruction( + base solana.PublicKey, + seed string, + lamports uint64, + space uint64, + owner solana.PublicKey, + + fundingAccount solana.PublicKey, + newAccount solana.PublicKey, +) *Instruction { + return &Instruction{ + BaseVariant: bin.BaseVariant{ + + TypeID: Instruction_CreateAccountWithSeed, + + Impl: &CreateAccountWithSeed{ + + Base: base, + Seed: seed, + Lamports: bin.Uint64(lamports), + Space: bin.Uint64(space), + Owner: owner, + + AccountMetaSlice: func() solana.AccountMetaSlice { + res := solana.AccountMetaSlice{ + solana.NewAccountMeta(fundingAccount, true, true), + solana.NewAccountMeta(newAccount, true, false), + } + + if !base.Equals(fundingAccount) { + res.Append(solana.NewAccountMeta(base, false, true)) + } + + return res + }(), + }, + }, + } +} + +type CreateAccountWithSeed struct { + // Base public key. + Base solana.PublicKey + + SeedSize int `bin:"sizeof=Seed"` + // String of ASCII chars, no longer than solana.MAX_SEED_LEN + Seed string + + // Number of lamports to transfer to the new account. + Lamports bin.Uint64 + + // Number of bytes of memory to allocate + Space bin.Uint64 + + // Owner program account address. + Owner solana.PublicKey + + // [0] = [WRITE, SIGNER] Funding account. + // [1] = [WRITE] Created account. + // [2] = [SIGNER] Base account; the account matching the base Pubkey below must be provided as a signer, + // but may be the same as the funding account and provided as account 0 + solana.AccountMetaSlice `bin:"-"` +} diff --git a/programs/system/Transfer.go b/programs/system/Transfer.go new file mode 100644 index 00000000..7f67e2a1 --- /dev/null +++ b/programs/system/Transfer.go @@ -0,0 +1,36 @@ +package system + +import ( + bin "github.com/dfuse-io/binary" + solana "github.com/gagliardetto/solana-go" +) + +func NewTransferInstruction( + lamports uint64, + from solana.PublicKey, + to solana.PublicKey, +) *Instruction { + return &Instruction{ + BaseVariant: bin.BaseVariant{ + + TypeID: Instruction_Transfer, + + Impl: &Transfer{ + Lamports: bin.Uint64(lamports), + + AccountMetaSlice: []*solana.AccountMeta{ + solana.NewAccountMeta(from, true, true), + solana.NewAccountMeta(to, true, false), + }, + }, + }, + } +} + +type Transfer struct { + Lamports bin.Uint64 + + // [0] = [WRITE, SIGNER] Funding account. + // [1] = [WRITE] Recipient account. + solana.AccountMetaSlice `bin:"-"` +} diff --git a/programs/system/WithdrawNonceAccount.go b/programs/system/WithdrawNonceAccount.go new file mode 100644 index 00000000..af5b82a7 --- /dev/null +++ b/programs/system/WithdrawNonceAccount.go @@ -0,0 +1,42 @@ +package system + +import ( + bin "github.com/dfuse-io/binary" + solana "github.com/gagliardetto/solana-go" +) + +func NewWithdrawNonceAccountInstruction( + lamports uint64, + + // TODO: + nonceAccount solana.PublicKey, + nonceAuthority solana.PublicKey, +) *Instruction { + return &Instruction{ + BaseVariant: bin.BaseVariant{ + + TypeID: Instruction_WithdrawNonceAccount, + + Impl: &WithdrawNonceAccount{ + Lamports: bin.Uint64(lamports), + AccountMetaSlice: []*solana.AccountMeta{ + solana.NewAccountMeta(nonceAccount, true, false), + solana.NewAccountMeta(SysVarRecentBlockHashesPubkey, false, false), + solana.NewAccountMeta(nonceAuthority, true, false), + }, + }, + }, + } +} + +type WithdrawNonceAccount struct { + // The u64 parameter is the lamports to withdraw, which must leave the account balance above the rent exempt reserve or at zero. + Lamports bin.Uint64 + + // [0] = [WRITE] Nonce account. + // [1] = [WRITE] Recipient account. + // [2] = [] RecentBlockhashes sysvar. + // [3] = [] Rent sysvar. + // [4] = [SIGNER] Nonce authority. + solana.AccountMetaSlice `bin:"-"` +} diff --git a/programs/system/instructions.go b/programs/system/instructions.go index 3339c32b..9e8b73b9 100644 --- a/programs/system/instructions.go +++ b/programs/system/instructions.go @@ -30,84 +30,83 @@ func init() { solana.RegisterInstructionDecoder(PROGRAM_ID, registryDecodeInstruction) } -func registryDecodeInstruction(accounts []*solana.AccountMeta, data []byte) (interface{}, error) { - inst, err := DecodeInstruction(accounts, data) - if err != nil { - return nil, err - } - return inst, nil -} +const ( + // Create a new account. + Instruction_CreateAccount uint32 = iota -func DecodeInstruction(accounts []*solana.AccountMeta, data []byte) (*Instruction, error) { - var inst *Instruction - if err := bin.NewDecoder(data).Decode(&inst); err != nil { - return nil, fmt.Errorf("unable to decode instruction for serum program: %w", err) - } + // Assign account to a program. + Instruction_Assign - if v, ok := inst.Impl.(solana.AccountSettable); ok { - err := v.SetAccounts(accounts) - if err != nil { - return nil, fmt.Errorf("unable to set accounts for instruction: %w", err) - } - } + // Transfer lamports. + Instruction_Transfer - return inst, nil -} + // Create a new account at an address derived from a base pubkey and a seed. + Instruction_CreateAccountWithSeed -func NewCreateAccountInstruction(lamports uint64, space uint64, owner, from, to solana.PublicKey) *Instruction { - return &Instruction{ - BaseVariant: bin.BaseVariant{ - TypeID: 0, - Impl: &CreateAccount{ - Lamports: bin.Uint64(lamports), - Space: bin.Uint64(space), - Owner: owner, - Accounts: &CreateAccountAccounts{ - From: &solana.AccountMeta{PublicKey: from, IsSigner: true, IsWritable: true}, - New: &solana.AccountMeta{PublicKey: to, IsSigner: true, IsWritable: true}, - }, - }, - }, - } -} + // Consumes a stored nonce, replacing it with a successor. + Instruction_AdvanceNonceAccount -func NewTransferInstruction( - lamports uint64, - from solana.PublicKey, - to solana.PublicKey, -) *Instruction { - return &Instruction{ - BaseVariant: bin.BaseVariant{ - TypeID: 2, - Impl: &Transfer{ - Lamports: bin.Uint64(lamports), - Accounts: &TransferAccounts{ - From: &solana.AccountMeta{PublicKey: from, IsSigner: true, IsWritable: true}, - To: &solana.AccountMeta{PublicKey: to, IsSigner: false, IsWritable: true}, - }, - }, - }, - } -} + // Withdraw funds from a nonce account. + Instruction_WithdrawNonceAccount + + // Drive state of Uninitalized nonce account to Initialized, setting the nonce value. + Instruction_InitializeNonceAccount + + // Change the entity authorized to execute nonce instruction_s on the account. + Instruction_AuthorizeNonceAccount + + // Allocate space in a (possibly new) account without funding. + Instruction_Allocate + + // Allocate space for and assign an account at an address derived from a base public key and a seed. + Instruction_AllocateWithSeed + + // Assign account to a program based on a seed. + Instruction_AssignWithSeed + + // Transfer lamports from a derived address. + Instruction_TransferWithSeed +) type Instruction struct { bin.BaseVariant } -func (i *Instruction) Accounts() (out []*solana.AccountMeta) { - switch i.TypeID { - case 0: - accounts := i.Impl.(*CreateAccount).Accounts - out = []*solana.AccountMeta{accounts.From, accounts.New} - case 1: - // no account here - case 2: - accounts := i.Impl.(*Transfer).Accounts - out = []*solana.AccountMeta{accounts.From, accounts.To} - } - return +var ( + // TODO: each instruction must be here: + _ solana.AccountsGettable = &CreateAccount{} + _ solana.AccountsSettable = &CreateAccount{} + + _ solana.AccountsGettable = &Assign{} + _ solana.AccountsSettable = &Assign{} + + _ solana.AccountsGettable = &Transfer{} + _ solana.AccountsSettable = &Transfer{} + + _ solana.AccountsGettable = &CreateAccountWithSeed{} + _ solana.AccountsSettable = &CreateAccountWithSeed{} + + _ solana.AccountsGettable = &AdvanceNonceAccount{} + _ solana.AccountsSettable = &AdvanceNonceAccount{} +) + +func (ins *Instruction) Accounts() (out []*solana.AccountMeta) { + return ins.Impl.(solana.AccountsGettable).GetAccounts() } +// InstructionImplDef is used for deciding binary, +// encoding and decoding json. +var InstructionImplDef = bin.NewVariantDefinition( + bin.Uint32TypeIDEncoding, + []bin.VariantType{ + // TODO: + {"create_account", (*CreateAccount)(nil)}, + {"assign", (*Assign)(nil)}, + {"transfer", (*Transfer)(nil)}, + {"create_account_with_seed", (*CreateAccountWithSeed)(nil)}, + {"advance_nonce_account", (*AdvanceNonceAccount)(nil)}, + }) + func (i *Instruction) ProgramID() solana.PublicKey { return PROGRAM_ID } @@ -124,12 +123,6 @@ func (i *Instruction) TextEncode(encoder *text.Encoder, option *text.Option) err return encoder.Encode(i.Impl, option) } -var InstructionImplDef = bin.NewVariantDefinition(bin.Uint32TypeIDEncoding, []bin.VariantType{ - {"create_account", (*CreateAccount)(nil)}, - {"assign", (*Assign)(nil)}, - {"transfer", (*Transfer)(nil)}, -}) - func (i *Instruction) UnmarshalBinary(decoder *bin.Decoder) error { return i.BaseVariant.UnmarshalBinaryVariant(decoder, InstructionImplDef) } @@ -142,67 +135,28 @@ func (i *Instruction) MarshalBinary(encoder *bin.Encoder) error { return encoder.Encode(i.Impl) } -type CreateAccountAccounts struct { - From *solana.AccountMeta `text:"linear,notype"` - New *solana.AccountMeta `text:"linear,notype"` -} - -type CreateAccount struct { - Lamports bin.Uint64 - Space bin.Uint64 - Owner solana.PublicKey - Accounts *CreateAccountAccounts `bin:"-"` -} - -func (i *CreateAccount) SetAccounts(accounts []*solana.AccountMeta) error { - i.Accounts = &CreateAccountAccounts{ - From: accounts[0], - New: accounts[1], +func registryDecodeInstruction(accounts []*solana.AccountMeta, data []byte) (interface{}, error) { + inst, err := DecodeInstruction(accounts, data) + if err != nil { + return nil, err } - return nil -} - -type Assign struct { - // prefixed with byte 0x01 - Owner solana.PublicKey -} - -type Transfer struct { - // Prefixed with byte 0x02 - Lamports bin.Uint64 - Accounts *TransferAccounts `bin:"-"` -} - -type TransferAccounts struct { - From *solana.AccountMeta `text:"linear,notype"` - To *solana.AccountMeta `text:"linear,notype"` + return inst, nil } -func (i *Transfer) SetAccounts(accounts []*solana.AccountMeta) error { - i.Accounts = &TransferAccounts{ - From: accounts[0], - To: accounts[1], +func DecodeInstruction(accounts []*solana.AccountMeta, data []byte) (*Instruction, error) { + var inst *Instruction + if err := bin.NewDecoder(data).Decode(&inst); err != nil { + return nil, fmt.Errorf("unable to decode instruction for serum program: %w", err) } - return nil -} - -type CreateAccountWithSeed struct { - // Prefixed with byte 0x03 - Base solana.PublicKey - SeedSize int `bin:"sizeof=Seed"` - Seed string - Lamports bin.Uint64 - Space bin.Uint64 - Owner solana.PublicKey -} -type AdvanceNonceAccount struct { - // Prefix with 0x04 -} + 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) + } + } -type WithdrawNonceAccount struct { - // Prefix with 0x05 - Lamports bin.Uint64 + return inst, nil } type InitializeNonceAccount struct { diff --git a/programs/system/sysvar.go b/programs/system/sysvar.go new file mode 100644 index 00000000..17127c8b --- /dev/null +++ b/programs/system/sysvar.go @@ -0,0 +1,53 @@ +package system + +import "github.com/gagliardetto/solana-go" + +// See more here: https://github.com/solana-labs/solana/blob/master/docs/src/developing/runtime-facilities/sysvars.md + +// From https://github.com/solana-labs/solana/blob/94ab0eb49f1bce18d0a157dfe7a2bb1fb39dbe2c/docs/src/developing/runtime-facilities/sysvars.md +var ( + // The Clock sysvar contains data on cluster time, + // including the current slot, epoch, and estimated wall-clock Unix timestamp. + // It is updated every slot. + SysVarClockPubkey = solana.MustPublicKeyFromBase58("SysvarC1ock11111111111111111111111111111111") + + // The EpochSchedule sysvar contains epoch scheduling constants that are set in genesis, + // and enables calculating the number of slots in a given epoch, + // the epoch for a given slot, etc. + // (Note: the epoch schedule is distinct from the leader schedule) + SysVarEpochSchedulePubkey = solana.MustPublicKeyFromBase58("SysvarEpochSchedu1e111111111111111111111111") + + // The Fees sysvar contains the fee calculator for the current slot. + // It is updated every slot, based on the fee-rate governor. + SysVarFeesPubkey = solana.MustPublicKeyFromBase58("SysvarFees111111111111111111111111111111111") + + // The Instructions sysvar contains the serialized instructions in a Message while that Message is being processed. + // This allows program instructions to reference other instructions in the same transaction. + SysVarInstructionsPubkey = solana.MustPublicKeyFromBase58("Sysvar1nstructions1111111111111111111111111") + + // The RecentBlockhashes sysvar contains the active recent blockhashes as well as their associated fee calculators. + // It is updated every slot. + // Entries are ordered by descending block height, + // so the first entry holds the most recent block hash, + // and the last entry holds an old block hash. + SysVarRecentBlockHashesPubkey = solana.MustPublicKeyFromBase58("SysvarRecentB1ockHashes11111111111111111111") + + // The Rent sysvar contains the rental rate. + // Currently, the rate is static and set in genesis. + // The Rent burn percentage is modified by manual feature activation. + SysVarRentPubkey = solana.MustPublicKeyFromBase58("SysvarRent111111111111111111111111111111111") + + // The SlotHashes sysvar contains the most recent hashes of the slot's parent banks. + // It is updated every slot. + SysVarSlotHashesPubkey = solana.MustPublicKeyFromBase58("SysvarS1otHashes111111111111111111111111111") + + // The SlotHistory sysvar contains a bitvector of slots present over the last epoch. It is updated every slot. + SysVarSlotHistoryPubkey = solana.MustPublicKeyFromBase58("SysvarS1otHistory11111111111111111111111111") + + // 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 = solana.MustPublicKeyFromBase58("SysvarStakeHistory1111111111111111111111111") + + // + SysVarRewardsPubkey = solana.MustPublicKeyFromBase58("SysvarRewards111111111111111111111111111111") +) diff --git a/programs/token/instructions.go b/programs/token/instructions.go index 03fb285b..fbc5ef93 100644 --- a/programs/token/instructions.go +++ b/programs/token/instructions.go @@ -43,7 +43,7 @@ func DecodeInstruction(accounts []*solana.AccountMeta, data []byte) (*Instructio return nil, fmt.Errorf("unable to decode instruction for serum program: %w", err) } - if v, ok := inst.Impl.(solana.AccountSettable); ok { + 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) diff --git a/programs/tokenregistry/instruction.go b/programs/tokenregistry/instruction.go index 5dba509b..935b099e 100644 --- a/programs/tokenregistry/instruction.go +++ b/programs/tokenregistry/instruction.go @@ -29,7 +29,7 @@ func DecodeInstruction(accounts []*solana.AccountMeta, data []byte) (*Instructio return nil, fmt.Errorf("unable to decode instruction for serum program: %w", err) } - if v, ok := inst.Impl.(solana.AccountSettable); ok { + 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) diff --git a/transaction.go b/transaction.go index 9c3c4d97..0e4db1d3 100644 --- a/transaction.go +++ b/transaction.go @@ -10,8 +10,8 @@ import ( ) type Instruction interface { - Accounts() []*AccountMeta // returns the list of accounts the instructions requires ProgramID() PublicKey // the programID the instruction acts on + Accounts() []*AccountMeta // returns the list of accounts the instructions requires Data() ([]byte, error) // the binary encoded instructions } @@ -188,7 +188,7 @@ func NewTransaction(instructions []Instruction, blockHash Hash, opts ...Transact ) } - for trxIdx, instruction := range instructions { + for txIdx, instruction := range instructions { accounts = instruction.Accounts() accountIndex := make([]uint16, len(accounts)) for idx, acc := range accounts { @@ -196,7 +196,7 @@ func NewTransaction(instructions []Instruction, blockHash Hash, opts ...Transact } data, err := instruction.Data() if err != nil { - return nil, fmt.Errorf("unable to encode instructions [%d]: %w", trxIdx, err) + return nil, fmt.Errorf("unable to encode instructions [%d]: %w", txIdx, err) } message.Instructions = append(message.Instructions, CompiledInstruction{ ProgramIDIndex: accountKeyIndex[instruction.ProgramID().String()], @@ -235,13 +235,13 @@ func (tx *Transaction) MarshalBinary() ([]byte, error) { return output, nil } -func (t *Transaction) Sign(getter privateKeyGetter) (out []Signature, err error) { - messageContent, err := t.Message.MarshalBinary() +func (tx *Transaction) Sign(getter privateKeyGetter) (out []Signature, err error) { + messageContent, err := tx.Message.MarshalBinary() if err != nil { return nil, fmt.Errorf("unable to encode message for signing: %w", err) } - signerKeys := t.Message.signerKeys() + signerKeys := tx.Message.signerKeys() for _, key := range signerKeys { privateKey := getter(key) @@ -254,7 +254,7 @@ func (t *Transaction) Sign(getter privateKeyGetter) (out []Signature, err error) return nil, fmt.Errorf("failed to signed with key %q: %w", key.String(), err) } - t.Signatures = append(t.Signatures, s) + tx.Signatures = append(tx.Signatures, s) } - return t.Signatures, nil + return tx.Signatures, nil } From 493f9907e53eb54d509f7ce15370cf6a3152296c Mon Sep 17 00:00:00 2001 From: Slavomir Date: Thu, 29 Jul 2021 17:58:03 +0200 Subject: [PATCH 02/34] Complete system program --- programs/system/Allocate.go | 33 +++++++++++++ programs/system/AllocateWithSeed.go | 45 +++++++++++++++++ programs/system/AssignWithSeed.go | 42 ++++++++++++++++ programs/system/AuthorizeNonceAccount.go | 37 ++++++++++++++ programs/system/InitializeNonceAccount.go | 40 +++++++++++++++ programs/system/TransferWithSeed.go | 46 +++++++++++++++++ programs/system/WithdrawNonceAccount.go | 6 ++- programs/system/instructions.go | 60 +++++++++++------------ 8 files changed, 275 insertions(+), 34 deletions(-) create mode 100644 programs/system/Allocate.go create mode 100644 programs/system/AllocateWithSeed.go create mode 100644 programs/system/AssignWithSeed.go create mode 100644 programs/system/AuthorizeNonceAccount.go create mode 100644 programs/system/InitializeNonceAccount.go create mode 100644 programs/system/TransferWithSeed.go diff --git a/programs/system/Allocate.go b/programs/system/Allocate.go new file mode 100644 index 00000000..c26f7634 --- /dev/null +++ b/programs/system/Allocate.go @@ -0,0 +1,33 @@ +package system + +import ( + bin "github.com/dfuse-io/binary" + solana "github.com/gagliardetto/solana-go" +) + +func NewAllocateInstruction( + space uint64, + accountPubKey solana.PublicKey, +) *Instruction { + return &Instruction{ + BaseVariant: bin.BaseVariant{ + + TypeID: Instruction_Allocate, + + Impl: &Allocate{ + Space: bin.Uint64(space), + AccountMetaSlice: []*solana.AccountMeta{ + solana.NewAccountMeta(accountPubKey, true, true), + }, + }, + }, + } +} + +type Allocate struct { + // Number of bytes of memory to allocate. + Space bin.Uint64 + + // [0] = [WRITE, SIGNER] New account + solana.AccountMetaSlice `bin:"-"` +} diff --git a/programs/system/AllocateWithSeed.go b/programs/system/AllocateWithSeed.go new file mode 100644 index 00000000..3495cedb --- /dev/null +++ b/programs/system/AllocateWithSeed.go @@ -0,0 +1,45 @@ +package system + +import ( + bin "github.com/dfuse-io/binary" + solana "github.com/gagliardetto/solana-go" +) + +func NewAllocateWithSeedInstruction( + accountPubKey solana.PublicKey, + basePubKey solana.PublicKey, + seed string, + space uint64, + owner solana.PublicKey, +) *Instruction { + return &Instruction{ + BaseVariant: bin.BaseVariant{ + + TypeID: Instruction_AllocateWithSeed, + + Impl: &AllocateWithSeed{ + Base: basePubKey, + Seed: seed, + Space: bin.Uint64(space), + Owner: owner, + + AccountMetaSlice: []*solana.AccountMeta{ + solana.NewAccountMeta(accountPubKey, true, false), + solana.NewAccountMeta(basePubKey, false, true), + }, + }, + }, + } +} + +type AllocateWithSeed struct { + Base solana.PublicKey + SeedSize int `bin:"sizeof=Seed"` + Seed string + Space bin.Uint64 + Owner solana.PublicKey + + // [0] = [WRITE] Allocated account. + // [1] = [SIGNER] Base account. + solana.AccountMetaSlice `bin:"-"` +} diff --git a/programs/system/AssignWithSeed.go b/programs/system/AssignWithSeed.go new file mode 100644 index 00000000..44d1f17b --- /dev/null +++ b/programs/system/AssignWithSeed.go @@ -0,0 +1,42 @@ +package system + +import ( + bin "github.com/dfuse-io/binary" + solana "github.com/gagliardetto/solana-go" +) + +func NewAssignWithSeedInstruction( + accountPubKey solana.PublicKey, + basePubKey solana.PublicKey, + seed string, + owner solana.PublicKey, +) *Instruction { + return &Instruction{ + BaseVariant: bin.BaseVariant{ + + TypeID: Instruction_AssignWithSeed, + + Impl: &AssignWithSeed{ + Base: basePubKey, + Seed: seed, + Owner: owner, + + AccountMetaSlice: []*solana.AccountMeta{ + solana.NewAccountMeta(accountPubKey, true, false), + solana.NewAccountMeta(basePubKey, false, true), + }, + }, + }, + } +} + +type AssignWithSeed struct { + Base solana.PublicKey + SeedSize int `bin:"sizeof=Seed"` + Seed string + Owner solana.PublicKey + + // [0] = [WRITE] Assigned account. + // [1] = [SIGNER] Base account. + solana.AccountMetaSlice `bin:"-"` +} diff --git a/programs/system/AuthorizeNonceAccount.go b/programs/system/AuthorizeNonceAccount.go new file mode 100644 index 00000000..c9936b66 --- /dev/null +++ b/programs/system/AuthorizeNonceAccount.go @@ -0,0 +1,37 @@ +package system + +import ( + bin "github.com/dfuse-io/binary" + solana "github.com/gagliardetto/solana-go" +) + +func NewAuthorizeNonceAccountInstruction( + authorizePubKey solana.PublicKey, + + noncePubKey solana.PublicKey, + nonceAuthority solana.PublicKey, +) *Instruction { + return &Instruction{ + BaseVariant: bin.BaseVariant{ + + TypeID: Instruction_AuthorizeNonceAccount, + + Impl: &AuthorizeNonceAccount{ + PubKey: authorizePubKey, + AccountMetaSlice: []*solana.AccountMeta{ + solana.NewAccountMeta(noncePubKey, true, false), + solana.NewAccountMeta(nonceAuthority, false, true), + }, + }, + }, + } +} + +type AuthorizeNonceAccount struct { + // The Pubkey parameter identifies the entity to authorize. + PubKey solana.PublicKey + + // [0] = [WRITE] Nonce account. + // [1] = [SIGNER] Nonce authority. + solana.AccountMetaSlice `bin:"-"` +} diff --git a/programs/system/InitializeNonceAccount.go b/programs/system/InitializeNonceAccount.go new file mode 100644 index 00000000..f2c98c20 --- /dev/null +++ b/programs/system/InitializeNonceAccount.go @@ -0,0 +1,40 @@ +package system + +import ( + bin "github.com/dfuse-io/binary" + solana "github.com/gagliardetto/solana-go" +) + +func NewInitializeNonceAccountInstruction( + authPubKey solana.PublicKey, + noncePubKey solana.PublicKey, +) *Instruction { + return &Instruction{ + BaseVariant: bin.BaseVariant{ + + TypeID: Instruction_InitializeNonceAccount, + + Impl: &InitializeNonceAccount{ + AuthPubKey: authPubKey, + AccountMetaSlice: []*solana.AccountMeta{ + solana.NewAccountMeta(noncePubKey, true, false), + solana.NewAccountMeta(SysVarRecentBlockHashesPubkey, false, false), + solana.NewAccountMeta(SysVarRentPubkey, false, false), + }, + }, + }, + } +} + +type InitializeNonceAccount struct { + // The Pubkey parameter specifies the entity + // authorized to execute nonce instruction on the account. + // No signatures are required to execute this instruction, + // enabling derived nonce account addresses + AuthPubKey solana.PublicKey + + // [0] = [WRITE] Nonce account. + // [1] = [] RecentBlockhashes sysvar. + // [2] = [] Rent sysvar. + solana.AccountMetaSlice `bin:"-"` +} diff --git a/programs/system/TransferWithSeed.go b/programs/system/TransferWithSeed.go new file mode 100644 index 00000000..974fcdd2 --- /dev/null +++ b/programs/system/TransferWithSeed.go @@ -0,0 +1,46 @@ +package system + +import ( + bin "github.com/dfuse-io/binary" + solana "github.com/gagliardetto/solana-go" +) + +func NewTransferWithSeedInstruction( + from solana.PublicKey, + to solana.PublicKey, + basePubKey solana.PublicKey, + owner solana.PublicKey, + seed string, + lamports uint64, +) *Instruction { + return &Instruction{ + BaseVariant: bin.BaseVariant{ + + TypeID: Instruction_TransferWithSeed, + + Impl: &TransferWithSeed{ + Lamports: bin.Uint64(lamports), + Seed: seed, + Owner: owner, + + AccountMetaSlice: []*solana.AccountMeta{ + solana.NewAccountMeta(from, true, false), + solana.NewAccountMeta(basePubKey, false, true), + solana.NewAccountMeta(to, true, false), + }, + }, + }, + } +} + +type TransferWithSeed struct { + Lamports bin.Uint64 + SeedSize int `bin:"sizeof=Seed"` + Seed string + Owner solana.PublicKey + + // [0] = [WRITE] Funding account. + // [1] = [SIGNER] Base for funding account. + // [2] = [WRITE] Recipient account. + solana.AccountMetaSlice `bin:"-"` +} diff --git a/programs/system/WithdrawNonceAccount.go b/programs/system/WithdrawNonceAccount.go index af5b82a7..5462ee65 100644 --- a/programs/system/WithdrawNonceAccount.go +++ b/programs/system/WithdrawNonceAccount.go @@ -8,8 +8,8 @@ import ( func NewWithdrawNonceAccountInstruction( lamports uint64, - // TODO: nonceAccount solana.PublicKey, + recipientAccount solana.PublicKey, nonceAuthority solana.PublicKey, ) *Instruction { return &Instruction{ @@ -21,8 +21,10 @@ func NewWithdrawNonceAccountInstruction( Lamports: bin.Uint64(lamports), AccountMetaSlice: []*solana.AccountMeta{ solana.NewAccountMeta(nonceAccount, true, false), + solana.NewAccountMeta(recipientAccount, true, false), solana.NewAccountMeta(SysVarRecentBlockHashesPubkey, false, false), - solana.NewAccountMeta(nonceAuthority, true, false), + solana.NewAccountMeta(SysVarRentPubkey, false, false), + solana.NewAccountMeta(nonceAuthority, false, true), }, }, }, diff --git a/programs/system/instructions.go b/programs/system/instructions.go index 9e8b73b9..437d8aa8 100644 --- a/programs/system/instructions.go +++ b/programs/system/instructions.go @@ -88,6 +88,27 @@ var ( _ solana.AccountsGettable = &AdvanceNonceAccount{} _ solana.AccountsSettable = &AdvanceNonceAccount{} + + _ solana.AccountsGettable = &WithdrawNonceAccount{} + _ solana.AccountsSettable = &WithdrawNonceAccount{} + + _ solana.AccountsGettable = &InitializeNonceAccount{} + _ solana.AccountsSettable = &InitializeNonceAccount{} + + _ solana.AccountsGettable = &AuthorizeNonceAccount{} + _ solana.AccountsSettable = &AuthorizeNonceAccount{} + + _ solana.AccountsGettable = &Allocate{} + _ solana.AccountsSettable = &Allocate{} + + _ solana.AccountsGettable = &AllocateWithSeed{} + _ solana.AccountsSettable = &AllocateWithSeed{} + + _ solana.AccountsGettable = &AssignWithSeed{} + _ solana.AccountsSettable = &AssignWithSeed{} + + _ solana.AccountsGettable = &TransferWithSeed{} + _ solana.AccountsSettable = &TransferWithSeed{} ) func (ins *Instruction) Accounts() (out []*solana.AccountMeta) { @@ -105,6 +126,13 @@ var InstructionImplDef = bin.NewVariantDefinition( {"transfer", (*Transfer)(nil)}, {"create_account_with_seed", (*CreateAccountWithSeed)(nil)}, {"advance_nonce_account", (*AdvanceNonceAccount)(nil)}, + {"withdraw_nonce_account", (*WithdrawNonceAccount)(nil)}, + {"initialize_nonce_account", (*InitializeNonceAccount)(nil)}, + {"authorize_nonce_account", (*AuthorizeNonceAccount)(nil)}, + {"allocate", (*Allocate)(nil)}, + {"allocate_with_seed", (*AllocateWithSeed)(nil)}, + {"assign_with_seed", (*AssignWithSeed)(nil)}, + {"transfer_with_seed", (*TransferWithSeed)(nil)}, }) func (i *Instruction) ProgramID() solana.PublicKey { @@ -158,35 +186,3 @@ func DecodeInstruction(accounts []*solana.AccountMeta, data []byte) (*Instructio return inst, nil } - -type InitializeNonceAccount struct { - // Prefix with 0x06 - AuthorizedAccount solana.PublicKey -} - -type AuthorizeNonceAccount struct { - // Prefix with 0x07 - AuthorizeAccount solana.PublicKey -} - -type Allocate struct { - // Prefix with 0x08 - Space bin.Uint64 -} - -type AllocateWithSeed struct { - // Prefixed with byte 0x09 - Base solana.PublicKey - SeedSize int `bin:"sizeof=Seed"` - Seed string - Space bin.Uint64 - Owner solana.PublicKey -} - -type AssignWithSeed struct { - // Prefixed with byte 0x0a - Base solana.PublicKey - SeedSize int `bin:"sizeof=Seed"` - Seed string - Owner solana.PublicKey -} From 0cfb1b0760733735410166b292768880e86c5f2d Mon Sep 17 00:00:00 2001 From: Slavomir Date: Thu, 29 Jul 2021 19:36:32 +0200 Subject: [PATCH 03/34] Fix typos --- programs/system/Allocate.go | 2 +- programs/system/instructions.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/programs/system/Allocate.go b/programs/system/Allocate.go index c26f7634..5ef57649 100644 --- a/programs/system/Allocate.go +++ b/programs/system/Allocate.go @@ -28,6 +28,6 @@ type Allocate struct { // Number of bytes of memory to allocate. Space bin.Uint64 - // [0] = [WRITE, SIGNER] New account + // [0] = [WRITE, SIGNER] New account. solana.AccountMetaSlice `bin:"-"` } diff --git a/programs/system/instructions.go b/programs/system/instructions.go index 437d8aa8..708b44a4 100644 --- a/programs/system/instructions.go +++ b/programs/system/instructions.go @@ -52,7 +52,7 @@ const ( // Drive state of Uninitalized nonce account to Initialized, setting the nonce value. Instruction_InitializeNonceAccount - // Change the entity authorized to execute nonce instruction_s on the account. + // Change the entity authorized to execute nonce instructions on the account. Instruction_AuthorizeNonceAccount // Allocate space in a (possibly new) account without funding. From 1c2f65df8ef61724d87487a0ba078ba97934a8fd Mon Sep 17 00:00:00 2001 From: Slavomir Date: Thu, 29 Jul 2021 20:18:19 +0200 Subject: [PATCH 04/34] Add utility methods to AccountMeta --- account.go | 21 +++++++++++++++++++++ account_test.go | 21 +++++++++++++++++++++ programs/system/AdvanceNonceAccount.go | 6 +++--- programs/system/Allocate.go | 2 +- programs/system/AllocateWithSeed.go | 4 ++-- programs/system/Assign.go | 2 +- programs/system/AssignWithSeed.go | 4 ++-- programs/system/AuthorizeNonceAccount.go | 4 ++-- programs/system/CreateAccount.go | 4 ++-- programs/system/CreateAccountWithSeed.go | 6 +++--- programs/system/InitializeNonceAccount.go | 6 +++--- programs/system/Transfer.go | 4 ++-- programs/system/TransferWithSeed.go | 6 +++--- programs/system/WithdrawNonceAccount.go | 10 +++++----- 14 files changed, 71 insertions(+), 29 deletions(-) diff --git a/account.go b/account.go index dc2fb33b..e4aa7538 100644 --- a/account.go +++ b/account.go @@ -38,6 +38,27 @@ type AccountMeta struct { IsSigner bool } +// Meta intializes a new AccountMeta with the provided pubKey. +func Meta( + pubKey PublicKey, +) *AccountMeta { + return &AccountMeta{ + PublicKey: pubKey, + } +} + +// WRITE sets IsWritable to true. +func (meta *AccountMeta) WRITE() *AccountMeta { + meta.IsWritable = true + return meta +} + +// SIGNER sets IsSigner to true. +func (meta *AccountMeta) SIGNER() *AccountMeta { + meta.IsSigner = true + return meta +} + func NewAccountMeta( pubKey PublicKey, WRITE bool, diff --git a/account_test.go b/account_test.go index a628ec43..6fa9f6dc 100644 --- a/account_test.go +++ b/account_test.go @@ -119,3 +119,24 @@ func TestNewAccountMeta(t *testing.T) { require.Equal(t, isSigner, out.IsSigner) require.Equal(t, isWritable, out.IsWritable) } + +func TestMeta(t *testing.T) { + pkey := MustPublicKeyFromBase58("SysvarS1otHashes111111111111111111111111111") + + meta := Meta(pkey) + require.NotNil(t, meta) + require.Equal(t, pkey, meta.PublicKey) + + require.False(t, meta.IsSigner) + require.False(t, meta.IsWritable) + + meta.SIGNER() + + require.True(t, meta.IsSigner) + require.False(t, meta.IsWritable) + + meta.WRITE() + + require.True(t, meta.IsSigner) + require.True(t, meta.IsWritable) +} diff --git a/programs/system/AdvanceNonceAccount.go b/programs/system/AdvanceNonceAccount.go index 2252bafb..6ebfff02 100644 --- a/programs/system/AdvanceNonceAccount.go +++ b/programs/system/AdvanceNonceAccount.go @@ -16,9 +16,9 @@ func NewAdvanceNonceAccountInstruction( Impl: &AdvanceNonceAccount{ AccountMetaSlice: []*solana.AccountMeta{ - solana.NewAccountMeta(nonceAccount, true, false), - solana.NewAccountMeta(SysVarRecentBlockHashesPubkey, false, false), - solana.NewAccountMeta(nonceAuthority, true, false), + solana.Meta(nonceAccount).WRITE(), + solana.Meta(SysVarRecentBlockHashesPubkey), + solana.Meta(nonceAuthority).SIGNER(), }, }, }, diff --git a/programs/system/Allocate.go b/programs/system/Allocate.go index 5ef57649..ce8508d4 100644 --- a/programs/system/Allocate.go +++ b/programs/system/Allocate.go @@ -17,7 +17,7 @@ func NewAllocateInstruction( Impl: &Allocate{ Space: bin.Uint64(space), AccountMetaSlice: []*solana.AccountMeta{ - solana.NewAccountMeta(accountPubKey, true, true), + solana.Meta(accountPubKey).WRITE().SIGNER(), }, }, }, diff --git a/programs/system/AllocateWithSeed.go b/programs/system/AllocateWithSeed.go index 3495cedb..ee614bd5 100644 --- a/programs/system/AllocateWithSeed.go +++ b/programs/system/AllocateWithSeed.go @@ -24,8 +24,8 @@ func NewAllocateWithSeedInstruction( Owner: owner, AccountMetaSlice: []*solana.AccountMeta{ - solana.NewAccountMeta(accountPubKey, true, false), - solana.NewAccountMeta(basePubKey, false, true), + solana.Meta(accountPubKey).WRITE(), + solana.Meta(basePubKey).SIGNER(), }, }, }, diff --git a/programs/system/Assign.go b/programs/system/Assign.go index 75a5d401..3c08c8bc 100644 --- a/programs/system/Assign.go +++ b/programs/system/Assign.go @@ -21,7 +21,7 @@ func NewAssignInstruction( NewOwner: assignToProgramID, AccountMetaSlice: []*solana.AccountMeta{ - solana.NewAccountMeta(accountPubkey, true, true), + solana.Meta(accountPubkey).WRITE().SIGNER(), }, }, }, diff --git a/programs/system/AssignWithSeed.go b/programs/system/AssignWithSeed.go index 44d1f17b..51910794 100644 --- a/programs/system/AssignWithSeed.go +++ b/programs/system/AssignWithSeed.go @@ -22,8 +22,8 @@ func NewAssignWithSeedInstruction( Owner: owner, AccountMetaSlice: []*solana.AccountMeta{ - solana.NewAccountMeta(accountPubKey, true, false), - solana.NewAccountMeta(basePubKey, false, true), + solana.Meta(accountPubKey).WRITE(), + solana.Meta(basePubKey).SIGNER(), }, }, }, diff --git a/programs/system/AuthorizeNonceAccount.go b/programs/system/AuthorizeNonceAccount.go index c9936b66..15e12847 100644 --- a/programs/system/AuthorizeNonceAccount.go +++ b/programs/system/AuthorizeNonceAccount.go @@ -19,8 +19,8 @@ func NewAuthorizeNonceAccountInstruction( Impl: &AuthorizeNonceAccount{ PubKey: authorizePubKey, AccountMetaSlice: []*solana.AccountMeta{ - solana.NewAccountMeta(noncePubKey, true, false), - solana.NewAccountMeta(nonceAuthority, false, true), + solana.Meta(noncePubKey).WRITE(), + solana.Meta(nonceAuthority).SIGNER(), }, }, }, diff --git a/programs/system/CreateAccount.go b/programs/system/CreateAccount.go index 62053cda..09de25dd 100644 --- a/programs/system/CreateAccount.go +++ b/programs/system/CreateAccount.go @@ -25,8 +25,8 @@ func NewCreateAccountInstruction( Owner: owner, AccountMetaSlice: []*solana.AccountMeta{ - solana.NewAccountMeta(fundingAccount, true, true), - solana.NewAccountMeta(newAccount, true, true), + solana.Meta(fundingAccount).WRITE().SIGNER(), + solana.Meta(newAccount).WRITE().SIGNER(), }, }, }, diff --git a/programs/system/CreateAccountWithSeed.go b/programs/system/CreateAccountWithSeed.go index 98b08a7f..870ddade 100644 --- a/programs/system/CreateAccountWithSeed.go +++ b/programs/system/CreateAccountWithSeed.go @@ -30,12 +30,12 @@ func NewCreateAccountWithSeedInstruction( AccountMetaSlice: func() solana.AccountMetaSlice { res := solana.AccountMetaSlice{ - solana.NewAccountMeta(fundingAccount, true, true), - solana.NewAccountMeta(newAccount, true, false), + solana.Meta(fundingAccount).WRITE().SIGNER(), + solana.Meta(newAccount).WRITE(), } if !base.Equals(fundingAccount) { - res.Append(solana.NewAccountMeta(base, false, true)) + res.Append(solana.Meta(base).SIGNER()) } return res diff --git a/programs/system/InitializeNonceAccount.go b/programs/system/InitializeNonceAccount.go index f2c98c20..8d072056 100644 --- a/programs/system/InitializeNonceAccount.go +++ b/programs/system/InitializeNonceAccount.go @@ -17,9 +17,9 @@ func NewInitializeNonceAccountInstruction( Impl: &InitializeNonceAccount{ AuthPubKey: authPubKey, AccountMetaSlice: []*solana.AccountMeta{ - solana.NewAccountMeta(noncePubKey, true, false), - solana.NewAccountMeta(SysVarRecentBlockHashesPubkey, false, false), - solana.NewAccountMeta(SysVarRentPubkey, false, false), + solana.Meta(noncePubKey).WRITE(), + solana.Meta(SysVarRecentBlockHashesPubkey), + solana.Meta(SysVarRentPubkey), }, }, }, diff --git a/programs/system/Transfer.go b/programs/system/Transfer.go index 7f67e2a1..7fa9674a 100644 --- a/programs/system/Transfer.go +++ b/programs/system/Transfer.go @@ -19,8 +19,8 @@ func NewTransferInstruction( Lamports: bin.Uint64(lamports), AccountMetaSlice: []*solana.AccountMeta{ - solana.NewAccountMeta(from, true, true), - solana.NewAccountMeta(to, true, false), + solana.Meta(from).WRITE().SIGNER(), + solana.Meta(to).WRITE(), }, }, }, diff --git a/programs/system/TransferWithSeed.go b/programs/system/TransferWithSeed.go index 974fcdd2..45eca04a 100644 --- a/programs/system/TransferWithSeed.go +++ b/programs/system/TransferWithSeed.go @@ -24,9 +24,9 @@ func NewTransferWithSeedInstruction( Owner: owner, AccountMetaSlice: []*solana.AccountMeta{ - solana.NewAccountMeta(from, true, false), - solana.NewAccountMeta(basePubKey, false, true), - solana.NewAccountMeta(to, true, false), + solana.Meta(from).WRITE(), + solana.Meta(basePubKey).SIGNER(), + solana.Meta(to).WRITE(), }, }, }, diff --git a/programs/system/WithdrawNonceAccount.go b/programs/system/WithdrawNonceAccount.go index 5462ee65..c3ecb172 100644 --- a/programs/system/WithdrawNonceAccount.go +++ b/programs/system/WithdrawNonceAccount.go @@ -20,11 +20,11 @@ func NewWithdrawNonceAccountInstruction( Impl: &WithdrawNonceAccount{ Lamports: bin.Uint64(lamports), AccountMetaSlice: []*solana.AccountMeta{ - solana.NewAccountMeta(nonceAccount, true, false), - solana.NewAccountMeta(recipientAccount, true, false), - solana.NewAccountMeta(SysVarRecentBlockHashesPubkey, false, false), - solana.NewAccountMeta(SysVarRentPubkey, false, false), - solana.NewAccountMeta(nonceAuthority, false, true), + solana.Meta(nonceAccount).WRITE(), + solana.Meta(recipientAccount).WRITE(), + solana.Meta(SysVarRecentBlockHashesPubkey), + solana.Meta(SysVarRentPubkey), + solana.Meta(nonceAuthority).SIGNER(), }, }, }, From fa21149f16c41ad0f10bd96b956e0b7b5eebecef Mon Sep 17 00:00:00 2001 From: Slavomir Date: Fri, 30 Jul 2021 19:23:34 +0200 Subject: [PATCH 05/34] Start adding builders --- programs/system/CreateAccount.go | 90 +++++++++++++++++++++++++------- transaction.go | 51 ++++++++++++++++-- 2 files changed, 118 insertions(+), 23 deletions(-) diff --git a/programs/system/CreateAccount.go b/programs/system/CreateAccount.go index 09de25dd..78b4f4a4 100644 --- a/programs/system/CreateAccount.go +++ b/programs/system/CreateAccount.go @@ -1,6 +1,8 @@ package system import ( + "fmt" + bin "github.com/dfuse-io/binary" solana "github.com/gagliardetto/solana-go" ) @@ -13,32 +15,80 @@ func NewCreateAccountInstruction( fundingAccount solana.PublicKey, newAccount solana.PublicKey, ) *Instruction { - return &Instruction{ - BaseVariant: bin.BaseVariant{ - - TypeID: Instruction_CreateAccount, - - Impl: &CreateAccount{ - - Lamports: bin.Uint64(lamports), - Space: bin.Uint64(space), - Owner: owner, - - AccountMetaSlice: []*solana.AccountMeta{ - solana.Meta(fundingAccount).WRITE().SIGNER(), - solana.Meta(newAccount).WRITE().SIGNER(), - }, - }, - }, - } + return NewCreateAccountBuilder(). + WithLamports(lamports). + WithSpace(space). + WithOwner(owner). + WithFundingAccount(fundingAccount). + WithNewAccount(newAccount). + Build() } +// Create a new account. type CreateAccount struct { + // Number of lamports to transfer to the new account. Lamports bin.Uint64 - Space bin.Uint64 - Owner solana.PublicKey + // Number of bytes of memory to allocate. + Space bin.Uint64 + // Address of program that will own the new account. + Owner solana.PublicKey // [0] = [WRITE, SIGNER] Funding account. // [1] = [WRITE, SIGNER] New account. solana.AccountMetaSlice `bin:"-"` } + +func NewCreateAccountBuilder() *CreateAccount { + return &CreateAccount{ + AccountMetaSlice: make(solana.AccountMetaSlice, 2), + } +} + +// Number of lamports to transfer to the new account. +func (ins *CreateAccount) WithLamports(lamports uint64) *CreateAccount { + ins.Lamports = bin.Uint64(lamports) + return ins +} + +// Number of bytes of memory to allocate. +func (ins *CreateAccount) WithSpace(space uint64) *CreateAccount { + ins.Space = bin.Uint64(space) + return ins +} + +// Address of program that will own the new account. +func (ins *CreateAccount) WithOwner(owner solana.PublicKey) *CreateAccount { + ins.Owner = owner + return ins +} + +// Sets the account that will fund the new account. +func (ins *CreateAccount) WithFundingAccount(fundingAccount solana.PublicKey) *CreateAccount { + ins.AccountMetaSlice[0] = solana.Meta(fundingAccount).WRITE().SIGNER() + return ins +} + +func (ins *CreateAccount) WithNewAccount(newAccount solana.PublicKey) *CreateAccount { + ins.AccountMetaSlice[1] = solana.Meta(newAccount).WRITE().SIGNER() + return ins +} + +func (ins *CreateAccount) Validate() error { + for accIndex, acc := range ins.AccountMetaSlice { + if acc == nil { + return fmt.Errorf("ins.AccountMetaSlice[%v] is nil", accIndex) + } + } + return nil +} + +func (ins *CreateAccount) Build() *Instruction { + return &Instruction{ + BaseVariant: bin.BaseVariant{ + + TypeID: Instruction_CreateAccount, + + Impl: ins, + }, + } +} diff --git a/transaction.go b/transaction.go index 0e4db1d3..e156e6f7 100644 --- a/transaction.go +++ b/transaction.go @@ -61,7 +61,52 @@ func (slice *pubkeySlice) has(pubkey PublicKey) bool { var debugNewTransaction = false -func NewTransaction(instructions []Instruction, blockHash Hash, opts ...TransactionOption) (*Transaction, error) { +type TransactionBuilder struct { + instructions []Instruction + recentBlockHash Hash + opts []TransactionOption +} + +// NewTransactionBuilder creates a new instruction builder. +func NewTransactionBuilder() *TransactionBuilder { + return &TransactionBuilder{} +} + +// WithInstruction adds the provided instruction to the builder. +func (builder *TransactionBuilder) WithInstruction(instruction Instruction) *TransactionBuilder { + builder.instructions = append(builder.instructions, instruction) + return builder +} + +// SetRecentBlockHash sets the recent blockhash for the instruction builder. +func (builder *TransactionBuilder) SetRecentBlockHash(recentBlockHash Hash) *TransactionBuilder { + builder.recentBlockHash = recentBlockHash + return builder +} + +// WithOpt adds a TransactionOption. +func (builder *TransactionBuilder) WithOpt(opt TransactionOption) *TransactionBuilder { + builder.opts = append(builder.opts, opt) + return builder +} + +// Set transaction fee payer. +// If not set, defaults to first signer account of the first instruction. +func (builder *TransactionBuilder) SetFeePayer(feePayer PublicKey) *TransactionBuilder { + builder.opts = append(builder.opts, TransactionPayer(feePayer)) + return builder +} + +// Build builds and returns a *Transaction. +func (builder *TransactionBuilder) Build() (*Transaction, error) { + return NewTransaction( + builder.instructions, + builder.recentBlockHash, + builder.opts..., + ) +} + +func NewTransaction(instructions []Instruction, recentBlockHash Hash, opts ...TransactionOption) (*Transaction, error) { if len(instructions) == 0 { return nil, fmt.Errorf("requires at-least one instruction to create a transaction") } @@ -82,7 +127,7 @@ func NewTransaction(instructions []Instruction, blockHash Hash, opts ...Transact } } if !found { - return nil, fmt.Errorf("cannot determine fee payer. You can ether pass the fee payer vai the 'TransactionWithInstructions' option parameter or it fallback to the first instruction's first signer") + return nil, fmt.Errorf("cannot determine fee payer. You can ether pass the fee payer via the 'TransactionWithInstructions' option parameter or it falls back to the first instruction's first signer") } } @@ -154,7 +199,7 @@ func NewTransaction(instructions []Instruction, blockHash Hash, opts ...Transact } message := Message{ - RecentBlockhash: blockHash, + RecentBlockhash: recentBlockHash, } accountKeyIndex := map[string]uint16{} for idx, acc := range finalAccounts { From bdc8d4c3600e2d94cf4d6269810d9b9af50b5808 Mon Sep 17 00:00:00 2001 From: Slavomir Date: Mon, 2 Aug 2021 16:37:18 +0200 Subject: [PATCH 06/34] Use *solana.PublicKey for optional pubkeys --- keys.go | 7 +++++++ rpc/client_test.go | 8 ++++---- .../getTokenAccountsByDelegate.go | 2 +- .../getTokenAccountsByOwner/getTokenAccountsByOwner.go | 2 +- rpc/examples/getVoteAccounts/getVoteAccounts.go | 2 +- rpc/getBlockProduction.go | 4 ++-- rpc/getTokenAccountsByDelegate.go | 10 +++++----- rpc/getTokenAccountsByOwner.go | 6 +++--- rpc/getVoteAccounts.go | 4 ++-- 9 files changed, 26 insertions(+), 19 deletions(-) diff --git a/keys.go b/keys.go index 7e5b0c7f..871bb28b 100644 --- a/keys.go +++ b/keys.go @@ -152,8 +152,15 @@ func (p PublicKey) Equals(pb PublicKey) bool { return p == pb } +// ToPointer returns a pointer to the pubkey. +func (p PublicKey) ToPointer() *PublicKey { + return &p +} + var zeroPublicKey = PublicKey{} +// IsZero returns whether the public key is zero. +// NOTE: the System Program public key is also zero. func (p PublicKey) IsZero() bool { return p == zeroPublicKey } diff --git a/rpc/client_test.go b/rpc/client_test.go index f0cef333..a3c6237a 100644 --- a/rpc/client_test.go +++ b/rpc/client_test.go @@ -587,7 +587,7 @@ func TestClient_GetBlockProductionWithOpts(t *testing.T) { Range: &SlotRangeRequest{ FirstSlot: firstSlot, LastSlot: &lastSlot, - Identity: identity, + Identity: &identity, }, }, ) @@ -2128,7 +2128,7 @@ func TestClient_GetVoteAccounts(t *testing.T) { client := New(server.URL) opts := &GetVoteAccountsOpts{ - VotePubkey: solana.MustPublicKeyFromBase58("vot33MHDqT6nSwubGzqtc6m16ChcUywxV7tNULF19Vu"), + VotePubkey: solana.MustPublicKeyFromBase58("vot33MHDqT6nSwubGzqtc6m16ChcUywxV7tNULF19Vu").ToPointer(), Commitment: CommitmentMax, } out, err := client.GetVoteAccounts( @@ -2322,7 +2322,7 @@ func TestClient_GetTokenAccountsByDelegate(t *testing.T) { context.Background(), pubKey, &GetTokenAccountsConfig{ - ProgramId: programID, + ProgramId: &programID, }, &GetTokenAccountsOpts{ Commitment: CommitmentMax, @@ -2373,7 +2373,7 @@ func TestClient_GetTokenAccountsByOwner(t *testing.T) { context.Background(), pubKey, &GetTokenAccountsConfig{ - ProgramId: programID, + ProgramId: &programID, }, &GetTokenAccountsOpts{ Commitment: CommitmentMax, diff --git a/rpc/examples/getTokenAccountsByDelegate/getTokenAccountsByDelegate.go b/rpc/examples/getTokenAccountsByDelegate/getTokenAccountsByDelegate.go index 50c870f9..b30169e3 100644 --- a/rpc/examples/getTokenAccountsByDelegate/getTokenAccountsByDelegate.go +++ b/rpc/examples/getTokenAccountsByDelegate/getTokenAccountsByDelegate.go @@ -17,7 +17,7 @@ func main() { context.TODO(), pubKey, &rpc.GetTokenAccountsConfig{ - Mint: solana.MustPublicKeyFromBase58("So11111111111111111111111111111111111111112"), + Mint: solana.MustPublicKeyFromBase58("So11111111111111111111111111111111111111112").ToPointer(), }, nil, ) diff --git a/rpc/examples/getTokenAccountsByOwner/getTokenAccountsByOwner.go b/rpc/examples/getTokenAccountsByOwner/getTokenAccountsByOwner.go index 17d646bd..318f9a33 100644 --- a/rpc/examples/getTokenAccountsByOwner/getTokenAccountsByOwner.go +++ b/rpc/examples/getTokenAccountsByOwner/getTokenAccountsByOwner.go @@ -17,7 +17,7 @@ func main() { context.TODO(), pubKey, &rpc.GetTokenAccountsConfig{ - Mint: solana.MustPublicKeyFromBase58("So11111111111111111111111111111111111111112"), + Mint: solana.MustPublicKeyFromBase58("So11111111111111111111111111111111111111112").ToPointer(), }, nil, ) diff --git a/rpc/examples/getVoteAccounts/getVoteAccounts.go b/rpc/examples/getVoteAccounts/getVoteAccounts.go index 7d9dd3aa..a984e8f2 100644 --- a/rpc/examples/getVoteAccounts/getVoteAccounts.go +++ b/rpc/examples/getVoteAccounts/getVoteAccounts.go @@ -15,7 +15,7 @@ func main() { out, err := client.GetVoteAccounts( context.TODO(), &rpc.GetVoteAccountsOpts{ - VotePubkey: solana.MustPublicKeyFromBase58("vot33MHDqT6nSwubGzqtc6m16ChcUywxV7tNULF19Vu"), + VotePubkey: solana.MustPublicKeyFromBase58("vot33MHDqT6nSwubGzqtc6m16ChcUywxV7tNULF19Vu").ToPointer(), }, ) if err != nil { diff --git a/rpc/getBlockProduction.go b/rpc/getBlockProduction.go index 6b8c0eda..77402e7d 100644 --- a/rpc/getBlockProduction.go +++ b/rpc/getBlockProduction.go @@ -36,7 +36,7 @@ type SlotRangeRequest struct { // Only return results for this validator identity. // // This parameter is optional. - Identity solana.PublicKey `json:"identity,omitempty"` + Identity *solana.PublicKey `json:"identity,omitempty"` } // GetBlockProduction returns recent block production information from the current or previous epoch. @@ -67,7 +67,7 @@ func (cl *Client) GetBlockProductionWithOpts( if opts.Range.LastSlot != nil { rngObj["lastSlot"] = opts.Range.LastSlot } - if !opts.Range.Identity.IsZero() { + if opts.Range.Identity != nil { rngObj["identity"] = opts.Range.Identity } obj["range"] = rngObj diff --git a/rpc/getTokenAccountsByDelegate.go b/rpc/getTokenAccountsByDelegate.go index 0cb5575e..2de5484e 100644 --- a/rpc/getTokenAccountsByDelegate.go +++ b/rpc/getTokenAccountsByDelegate.go @@ -9,12 +9,12 @@ import ( type GetTokenAccountsConfig struct { // Pubkey of the specific token Mint to limit accounts to. - Mint solana.PublicKey `json:"mint"` + Mint *solana.PublicKey `json:"mint"` // OR: // Pubkey of the Token program ID that owns the accounts. - ProgramId solana.PublicKey `json:"programId"` + ProgramId *solana.PublicKey `json:"programId"` } type GetTokenAccountsOpts struct { @@ -36,16 +36,16 @@ func (cl *Client) GetTokenAccountsByDelegate( if conf == nil { return nil, errors.New("conf is nil") } - if !conf.Mint.IsZero() && !conf.ProgramId.IsZero() { + if conf.Mint != nil && conf.ProgramId != nil { return nil, errors.New("conf.Mint and conf.ProgramId are both set; must be just one of them") } { confObj := M{} - if !conf.Mint.IsZero() { + if conf.Mint != nil { confObj["mint"] = conf.Mint } - if !conf.ProgramId.IsZero() { + if conf.ProgramId != nil { confObj["programId"] = conf.ProgramId } if len(confObj) > 0 { diff --git a/rpc/getTokenAccountsByOwner.go b/rpc/getTokenAccountsByOwner.go index 07286148..a851b3fa 100644 --- a/rpc/getTokenAccountsByOwner.go +++ b/rpc/getTokenAccountsByOwner.go @@ -18,16 +18,16 @@ func (cl *Client) GetTokenAccountsByOwner( if conf == nil { return nil, errors.New("conf is nil") } - if !conf.Mint.IsZero() && !conf.ProgramId.IsZero() { + if conf.Mint != nil && conf.ProgramId != nil { return nil, errors.New("conf.Mint and conf.ProgramId are both set; must be just one of them") } { confObj := M{} - if !conf.Mint.IsZero() { + if conf.Mint != nil { confObj["mint"] = conf.Mint } - if !conf.ProgramId.IsZero() { + if conf.ProgramId != nil { confObj["programId"] = conf.ProgramId } if len(confObj) > 0 { diff --git a/rpc/getVoteAccounts.go b/rpc/getVoteAccounts.go index ed9c881b..733738cf 100644 --- a/rpc/getVoteAccounts.go +++ b/rpc/getVoteAccounts.go @@ -10,7 +10,7 @@ type GetVoteAccountsOpts struct { Commitment CommitmentType `json:"commitment,omitempty"` // (optional) Only return results for this validator vote address. - VotePubkey solana.PublicKey `json:"votePubkey,omitempty"` + VotePubkey *solana.PublicKey `json:"votePubkey,omitempty"` // (optional) Do not filter out delinquent validators with no stake. KeepUnstakedDelinquents *bool `json:"keepUnstakedDelinquents,omitempty"` @@ -34,7 +34,7 @@ func (cl *Client) GetVoteAccounts( if opts.Commitment != "" { obj["commitment"] = string(opts.Commitment) } - if !opts.VotePubkey.IsZero() { + if opts.VotePubkey != nil { obj["votePubkey"] = opts.VotePubkey.String() } if opts.KeepUnstakedDelinquents != nil { From dceb00918c64304ebfed06e7beb6b5001b3c787b Mon Sep 17 00:00:00 2001 From: Slavomir Date: Mon, 2 Aug 2021 16:56:12 +0200 Subject: [PATCH 07/34] system program: add getters --- account.go | 11 +++++++++++ programs/system/CreateAccount.go | 28 ++++++++++++++++++++++++---- transaction.go | 4 ++-- 3 files changed, 37 insertions(+), 6 deletions(-) diff --git a/account.go b/account.go index e4aa7538..ef5aec8c 100644 --- a/account.go +++ b/account.go @@ -98,3 +98,14 @@ func (slice *AccountMetaSlice) SetAccounts(accounts []*AccountMeta) error { func (slice AccountMetaSlice) GetAccounts() []*AccountMeta { return slice } + +// GetSigners returns the accounts that are signers. +func (slice AccountMetaSlice) GetSigners() []*AccountMeta { + signers := make([]*AccountMeta, 0) + for _, ac := range slice { + if ac.IsSigner { + signers = append(signers, ac) + } + } + return signers +} diff --git a/programs/system/CreateAccount.go b/programs/system/CreateAccount.go index 78b4f4a4..aeb62ae4 100644 --- a/programs/system/CreateAccount.go +++ b/programs/system/CreateAccount.go @@ -38,41 +38,61 @@ type CreateAccount struct { solana.AccountMetaSlice `bin:"-"` } +// NewCreateAccountBuilder initializes a new CreateAccount builder. func NewCreateAccountBuilder() *CreateAccount { return &CreateAccount{ AccountMetaSlice: make(solana.AccountMetaSlice, 2), } } -// Number of lamports to transfer to the new account. +// WithLamports sets the number of lamports to transfer to the new account. func (ins *CreateAccount) WithLamports(lamports uint64) *CreateAccount { ins.Lamports = bin.Uint64(lamports) return ins } -// Number of bytes of memory to allocate. +// WithSpace sets the number of bytes of memory to allocate. func (ins *CreateAccount) WithSpace(space uint64) *CreateAccount { ins.Space = bin.Uint64(space) return ins } -// Address of program that will own the new account. +// WithOwner sets the address of program that will own the new account. func (ins *CreateAccount) WithOwner(owner solana.PublicKey) *CreateAccount { ins.Owner = owner return ins } -// Sets the account that will fund the new account. +// WithFundingAccount sets the account that will fund the new account. func (ins *CreateAccount) WithFundingAccount(fundingAccount solana.PublicKey) *CreateAccount { ins.AccountMetaSlice[0] = solana.Meta(fundingAccount).WRITE().SIGNER() return ins } +// GetFundingAccount gets the account that will fund the new account. +func (ins *CreateAccount) GetFundingAccount() *solana.PublicKey { + ac := ins.AccountMetaSlice[0] + if ac == nil { + return nil + } + return &ac.PublicKey +} + +// WithNewAccount sets the new account that will be created. func (ins *CreateAccount) WithNewAccount(newAccount solana.PublicKey) *CreateAccount { ins.AccountMetaSlice[1] = solana.Meta(newAccount).WRITE().SIGNER() return ins } +// GetNewAccount gets the new account. +func (ins *CreateAccount) GetNewAccount() *solana.PublicKey { + ac := ins.AccountMetaSlice[1] + if ac == nil { + return nil + } + return &ac.PublicKey +} + func (ins *CreateAccount) Validate() error { for accIndex, acc := range ins.AccountMetaSlice { if acc == nil { diff --git a/transaction.go b/transaction.go index e156e6f7..7c34e3a0 100644 --- a/transaction.go +++ b/transaction.go @@ -72,8 +72,8 @@ func NewTransactionBuilder() *TransactionBuilder { return &TransactionBuilder{} } -// WithInstruction adds the provided instruction to the builder. -func (builder *TransactionBuilder) WithInstruction(instruction Instruction) *TransactionBuilder { +// AddInstruction adds the provided instruction to the builder. +func (builder *TransactionBuilder) AddInstruction(instruction Instruction) *TransactionBuilder { builder.instructions = append(builder.instructions, instruction) return builder } From 26a30a354b0a0dedb5a856ea05f6ae2236e018e3 Mon Sep 17 00:00:00 2001 From: Slavomir Date: Tue, 3 Aug 2021 00:54:36 +0200 Subject: [PATCH 08/34] System program: refactor AdvanceNonceAccount --- programs/system/AdvanceNonceAccount.go | 76 +++++++++++++++++++++----- 1 file changed, 62 insertions(+), 14 deletions(-) diff --git a/programs/system/AdvanceNonceAccount.go b/programs/system/AdvanceNonceAccount.go index 6ebfff02..ce6f3c4f 100644 --- a/programs/system/AdvanceNonceAccount.go +++ b/programs/system/AdvanceNonceAccount.go @@ -1,6 +1,8 @@ package system import ( + "fmt" + bin "github.com/dfuse-io/binary" solana "github.com/gagliardetto/solana-go" ) @@ -9,25 +11,71 @@ func NewAdvanceNonceAccountInstruction( nonceAccount solana.PublicKey, nonceAuthority solana.PublicKey, ) *Instruction { - return &Instruction{ - BaseVariant: bin.BaseVariant{ - - TypeID: Instruction_AdvanceNonceAccount, - - Impl: &AdvanceNonceAccount{ - AccountMetaSlice: []*solana.AccountMeta{ - solana.Meta(nonceAccount).WRITE(), - solana.Meta(SysVarRecentBlockHashesPubkey), - solana.Meta(nonceAuthority).SIGNER(), - }, - }, - }, - } + return NewAdvanceNonceAccountBuilder(). + WithNonceAccount(nonceAccount). + WithNonceAuthority(nonceAuthority). + Build() } +// Consumes a stored nonce, replacing it with a successor. type AdvanceNonceAccount struct { // [0] = [WRITE] Nonce account. // [1] = [] RecentBlockhashes sysvar. // [2] = [SIGNER] Nonce authority. solana.AccountMetaSlice `bin:"-"` } + +// NewAdvanceNonceAccountBuilder initializes a new AdvanceNonceAccount builder. +func NewAdvanceNonceAccountBuilder() *AdvanceNonceAccount { + nb := &AdvanceNonceAccount{ + AccountMetaSlice: make(solana.AccountMetaSlice, 3), + } + nb.AccountMetaSlice[1] = solana.Meta(SysVarRecentBlockHashesPubkey) + return nb +} + +func (ins *AdvanceNonceAccount) WithNonceAccount(nonceAccount solana.PublicKey) *AdvanceNonceAccount { + ins.AccountMetaSlice[0] = solana.Meta(nonceAccount).WRITE() + return ins +} + +func (ins *AdvanceNonceAccount) GetNonceAccount() *solana.PublicKey { + ac := ins.AccountMetaSlice[0] + if ac == nil { + return nil + } + return &ac.PublicKey +} + +func (ins *AdvanceNonceAccount) WithNonceAuthority(nonceAuthority solana.PublicKey) *AdvanceNonceAccount { + ins.AccountMetaSlice[2] = solana.Meta(nonceAuthority).SIGNER() + return ins +} + +func (ins *AdvanceNonceAccount) GetNonceAuthority() *solana.PublicKey { + ac := ins.AccountMetaSlice[2] + if ac == nil { + return nil + } + return &ac.PublicKey +} + +func (ins *AdvanceNonceAccount) Validate() error { + for accIndex, acc := range ins.AccountMetaSlice { + if acc == nil { + return fmt.Errorf("ins.AccountMetaSlice[%v] is nil", accIndex) + } + } + return nil +} + +func (ins *AdvanceNonceAccount) Build() *Instruction { + return &Instruction{ + BaseVariant: bin.BaseVariant{ + + TypeID: Instruction_AdvanceNonceAccount, + + Impl: ins, + }, + } +} From e87c0eb4625e457bbe6478b959b9cbcf71c27027 Mon Sep 17 00:00:00 2001 From: Slavomir Date: Wed, 4 Aug 2021 20:31:20 +0200 Subject: [PATCH 09/34] Update logo --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index eaa579bd..3b9d1d30 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Clients for Solana native programs, Solana Program Library (SPL), and [Serum DEX More contracts to come.
- +
## Contents @@ -2380,4 +2380,3 @@ We encourage everyone to contribute, submit issues, PRs, discuss. Every kind of ## Credits - Gopher logo was originally created by Takuya Ueda (https://twitter.com/tenntenn). Licensed under the Creative Commons 3.0 Attributions license. -- Pit Vipers https://www.pitviper.com/ From 915e2efbd3ed52a77874f70fda74360ad64a5fc2 Mon Sep 17 00:00:00 2001 From: Slavomir Date: Sun, 8 Aug 2021 17:51:40 +0200 Subject: [PATCH 10/34] Replace github.com/dfuse-io/binary with github.com/gagliardetto/binary --- go.mod | 2 ++ go.sum | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 38ae9693..10fc0512 100644 --- a/go.mod +++ b/go.mod @@ -47,3 +47,5 @@ require ( google.golang.org/api v0.29.0 gopkg.in/yaml.v2 v2.4.0 // indirect ) + +replace github.com/dfuse-io/binary => github.com/gagliardetto/binary v0.2.0 diff --git a/go.sum b/go.sum index 8e033ce1..f320b8c4 100644 --- a/go.sum +++ b/go.sum @@ -72,8 +72,6 @@ github.com/daaku/go.zipexe v1.0.0/go.mod h1:z8IiR6TsVLEYKwXAoE/I+8ys/sDkgTzSL0CL github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dfuse-io/binary v0.0.0-20210216024852-4ae6830a495d h1:EvEAQZ38olo+sALrqqCo345IeB2HH4im1deNOcjbi0s= -github.com/dfuse-io/binary v0.0.0-20210216024852-4ae6830a495d/go.mod h1:GDFX6qH3BQZPWTeYaA4ZW98T94zs2skRoG3oMz/0jw0= github.com/dfuse-io/logging v0.0.0-20201110202154-26697de88c79/go.mod h1:V+ED4kT/t/lKtH99JQmKIb0v9WL3VaYkJ36CfHlVECI= github.com/dfuse-io/logging v0.0.0-20210109005628-b97a57253f70 h1:CuJS05R9jmNlUK8GOxrEELPbfXm0EuGh/30LjkjN5vo= github.com/dfuse-io/logging v0.0.0-20210109005628-b97a57253f70/go.mod h1:EoK/8RFbMEteaCaz89uessDTnCWjbbcr+DXcBh4el5o= @@ -87,6 +85,8 @@ github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/gagliardetto/binary v0.2.0 h1:OPQjHRDg6pZVtj59k4BlhYJMgYqkL6V12bnnnY1zInk= +github.com/gagliardetto/binary v0.2.0/go.mod h1:GDFX6qH3BQZPWTeYaA4ZW98T94zs2skRoG3oMz/0jw0= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= From bbd80922678e53fea793a547f7a0eb9ed89961d1 Mon Sep 17 00:00:00 2001 From: Slavomir Date: Sun, 8 Aug 2021 17:51:50 +0200 Subject: [PATCH 11/34] Fix typo --- programs/system/instructions.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/programs/system/instructions.go b/programs/system/instructions.go index 708b44a4..a6c13822 100644 --- a/programs/system/instructions.go +++ b/programs/system/instructions.go @@ -174,7 +174,7 @@ func registryDecodeInstruction(accounts []*solana.AccountMeta, data []byte) (int func DecodeInstruction(accounts []*solana.AccountMeta, data []byte) (*Instruction, error) { var inst *Instruction if err := bin.NewDecoder(data).Decode(&inst); err != nil { - return nil, fmt.Errorf("unable to decode instruction for serum program: %w", err) + return nil, fmt.Errorf("unable to decode instruction: %w", err) } if v, ok := inst.Impl.(solana.AccountsSettable); ok { From ef0741933ad0089adbe3a51860ba2cf97e01b2ed Mon Sep 17 00:00:00 2001 From: Slavomir Date: Sun, 8 Aug 2021 17:53:42 +0200 Subject: [PATCH 12/34] Refactor Instruction and Message marshal/unmarshal --- transaction.go | 29 +++++++++- types.go | 146 +++++++++++++++++++++++++++++++++++-------------- 2 files changed, 133 insertions(+), 42 deletions(-) diff --git a/transaction.go b/transaction.go index 7c34e3a0..55b0af40 100644 --- a/transaction.go +++ b/transaction.go @@ -269,7 +269,8 @@ func (tx *Transaction) MarshalBinary() ([]byte, error) { return nil, fmt.Errorf("failed to encode tx.Message to binary: %w", err) } - signatureCount := UintToVarLenBytes(uint64(len(tx.Signatures))) + var signatureCount []byte + bin.EncodeLength(&signatureCount, len(tx.Signatures)) output := make([]byte, 0, len(signatureCount)+len(signatureCount)*64+len(messageContent)) output = append(output, signatureCount...) for _, sig := range tx.Signatures { @@ -280,6 +281,32 @@ func (tx *Transaction) MarshalBinary() ([]byte, error) { return output, nil } +func (tx *Transaction) UnmarshalBinary(decoder *bin.Decoder) (err error) { + { + numSignatures, err := bin.DecodeLengthFromByteReader(decoder) + if err != nil { + return err + } + + for i := 0; i < numSignatures; i++ { + sigBytes, err := decoder.ReadNBytes(64) + if err != nil { + return err + } + var sig Signature + copy(sig[:], sigBytes) + tx.Signatures = append(tx.Signatures, sig) + } + } + { + err := tx.Message.UnmarshalBinary(decoder) + if err != nil { + return err + } + } + return nil +} + func (tx *Transaction) Sign(getter privateKeyGetter) (out []Signature, err error) { messageContent, err := tx.Message.MarshalBinary() if err != nil { diff --git a/types.go b/types.go index 55dfd8c8..845dda74 100644 --- a/types.go +++ b/types.go @@ -15,7 +15,6 @@ package solana import ( - "encoding/binary" "fmt" bin "github.com/dfuse-io/binary" @@ -58,56 +57,117 @@ type Message struct { Instructions []CompiledInstruction `json:"instructions"` } -// UintToVarLenBytes is used for creating a []byte that contains -// the length of a variable, and is used for creating length -// prefixes in a marshaled transaction. -func UintToVarLenBytes(l uint64) []byte { - if l == 0 { - return []byte{0x0} +func (mx *Message) MarshalBinary() ([]byte, error) { + buf := []byte{ + mx.Header.NumRequiredSignatures, + mx.Header.NumReadonlySignedAccounts, + mx.Header.NumReadonlyUnsignedAccounts, + } + + bin.EncodeLength(&buf, len(mx.AccountKeys)) + for _, key := range mx.AccountKeys { + buf = append(buf, key[:]...) } - b := make([]byte, binary.MaxVarintLen64) - binary.PutUvarint(b, l) - return TrimRightZeros(b) -} -// TrimRightZeros reslices the provided slice -// by trimming all trailing zeros from the slice. -func TrimRightZeros(buf []byte) []byte { - cutIndex := len(buf) - for ; cutIndex > 0; cutIndex-- { - if buf[cutIndex-1] != 0 { - break + buf = append(buf, mx.RecentBlockhash[:]...) + + bin.EncodeLength(&buf, len(mx.Instructions)) + for _, instruction := range mx.Instructions { + buf = append(buf, byte(instruction.ProgramIDIndex)) + bin.EncodeLength(&buf, len(instruction.Accounts)) + for _, accountIdx := range instruction.Accounts { + buf = append(buf, byte(accountIdx)) } + + bin.EncodeLength(&buf, len(instruction.Data)) + buf = append(buf, instruction.Data...) } - return buf[:cutIndex] + return buf, nil } -func (m *Message) MarshalBinary() ([]byte, error) { - b := []byte{ - m.Header.NumRequiredSignatures, - m.Header.NumReadonlySignedAccounts, - m.Header.NumReadonlyUnsignedAccounts, +func (mx *Message) UnmarshalBinary(decoder *bin.Decoder) (err error) { + { + mx.Header.NumRequiredSignatures, err = decoder.ReadUint8() + if err != nil { + return err + } + mx.Header.NumReadonlySignedAccounts, err = decoder.ReadUint8() + if err != nil { + return err + } + mx.Header.NumReadonlyUnsignedAccounts, err = decoder.ReadUint8() + if err != nil { + return err + } } - - b = append(b, UintToVarLenBytes(uint64(len(m.AccountKeys)))...) - for _, key := range m.AccountKeys { - b = append(b, key[:]...) + { + numAccountKeys, err := bin.DecodeLengthFromByteReader(decoder) + if err != nil { + return err + } + for i := 0; i < numAccountKeys; i++ { + pubkeyBytes, err := decoder.ReadNBytes(32) + if err != nil { + return err + } + var sig PublicKey + copy(sig[:], pubkeyBytes) + mx.AccountKeys = append(mx.AccountKeys, sig) + } } - - b = append(b, m.RecentBlockhash[:]...) - - b = append(b, UintToVarLenBytes(uint64(len(m.Instructions)))...) - for _, instruction := range m.Instructions { - b = append(b, byte(instruction.ProgramIDIndex)) - b = append(b, UintToVarLenBytes(uint64(len(instruction.Accounts)))...) - for _, accountIdx := range instruction.Accounts { - b = append(b, byte(accountIdx)) + { + recentBlockhashBytes, err := decoder.ReadNBytes(32) + if err != nil { + return err } - - b = append(b, UintToVarLenBytes(uint64(len(instruction.Data)))...) - b = append(b, instruction.Data...) + var recentBlockhash Hash + copy(recentBlockhash[:], recentBlockhashBytes) + mx.RecentBlockhash = recentBlockhash } - return b, nil + { + numInstructions, err := bin.DecodeLengthFromByteReader(decoder) + if err != nil { + return err + } + for i := 0; i < numInstructions; i++ { + programIDIndex, err := decoder.ReadUint8() + if err != nil { + return err + } + var compInst CompiledInstruction + compInst.ProgramIDIndex = uint16(programIDIndex) + + { + numAccounts, err := bin.DecodeLengthFromByteReader(decoder) + if err != nil { + return err + } + compInst.AccountCount = bin.Varuint16(numAccounts) + for i := 0; i < numAccounts; i++ { + accountIndex, err := decoder.ReadUint8() + if err != nil { + return err + } + compInst.Accounts = append(compInst.Accounts, uint16(accountIndex)) + } + } + { + dataLen, err := bin.DecodeLengthFromByteReader(decoder) + if err != nil { + return err + } + dataBytes, err := decoder.ReadNBytes(dataLen) + if err != nil { + return err + } + compInst.DataLength = bin.Varuint16(dataLen) + compInst.Data = Base58(dataBytes) + } + mx.Instructions = append(mx.Instructions, compInst) + } + } + + return nil } func (m *Message) AccountMetaList() (out []*AccountMeta) { @@ -185,12 +245,16 @@ type MessageHeader struct { type CompiledInstruction struct { // Index into the message.accountKeys array indicating the program account that executes this instruction. + // NOTE: it is actually a uint8, but using a uint16 because uint8 is treated as a byte everywhere, + // and that can be an issue. ProgramIDIndex uint16 `json:"programIdIndex"` AccountCount bin.Varuint16 `json:"-" bin:"sizeof=Accounts"` DataLength bin.Varuint16 `json:"-" bin:"sizeof=Data"` // List of ordered indices into the message.accountKeys array indicating which accounts to pass to the program. + // NOTE: it is actually a []uint8, but using a uint16 because []uint8 is treated as a []byte everywhere, + // and that can be an issue. Accounts []uint16 `json:"accounts"` // The program input data encoded in a base-58 string. From e7471eff5f5f681eee2d65a52fc50e67be81dfd5 Mon Sep 17 00:00:00 2001 From: Slavomir Date: Sun, 8 Aug 2021 18:07:39 +0200 Subject: [PATCH 13/34] Add TestTransactionDecode --- transaction_test.go | 60 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/transaction_test.go b/transaction_test.go index 1837ba72..754cb7b7 100644 --- a/transaction_test.go +++ b/transaction_test.go @@ -1,9 +1,11 @@ package solana import ( + "encoding/base64" "testing" "github.com/magiconair/properties/assert" + "github.com/mr-tron/base58" "github.com/stretchr/testify/require" ) @@ -90,3 +92,61 @@ func TestNewTransaction(t *testing.T) { }, }) } + +func TestTransactionDecode(t *testing.T) { + encoded := "AfjEs3XhTc3hrxEvlnMPkm/cocvAUbFNbCl00qKnrFue6J53AhEqIFmcJJlJW3EDP5RmcMz+cNTTcZHW/WJYwAcBAAEDO8hh4VddzfcO5jbCt95jryl6y8ff65UcgukHNLWH+UQGgxCGGpgyfQVQV02EQYqm4QwzUt2qf9f1gVLM7rI4hwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA6ANIF55zOZWROWRkeh+lExxZBnKFqbvIxZDLE7EijjoBAgIAAQwCAAAAOTAAAAAAAAA=" + data, err := base64.StdEncoding.DecodeString(encoded) + require.NoError(t, err) + + tx, err := TransactionFromData(data) + require.NoError(t, err) + require.NotNil(t, tx) + + require.Len(t, tx.Signatures, 1) + require.Equal(t, + MustSignatureFromBase58("5yUSwqQqeZLEEYKxnG4JC4XhaaBpV3RS4nQbK8bQTyjLX5btVq9A1Ja5nuJzV7Z3Zq8G6EVKFvN4DKUL6PSAxmTk"), + tx.Signatures[0], + ) + + require.Equal(t, + []PublicKey{ + MustPublicKeyFromBase58("52NGrUqh6tSGhr59ajGxsH3VnAaoRdSdTbAaV9G3UW35"), + MustPublicKeyFromBase58("SRMuApVNdxXokk5GT7XD5cUUgXMBCoAz2LHeuAoKWRt"), + MustPublicKeyFromBase58("11111111111111111111111111111111"), + }, + tx.Message.AccountKeys, + ) + + require.Equal(t, + MessageHeader{ + NumRequiredSignatures: 1, + NumReadonlySignedAccounts: 0, + NumReadonlyUnsignedAccounts: 1, + }, + tx.Message.Header, + ) + + require.Equal(t, + MustHashFromBase58("GcgVK9buRA7YepZh3zXuS399GJAESCisLnLDBCmR5Aoj"), + tx.Message.RecentBlockhash, + ) + + decodedData, err := base58.Decode("3Bxs4ART6LMJ13T5") + require.NoError(t, err) + require.Equal(t, + []CompiledInstruction{ + { + ProgramIDIndex: 2, + AccountCount: 2, + DataLength: 12, + Accounts: []uint16{ + 0, + 1, + }, + Data: Base58(decodedData), + }, + }, + tx.Message.Instructions, + ) + +} From fca589f28f07abde90df7907c4350cd465e57fce Mon Sep 17 00:00:00 2001 From: Slavomir Date: Mon, 9 Aug 2021 15:44:34 +0200 Subject: [PATCH 14/34] Add EncodeToTree --- go.mod | 2 + go.sum | 4 + programs/system/Transfer.go | 21 ++- programs/system/instructions.go | 10 ++ text/tools.go | 279 ++++++++++++++++++++++++++++++++ text/tree.go | 28 ++++ transaction.go | 43 +++++ types.go | 26 +++ 8 files changed, 412 insertions(+), 1 deletion(-) create mode 100644 text/tools.go create mode 100644 text/tree.go diff --git a/go.mod b/go.mod index 10fc0512..638bbbc2 100644 --- a/go.mod +++ b/go.mod @@ -11,11 +11,13 @@ require ( contrib.go.opencensus.io/exporter/stackdriver v0.13.4 // indirect github.com/AlekSi/pointer v1.1.0 github.com/GeertJohan/go.rice v1.0.0 + github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59 github.com/buger/jsonparser v1.1.1 github.com/davecgh/go-spew v1.1.1 github.com/dfuse-io/binary v0.0.0-20210216024852-4ae6830a495d github.com/dfuse-io/logging v0.0.0-20210109005628-b97a57253f70 github.com/fatih/color v1.7.0 + github.com/gagliardetto/treeout v0.1.2 github.com/google/go-cmp v0.5.1 github.com/gorilla/rpc v1.2.0 github.com/gorilla/websocket v1.4.2 diff --git a/go.sum b/go.sum index f320b8c4..2d5c64a3 100644 --- a/go.sum +++ b/go.sum @@ -46,6 +46,8 @@ github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmV github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/aws/aws-sdk-go v1.22.1/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.23.20/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59 h1:WWB576BN5zNSZc/M9d/10pqEx5VHNhaQ/yOVAkmj5Yo= +github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59/go.mod h1:q/89r3U2H7sSsE2t6Kca0lfwTK8JdoNGS/yzM/4iH5I= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= @@ -87,6 +89,8 @@ github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/gagliardetto/binary v0.2.0 h1:OPQjHRDg6pZVtj59k4BlhYJMgYqkL6V12bnnnY1zInk= github.com/gagliardetto/binary v0.2.0/go.mod h1:GDFX6qH3BQZPWTeYaA4ZW98T94zs2skRoG3oMz/0jw0= +github.com/gagliardetto/treeout v0.1.2 h1:WXO7LDJTwINO37OQfNlf7s095Z1bAiwN2ACaZQic33Q= +github.com/gagliardetto/treeout v0.1.2/go.mod h1:loUefvXTrlRG5rYmJmExNryyBRh8f89VZhmMOyCyqok= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= diff --git a/programs/system/Transfer.go b/programs/system/Transfer.go index 7fa9674a..b16aa153 100644 --- a/programs/system/Transfer.go +++ b/programs/system/Transfer.go @@ -3,6 +3,9 @@ package system import ( bin "github.com/dfuse-io/binary" solana "github.com/gagliardetto/solana-go" + "github.com/gagliardetto/solana-go/text" + . "github.com/gagliardetto/solana-go/text" + "github.com/gagliardetto/treeout" ) func NewTransferInstruction( @@ -32,5 +35,21 @@ type Transfer struct { // [0] = [WRITE, SIGNER] Funding account. // [1] = [WRITE] Recipient account. - solana.AccountMetaSlice `bin:"-"` + solana.AccountMetaSlice `bin:"-" text:"-"` +} + +func (inst *Transfer) EncodeToTree(parent treeout.Branches) { + parent.Child(Sf(CC(IndigoBG(Bold("Program")), ": System (%s)"), text.ColorizeBG(PROGRAM_ID.String()))). + ParentFunc(func(programBranch treeout.Branches) { + programBranch.Child(CC(Purple(Bold("Instruction")), Bold(": Transfer"))). + ParentFunc(func(instructionBranch treeout.Branches) { + instructionBranch.Child("Params").ParentFunc(func(paramsBranch treeout.Branches) { + paramsBranch.Child(Sf(CC(Shakespeare("Lamports"), ": %v"), Lime(S(inst.Lamports)))) + }) + instructionBranch.Child("Accounts:").ParentFunc(func(accountsBranch treeout.Branches) { + accountsBranch.Child(Sf(CC(Shakespeare("Funding account"), ": %s"), text.ColorizeBG(inst.AccountMetaSlice[0].PublicKey.String()))) + accountsBranch.Child(Sf(CC(Shakespeare("Recipient account"), ": %s"), text.ColorizeBG(inst.AccountMetaSlice[1].PublicKey.String()))) + }) + }) + }) } diff --git a/programs/system/instructions.go b/programs/system/instructions.go index a6c13822..ce8e8886 100644 --- a/programs/system/instructions.go +++ b/programs/system/instructions.go @@ -19,9 +19,11 @@ import ( "encoding/binary" "fmt" + "github.com/davecgh/go-spew/spew" bin "github.com/dfuse-io/binary" solana "github.com/gagliardetto/solana-go" "github.com/gagliardetto/solana-go/text" + "github.com/gagliardetto/treeout" ) var PROGRAM_ID = solana.MustPublicKeyFromBase58("11111111111111111111111111111111") @@ -186,3 +188,11 @@ func DecodeInstruction(accounts []*solana.AccountMeta, data []byte) (*Instructio return inst, nil } + +func (inst *Instruction) EncodeToTree(parent treeout.Branches) { + if enToTree, ok := inst.Impl.(text.EncodableToTree); ok { + enToTree.EncodeToTree(parent) + } else { + parent.Child(spew.Sdump(inst)) + } +} diff --git a/text/tools.go b/text/tools.go new file mode 100644 index 00000000..94b1ae03 --- /dev/null +++ b/text/tools.go @@ -0,0 +1,279 @@ +package text + +import ( + "fmt" + "hash" + "hash/fnv" + "math" + "strings" + "sync" + + "github.com/aybabtme/rgbterm" +) + +var DisableColors = false + +func S(a ...interface{}) string { + return fmt.Sprint(a...) +} +func Sf(format string, a ...interface{}) string { + return fmt.Sprintf(format, a...) +} +func Ln(a ...interface{}) string { + return fmt.Sprintln(a...) +} + +// Lnsf is alias of fmt.Sprintln(fmt.Sprintf()) +func Lnsf(format string, a ...interface{}) string { + return Ln(Sf(format, a...)) +} + +// LnsfI is alias of fmt.Sprintln(fmt.Sprintf()) +func LnsfI(indent int, format string, a ...interface{}) string { + return Ln(Sf(strings.Repeat(" ", indent)+format, a...)) +} + +// CC concats strings +func CC(elems ...string) string { + return strings.Join(elems, "") +} + +func Black(str string) string { + if DisableColors { + return str + } + return rgbterm.FgString(str, 0, 0, 0) +} + +func White(str string) string { + if DisableColors { + return str + } + return rgbterm.FgString(str, 255, 255, 255) +} + +func BlackBG(str string) string { + if DisableColors { + return str + } + return rgbterm.BgString(str, 0, 0, 0) +} + +func WhiteBG(str string) string { + if DisableColors { + return str + } + return Black(rgbterm.BgString(str, 255, 255, 255)) +} + +func Lime(str string) string { + if DisableColors { + return str + } + return rgbterm.FgString(str, 252, 255, 43) +} + +func LimeBG(str string) string { + if DisableColors { + return str + } + return Black(rgbterm.BgString(str, 252, 255, 43)) +} + +func Yellow(str string) string { + if DisableColors { + return str + } + return BlackBG(rgbterm.FgString(str, 255, 255, 0)) +} + +func YellowBG(str string) string { + return Black(rgbterm.BgString(str, 255, 255, 0)) +} + +func Orange(str string) string { + if DisableColors { + return str + } + return rgbterm.FgString(str, 255, 165, 0) +} + +func OrangeBG(str string) string { + if DisableColors { + return str + } + return Black(rgbterm.BgString(str, 255, 165, 0)) +} + +func Red(str string) string { + if DisableColors { + return str + } + return rgbterm.FgString(str, 255, 0, 0) +} + +func RedBG(str string) string { + if DisableColors { + return str + } + return White(rgbterm.BgString(str, 220, 20, 60)) +} + +// light blue? +func Shakespeare(str string) string { + if DisableColors { + return str + } + return rgbterm.FgString(str, 82, 179, 217) +} + +func ShakespeareBG(str string) string { + if DisableColors { + return str + } + return White(rgbterm.BgString(str, 82, 179, 217)) +} + +func Purple(str string) string { + if DisableColors { + return str + } + return rgbterm.FgString(str, 255, 0, 255) +} + +func PurpleBG(str string) string { + if DisableColors { + return str + } + return Black(rgbterm.BgString(str, 255, 0, 255)) +} + +func Indigo(str string) string { + if DisableColors { + return str + } + return rgbterm.FgString(str, 75, 0, 130) +} + +func IndigoBG(str string) string { + if DisableColors { + return str + } + return rgbterm.BgString(str, 75, 0, 130) +} + +func Bold(str string) string { + if DisableColors { + return str + } + return foreachLine(str, func(idx int, line string) string { + return fmt.Sprintf("\033[1m%s\033[0m", line) + }) +} + +type sf func(int, string) string + +// Apply given transformation func for each line in string +func foreachLine(str string, transform sf) (out string) { + out = "" + + for idx, line := range strings.Split(str, "\n") { + out += transform(idx, line) + } + + return +} + +func HighlightRedBG(str, substr string) string { + return HighlightAnyCase(str, substr, RedBG) +} + +func HighlightLimeBG(str, substr string) string { + return HighlightAnyCase(str, substr, LimeBG) +} + +func HighlightAnyCase(str, substr string, colorer func(string) string) string { + substr = strings.ToLower(substr) + str = strings.ToLower(str) + + hiSubstr := colorer(substr) + return strings.Replace(str, substr, hiSubstr, -1) +} + +func StringToColor(str string) func(string) string { + hs := HashString(str) + r, g, b, _ := calcColor(hs) + + bgColor := WhiteBG + if IsLight(r, g, b) { + bgColor = BlackBG + } + return func(str string) string { + return bgColor(rgbterm.FgString(str, uint8(r), uint8(g), uint8(b))) + } +} + +func StringToColorBG(str string) func(string) string { + hs := HashString(str) + r, g, b, _ := calcColor(hs) + + textColor := White + if IsLight(r, g, b) { + textColor = Black + } + return func(str string) string { + return textColor(rgbterm.BgString(str, uint8(r), uint8(g), uint8(b))) + } +} + +func Colorize(str string) string { + colorizer := StringToColor(str) + return colorizer(str) +} + +func ColorizeBG(str string) string { + colorizer := StringToColorBG(str) + return colorizer(str) +} + +func calcColor(color uint64) (red, green, blue, alpha uint64) { + alpha = color & 0xFF + blue = (color >> 8) & 0xFF + green = (color >> 16) & 0xFF + red = (color >> 24) & 0xFF + + return red, green, blue, alpha +} + +// IsLight returns whether the color is perceived to be a light color +func IsLight(rr, gg, bb uint64) bool { + + r := float64(rr) + g := float64(gg) + b := float64(bb) + + hsp := math.Sqrt(0.299*math.Pow(r, 2) + 0.587*math.Pow(g, 2) + 0.114*math.Pow(b, 2)) + + return hsp > 130 +} + +var hasherPool *sync.Pool + +func init() { + hasherPool = &sync.Pool{ + New: func() interface{} { + return fnv.New64a() + }, + } +} + +func HashString(s string) uint64 { + h := hasherPool.Get().(hash.Hash64) + defer hasherPool.Put(h) + h.Reset() + _, err := h.Write([]byte(s)) + if err != nil { + panic(err) + } + return h.Sum64() +} diff --git a/text/tree.go b/text/tree.go new file mode 100644 index 00000000..bf809ba4 --- /dev/null +++ b/text/tree.go @@ -0,0 +1,28 @@ +package text + +import ( + "io" + + "github.com/gagliardetto/treeout" +) + +type TreeEncoder struct { + output io.Writer + *treeout.Tree +} + +type EncodableToTree interface { + EncodeToTree(parent treeout.Branches) +} + +func NewTreeEncoder(w io.Writer, docs ...string) *TreeEncoder { + return &TreeEncoder{ + output: w, + Tree: treeout.New(docs...), + } +} + +func (e *TreeEncoder) WriteString(s string) error { + _, err := e.output.Write([]byte(s)) + return err +} diff --git a/transaction.go b/transaction.go index 55b0af40..47e7d052 100644 --- a/transaction.go +++ b/transaction.go @@ -6,6 +6,8 @@ import ( "sort" bin "github.com/dfuse-io/binary" + "github.com/gagliardetto/solana-go/text" + "github.com/gagliardetto/treeout" "go.uber.org/zap" ) @@ -330,3 +332,44 @@ func (tx *Transaction) Sign(getter privateKeyGetter) (out []Signature, err error } return tx.Signatures, nil } + +func (tx *Transaction) EncodeTree(encoder *text.TreeEncoder) error { + if len(encoder.Docs) == 0 { + encoder.Docs = []string{"Transaction"} + } + tx.EncodeToTree(encoder) + return encoder.WriteString(encoder.Tree.String()) +} + +func (tx *Transaction) EncodeToTree(parent treeout.Branches) { + + parent.ParentFunc(func(txTree treeout.Branches) { + txTree.Child("Signatures[]").ParentFunc(func(signaturesBranch treeout.Branches) { + for _, sig := range tx.Signatures { + signaturesBranch.Child(sig.String()) + } + }) + + txTree.Child("Message").ParentFunc(func(messageBranch treeout.Branches) { + tx.Message.EncodeToTree(messageBranch) + }) + }) + + parent.Child("Instructions[]").ParentFunc(func(message treeout.Branches) { + for _, inst := range tx.Message.Instructions { + + progKey, err := tx.ResolveProgramIDIndex(inst.ProgramIDIndex) + if err != nil { + panic(err) + } + + decodedInstruction, err := DecodeInstruction(progKey, inst.ResolveInstructionAccounts(&tx.Message), inst.Data) + if err != nil { + panic(err) + } + if enToTree, ok := decodedInstruction.(text.EncodableToTree); ok { + enToTree.EncodeToTree(message) + } + } + }) +} diff --git a/types.go b/types.go index 845dda74..59487b5d 100644 --- a/types.go +++ b/types.go @@ -18,6 +18,8 @@ import ( "fmt" bin "github.com/dfuse-io/binary" + "github.com/gagliardetto/solana-go/text" + "github.com/gagliardetto/treeout" ) type Transaction struct { @@ -57,6 +59,30 @@ type Message struct { Instructions []CompiledInstruction `json:"instructions"` } +func (mx *Message) EncodeToTree(parent treeout.Branches) { + parent.ParentFunc(func(txTree treeout.Branches) { + txTree.Child(text.Sf("RecentBlockhash: %s", mx.RecentBlockhash)) + + txTree.Child("AccountKeys[]").ParentFunc(func(accountKeysBranch treeout.Branches) { + for _, key := range mx.AccountKeys { + accountKeysBranch.Child(text.ColorizeBG(key.String())) + } + }) + + txTree.Child("Header").ParentFunc(func(message treeout.Branches) { + mx.Header.EncodeToTree(message) + }) + }) +} + +func (header *MessageHeader) EncodeToTree(parent treeout.Branches) { + parent.ParentFunc(func(mxBranch treeout.Branches) { + mxBranch.Child(text.Sf("NumRequiredSignatures: %v", header.NumRequiredSignatures)) + mxBranch.Child(text.Sf("NumReadonlySignedAccounts: %v", header.NumReadonlySignedAccounts)) + mxBranch.Child(text.Sf("NumReadonlyUnsignedAccounts: %v", header.NumReadonlyUnsignedAccounts)) + }) +} + func (mx *Message) MarshalBinary() ([]byte, error) { buf := []byte{ mx.Header.NumRequiredSignatures, From 3af5c3d17acf69bc5651614f68ed95e6d1d17375 Mon Sep 17 00:00:00 2001 From: Slavomir Date: Mon, 9 Aug 2021 15:48:32 +0200 Subject: [PATCH 15/34] Fix: Check DisableColors in Colorize and ColorizeBG --- text/tools.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/text/tools.go b/text/tools.go index 94b1ae03..65ddb33a 100644 --- a/text/tools.go +++ b/text/tools.go @@ -227,11 +227,17 @@ func StringToColorBG(str string) func(string) string { } func Colorize(str string) string { + if DisableColors { + return str + } colorizer := StringToColor(str) return colorizer(str) } func ColorizeBG(str string) string { + if DisableColors { + return str + } colorizer := StringToColorBG(str) return colorizer(str) } From 9a0d5212b14ee60c441f3e7b3a12abc29e4acbdf Mon Sep 17 00:00:00 2001 From: Slavomir Date: Mon, 9 Aug 2021 16:18:05 +0200 Subject: [PATCH 16/34] Cleanup --- programs/system/Transfer.go | 2 +- types.go | 30 +++++++++++++----------------- 2 files changed, 14 insertions(+), 18 deletions(-) diff --git a/programs/system/Transfer.go b/programs/system/Transfer.go index b16aa153..f1a7b2b1 100644 --- a/programs/system/Transfer.go +++ b/programs/system/Transfer.go @@ -46,7 +46,7 @@ func (inst *Transfer) EncodeToTree(parent treeout.Branches) { instructionBranch.Child("Params").ParentFunc(func(paramsBranch treeout.Branches) { paramsBranch.Child(Sf(CC(Shakespeare("Lamports"), ": %v"), Lime(S(inst.Lamports)))) }) - instructionBranch.Child("Accounts:").ParentFunc(func(accountsBranch treeout.Branches) { + instructionBranch.Child("Accounts").ParentFunc(func(accountsBranch treeout.Branches) { accountsBranch.Child(Sf(CC(Shakespeare("Funding account"), ": %s"), text.ColorizeBG(inst.AccountMetaSlice[0].PublicKey.String()))) accountsBranch.Child(Sf(CC(Shakespeare("Recipient account"), ": %s"), text.ColorizeBG(inst.AccountMetaSlice[1].PublicKey.String()))) }) diff --git a/types.go b/types.go index 59487b5d..11705751 100644 --- a/types.go +++ b/types.go @@ -59,28 +59,24 @@ type Message struct { Instructions []CompiledInstruction `json:"instructions"` } -func (mx *Message) EncodeToTree(parent treeout.Branches) { - parent.ParentFunc(func(txTree treeout.Branches) { - txTree.Child(text.Sf("RecentBlockhash: %s", mx.RecentBlockhash)) +func (mx *Message) EncodeToTree(txTree treeout.Branches) { + txTree.Child(text.Sf("RecentBlockhash: %s", mx.RecentBlockhash)) - txTree.Child("AccountKeys[]").ParentFunc(func(accountKeysBranch treeout.Branches) { - for _, key := range mx.AccountKeys { - accountKeysBranch.Child(text.ColorizeBG(key.String())) - } - }) + txTree.Child("AccountKeys[]").ParentFunc(func(accountKeysBranch treeout.Branches) { + for _, key := range mx.AccountKeys { + accountKeysBranch.Child(text.ColorizeBG(key.String())) + } + }) - txTree.Child("Header").ParentFunc(func(message treeout.Branches) { - mx.Header.EncodeToTree(message) - }) + txTree.Child("Header").ParentFunc(func(message treeout.Branches) { + mx.Header.EncodeToTree(message) }) } -func (header *MessageHeader) EncodeToTree(parent treeout.Branches) { - parent.ParentFunc(func(mxBranch treeout.Branches) { - mxBranch.Child(text.Sf("NumRequiredSignatures: %v", header.NumRequiredSignatures)) - mxBranch.Child(text.Sf("NumReadonlySignedAccounts: %v", header.NumReadonlySignedAccounts)) - mxBranch.Child(text.Sf("NumReadonlyUnsignedAccounts: %v", header.NumReadonlyUnsignedAccounts)) - }) +func (header *MessageHeader) EncodeToTree(mxBranch treeout.Branches) { + mxBranch.Child(text.Sf("NumRequiredSignatures: %v", header.NumRequiredSignatures)) + mxBranch.Child(text.Sf("NumReadonlySignedAccounts: %v", header.NumReadonlySignedAccounts)) + mxBranch.Child(text.Sf("NumReadonlyUnsignedAccounts: %v", header.NumReadonlyUnsignedAccounts)) } func (mx *Message) MarshalBinary() ([]byte, error) { From 393281bb2a7ca3f03ab3f26abec42eea7ed45d78 Mon Sep 17 00:00:00 2001 From: Slavomir Date: Mon, 9 Aug 2021 17:21:29 +0200 Subject: [PATCH 17/34] Add else when instruction is not EncodableToTree --- transaction.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/transaction.go b/transaction.go index 47e7d052..8a7220b8 100644 --- a/transaction.go +++ b/transaction.go @@ -5,6 +5,7 @@ import ( "fmt" "sort" + "github.com/davecgh/go-spew/spew" bin "github.com/dfuse-io/binary" "github.com/gagliardetto/solana-go/text" "github.com/gagliardetto/treeout" @@ -369,6 +370,8 @@ func (tx *Transaction) EncodeToTree(parent treeout.Branches) { } if enToTree, ok := decodedInstruction.(text.EncodableToTree); ok { enToTree.EncodeToTree(message) + } else { + message.Child(spew.Sdump(decodedInstruction)) } } }) From e3f106e97c42175a164205498a499365812f8397 Mon Sep 17 00:00:00 2001 From: Slavomir Date: Mon, 9 Aug 2021 17:38:58 +0200 Subject: [PATCH 18/34] Fix WriteString signature --- text/tree.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/text/tree.go b/text/tree.go index bf809ba4..55393e57 100644 --- a/text/tree.go +++ b/text/tree.go @@ -22,7 +22,6 @@ func NewTreeEncoder(w io.Writer, docs ...string) *TreeEncoder { } } -func (e *TreeEncoder) WriteString(s string) error { - _, err := e.output.Write([]byte(s)) - return err +func (enc *TreeEncoder) WriteString(s string) (int, error) { + return enc.output.Write([]byte(s)) } From 8f5a9371233dfbdddc901f75b620a086d8f0679c Mon Sep 17 00:00:00 2001 From: Slavomir Date: Mon, 9 Aug 2021 17:39:09 +0200 Subject: [PATCH 19/34] Fix WriteString signature --- transaction.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/transaction.go b/transaction.go index 8a7220b8..94d050fe 100644 --- a/transaction.go +++ b/transaction.go @@ -334,7 +334,7 @@ func (tx *Transaction) Sign(getter privateKeyGetter) (out []Signature, err error return tx.Signatures, nil } -func (tx *Transaction) EncodeTree(encoder *text.TreeEncoder) error { +func (tx *Transaction) EncodeTree(encoder *text.TreeEncoder) (int, error) { if len(encoder.Docs) == 0 { encoder.Docs = []string{"Transaction"} } From 7c9a344746429533bbf561e8dde09eba3bc225cd Mon Sep 17 00:00:00 2001 From: Slavomir Date: Wed, 11 Aug 2021 17:02:20 +0200 Subject: [PATCH 20/34] Add rpc/errors --- rpc/errors.go | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 rpc/errors.go diff --git a/rpc/errors.go b/rpc/errors.go new file mode 100644 index 00000000..8b3d11fd --- /dev/null +++ b/rpc/errors.go @@ -0,0 +1,11 @@ +package rpc + +// rpc error: +// - https://github.com/solana-labs/solana/blob/d5961e9d9f005966f409fbddd40c3651591b27fb/client/src/rpc_custom_error.rs + +// transaction error: +// - https://github.com/solana-labs/solana/blob/8817f59b6e5ce26f578b0834dbfac5c7daa82fab/sdk/src/transaction.rs +// - https://github.com/solana-labs/solana/blob/899b09872bd7c4af4d1c9c73f7b26d268af276eb/storage-proto/proto/transaction_by_addr.proto + +// instruction error +// - https://github.com/solana-labs/solana/blob/f6371cce176d481b4132e5061262ca015db0f8b1/sdk/program/src/instruction.rs From 90b6f910bc61894994a48ecc93050bf0118e9d8d Mon Sep 17 00:00:00 2001 From: Slavomir Date: Wed, 11 Aug 2021 18:21:35 +0200 Subject: [PATCH 21/34] Clean up formatting --- programs/system/Transfer.go | 17 ++++++++------ programs/system/instructions.go | 41 ++++++++++++++++++++++++++++++--- text/format/format.go | 23 ++++++++++++++++++ 3 files changed, 71 insertions(+), 10 deletions(-) create mode 100644 text/format/format.go diff --git a/programs/system/Transfer.go b/programs/system/Transfer.go index f1a7b2b1..e6403c05 100644 --- a/programs/system/Transfer.go +++ b/programs/system/Transfer.go @@ -3,8 +3,7 @@ package system import ( bin "github.com/dfuse-io/binary" solana "github.com/gagliardetto/solana-go" - "github.com/gagliardetto/solana-go/text" - . "github.com/gagliardetto/solana-go/text" + "github.com/gagliardetto/solana-go/text/format" "github.com/gagliardetto/treeout" ) @@ -39,16 +38,20 @@ type Transfer struct { } func (inst *Transfer) EncodeToTree(parent treeout.Branches) { - parent.Child(Sf(CC(IndigoBG(Bold("Program")), ": System (%s)"), text.ColorizeBG(PROGRAM_ID.String()))). + parent.Child(format.Program(ProgramName, ProgramID)). + // ParentFunc(func(programBranch treeout.Branches) { - programBranch.Child(CC(Purple(Bold("Instruction")), Bold(": Transfer"))). + programBranch.Child(format.Instruction("Transfer")). + // ParentFunc(func(instructionBranch treeout.Branches) { + // instructionBranch.Child("Params").ParentFunc(func(paramsBranch treeout.Branches) { - paramsBranch.Child(Sf(CC(Shakespeare("Lamports"), ": %v"), Lime(S(inst.Lamports)))) + paramsBranch.Child(format.Param("Lamports", inst.Lamports)) }) + // instructionBranch.Child("Accounts").ParentFunc(func(accountsBranch treeout.Branches) { - accountsBranch.Child(Sf(CC(Shakespeare("Funding account"), ": %s"), text.ColorizeBG(inst.AccountMetaSlice[0].PublicKey.String()))) - accountsBranch.Child(Sf(CC(Shakespeare("Recipient account"), ": %s"), text.ColorizeBG(inst.AccountMetaSlice[1].PublicKey.String()))) + accountsBranch.Child(format.Account("FundingAccount", inst.AccountMetaSlice[0].PublicKey)) + accountsBranch.Child(format.Account("RecipientAccount", inst.AccountMetaSlice[1].PublicKey)) }) }) }) diff --git a/programs/system/instructions.go b/programs/system/instructions.go index ce8e8886..4a49986a 100644 --- a/programs/system/instructions.go +++ b/programs/system/instructions.go @@ -26,10 +26,14 @@ import ( "github.com/gagliardetto/treeout" ) -var PROGRAM_ID = solana.MustPublicKeyFromBase58("11111111111111111111111111111111") +var ( + ProgramID = solana.MustPublicKeyFromBase58("11111111111111111111111111111111") +) + +const ProgramName = "System" func init() { - solana.RegisterInstructionDecoder(PROGRAM_ID, registryDecodeInstruction) + solana.RegisterInstructionDecoder(ProgramID, registryDecodeInstruction) } const ( @@ -70,6 +74,37 @@ const ( Instruction_TransferWithSeed ) +func InstructionIDToName(id uint32) string { + switch id { + case Instruction_CreateAccount: + return "CreateAccount" + case Instruction_Assign: + return "Assign" + case Instruction_Transfer: + return "Transfer" + case Instruction_CreateAccountWithSeed: + return "CreateAccountWithSeed" + case Instruction_AdvanceNonceAccount: + return "AdvanceNonceAccount" + case Instruction_WithdrawNonceAccount: + return "WithdrawNonceAccount" + case Instruction_InitializeNonceAccount: + return "InitializeNonceAccount" + case Instruction_AuthorizeNonceAccount: + return "AuthorizeNonceAccount" + case Instruction_Allocate: + return "Allocate" + case Instruction_AllocateWithSeed: + return "AllocateWithSeed" + case Instruction_AssignWithSeed: + return "AssignWithSeed" + case Instruction_TransferWithSeed: + return "TransferWithSeed" + default: + return "" + } +} + type Instruction struct { bin.BaseVariant } @@ -138,7 +173,7 @@ var InstructionImplDef = bin.NewVariantDefinition( }) func (i *Instruction) ProgramID() solana.PublicKey { - return PROGRAM_ID + return ProgramID } func (i *Instruction) Data() ([]byte, error) { diff --git a/text/format/format.go b/text/format/format.go new file mode 100644 index 00000000..193a3a54 --- /dev/null +++ b/text/format/format.go @@ -0,0 +1,23 @@ +package format + +import ( + "github.com/gagliardetto/solana-go" + "github.com/gagliardetto/solana-go/text" + . "github.com/gagliardetto/solana-go/text" +) + +func Program(name string, programID solana.PublicKey) string { + return IndigoBG("Program") + ": " + Bold("System") + " " + text.ColorizeBG(programID.String()) +} + +func Instruction(name string) string { + return Purple(Bold("Instruction")) + ": " + Bold("Transfer") +} + +func Param(name string, value interface{}) string { + return Sf(CC(Shakespeare(name), ": %s"), Lime(S(value))) +} + +func Account(name string, pubKey solana.PublicKey) string { + return Shakespeare(name) + ": " + text.ColorizeBG(pubKey.String()) +} From fa2a3ae003638358f273e11b8d5731578285a0cb Mon Sep 17 00:00:00 2001 From: Slavomir Date: Wed, 11 Aug 2021 18:59:15 +0200 Subject: [PATCH 22/34] Move sysvars to solana package --- programs/system/AdvanceNonceAccount.go | 2 +- programs/system/InitializeNonceAccount.go | 4 ++-- programs/system/WithdrawNonceAccount.go | 4 ++-- programs/system/sysvar.go => sysvar.go | 24 +++++++++++------------ 4 files changed, 16 insertions(+), 18 deletions(-) rename programs/system/sysvar.go => sysvar.go (63%) diff --git a/programs/system/AdvanceNonceAccount.go b/programs/system/AdvanceNonceAccount.go index ce6f3c4f..26e7443b 100644 --- a/programs/system/AdvanceNonceAccount.go +++ b/programs/system/AdvanceNonceAccount.go @@ -30,7 +30,7 @@ func NewAdvanceNonceAccountBuilder() *AdvanceNonceAccount { nb := &AdvanceNonceAccount{ AccountMetaSlice: make(solana.AccountMetaSlice, 3), } - nb.AccountMetaSlice[1] = solana.Meta(SysVarRecentBlockHashesPubkey) + nb.AccountMetaSlice[1] = solana.Meta(solana.SysVarRecentBlockHashesPubkey) return nb } diff --git a/programs/system/InitializeNonceAccount.go b/programs/system/InitializeNonceAccount.go index 8d072056..c00385f5 100644 --- a/programs/system/InitializeNonceAccount.go +++ b/programs/system/InitializeNonceAccount.go @@ -18,8 +18,8 @@ func NewInitializeNonceAccountInstruction( AuthPubKey: authPubKey, AccountMetaSlice: []*solana.AccountMeta{ solana.Meta(noncePubKey).WRITE(), - solana.Meta(SysVarRecentBlockHashesPubkey), - solana.Meta(SysVarRentPubkey), + solana.Meta(solana.SysVarRecentBlockHashesPubkey), + solana.Meta(solana.SysVarRentPubkey), }, }, }, diff --git a/programs/system/WithdrawNonceAccount.go b/programs/system/WithdrawNonceAccount.go index c3ecb172..6c2cecde 100644 --- a/programs/system/WithdrawNonceAccount.go +++ b/programs/system/WithdrawNonceAccount.go @@ -22,8 +22,8 @@ func NewWithdrawNonceAccountInstruction( AccountMetaSlice: []*solana.AccountMeta{ solana.Meta(nonceAccount).WRITE(), solana.Meta(recipientAccount).WRITE(), - solana.Meta(SysVarRecentBlockHashesPubkey), - solana.Meta(SysVarRentPubkey), + solana.Meta(solana.SysVarRecentBlockHashesPubkey), + solana.Meta(solana.SysVarRentPubkey), solana.Meta(nonceAuthority).SIGNER(), }, }, diff --git a/programs/system/sysvar.go b/sysvar.go similarity index 63% rename from programs/system/sysvar.go rename to sysvar.go index 17127c8b..4815c479 100644 --- a/programs/system/sysvar.go +++ b/sysvar.go @@ -1,6 +1,4 @@ -package system - -import "github.com/gagliardetto/solana-go" +package solana // See more here: https://github.com/solana-labs/solana/blob/master/docs/src/developing/runtime-facilities/sysvars.md @@ -9,45 +7,45 @@ var ( // The Clock sysvar contains data on cluster time, // including the current slot, epoch, and estimated wall-clock Unix timestamp. // It is updated every slot. - SysVarClockPubkey = solana.MustPublicKeyFromBase58("SysvarC1ock11111111111111111111111111111111") + SysVarClockPubkey = MustPublicKeyFromBase58("SysvarC1ock11111111111111111111111111111111") // The EpochSchedule sysvar contains epoch scheduling constants that are set in genesis, // and enables calculating the number of slots in a given epoch, // the epoch for a given slot, etc. // (Note: the epoch schedule is distinct from the leader schedule) - SysVarEpochSchedulePubkey = solana.MustPublicKeyFromBase58("SysvarEpochSchedu1e111111111111111111111111") + SysVarEpochSchedulePubkey = MustPublicKeyFromBase58("SysvarEpochSchedu1e111111111111111111111111") // The Fees sysvar contains the fee calculator for the current slot. // It is updated every slot, based on the fee-rate governor. - SysVarFeesPubkey = solana.MustPublicKeyFromBase58("SysvarFees111111111111111111111111111111111") + SysVarFeesPubkey = MustPublicKeyFromBase58("SysvarFees111111111111111111111111111111111") // The Instructions sysvar contains the serialized instructions in a Message while that Message is being processed. // This allows program instructions to reference other instructions in the same transaction. - SysVarInstructionsPubkey = solana.MustPublicKeyFromBase58("Sysvar1nstructions1111111111111111111111111") + SysVarInstructionsPubkey = MustPublicKeyFromBase58("Sysvar1nstructions1111111111111111111111111") // The RecentBlockhashes sysvar contains the active recent blockhashes as well as their associated fee calculators. // It is updated every slot. // Entries are ordered by descending block height, // so the first entry holds the most recent block hash, // and the last entry holds an old block hash. - SysVarRecentBlockHashesPubkey = solana.MustPublicKeyFromBase58("SysvarRecentB1ockHashes11111111111111111111") + SysVarRecentBlockHashesPubkey = MustPublicKeyFromBase58("SysvarRecentB1ockHashes11111111111111111111") // The Rent sysvar contains the rental rate. // Currently, the rate is static and set in genesis. // The Rent burn percentage is modified by manual feature activation. - SysVarRentPubkey = solana.MustPublicKeyFromBase58("SysvarRent111111111111111111111111111111111") + SysVarRentPubkey = MustPublicKeyFromBase58("SysvarRent111111111111111111111111111111111") // The SlotHashes sysvar contains the most recent hashes of the slot's parent banks. // It is updated every slot. - SysVarSlotHashesPubkey = solana.MustPublicKeyFromBase58("SysvarS1otHashes111111111111111111111111111") + SysVarSlotHashesPubkey = MustPublicKeyFromBase58("SysvarS1otHashes111111111111111111111111111") // The SlotHistory sysvar contains a bitvector of slots present over the last epoch. It is updated every slot. - SysVarSlotHistoryPubkey = solana.MustPublicKeyFromBase58("SysvarS1otHistory11111111111111111111111111") + SysVarSlotHistoryPubkey = MustPublicKeyFromBase58("SysvarS1otHistory11111111111111111111111111") // 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 = solana.MustPublicKeyFromBase58("SysvarStakeHistory1111111111111111111111111") + SysVarStakeHistoryPubkey = MustPublicKeyFromBase58("SysvarStakeHistory1111111111111111111111111") // - SysVarRewardsPubkey = solana.MustPublicKeyFromBase58("SysvarRewards111111111111111111111111111111") + SysVarRewardsPubkey = MustPublicKeyFromBase58("SysvarRewards111111111111111111111111111111") ) From 1ff78310437b4149603580a0ecea84de1fee4e71 Mon Sep 17 00:00:00 2001 From: Slavomir Date: Sat, 21 Aug 2021 17:53:13 +0200 Subject: [PATCH 23/34] Upgrade binary package --- cmd/slnc/cmd/decoding.go | 4 +- cmd/slnc/cmd/get_spl_token.go | 2 +- programs/serum/instruction.go | 8 ++-- programs/serum/instruction_test.go | 2 +- programs/serum/queue.go | 16 +++++--- programs/serum/rpc.go | 2 +- programs/serum/types.go | 11 ++++-- programs/serum/types_test.go | 10 ++--- programs/system/instructions.go | 10 +++-- programs/token/instructions.go | 8 ++-- programs/token/types.go | 2 +- programs/token/types_test.go | 4 +- programs/tokenregistry/instruction.go | 10 +++-- programs/tokenregistry/types.go | 2 +- rpc/examples/getAccountInfo/getAccountInfo.go | 4 +- rpc/getAccountInfo.go | 2 +- transaction.go | 16 ++++++-- transaction_test.go | 3 +- types.go | 38 ++++++++++++------- types_test.go | 2 +- 20 files changed, 96 insertions(+), 60 deletions(-) diff --git a/cmd/slnc/cmd/decoding.go b/cmd/slnc/cmd/decoding.go index 6f584ce8..b64f7e83 100644 --- a/cmd/slnc/cmd/decoding.go +++ b/cmd/slnc/cmd/decoding.go @@ -39,14 +39,14 @@ func decodeAsToken(data []byte) (out interface{}, err error) { case 120: var tokenAcct token.Account - if err := bin.NewDecoder(data).Decode(&tokenAcct); err != nil { + if err := bin.NewBinDecoder(data).Decode(&tokenAcct); err != nil { return nil, fmt.Errorf("failed unpacking: %w", err) } return tokenAcct, nil case 40: var mint token.Mint - if err := bin.NewDecoder(data).Decode(&mint); err != nil { + if err := bin.NewBinDecoder(data).Decode(&mint); err != nil { log.Fatalln("failed unpack", err) } diff --git a/cmd/slnc/cmd/get_spl_token.go b/cmd/slnc/cmd/get_spl_token.go index 9dde8e0b..6716d65e 100644 --- a/cmd/slnc/cmd/get_spl_token.go +++ b/cmd/slnc/cmd/get_spl_token.go @@ -58,7 +58,7 @@ var getSPLTokenCmd = &cobra.Command{ acct := keyedAcct.Account //fmt.Println("Data len:", len(acct.Data), keyedAcct.Pubkey) var mint *token.Mint - if err := bin.NewDecoder(acct.Data.GetBinary()).Decode(&mint); err != nil { + if err := bin.NewBinDecoder(acct.Data.GetBinary()).Decode(&mint); err != nil { log.Fatalln("failed unpack", err) } diff --git a/programs/serum/instruction.go b/programs/serum/instruction.go index 2f7cdee2..17bcc2a9 100644 --- a/programs/serum/instruction.go +++ b/programs/serum/instruction.go @@ -24,7 +24,7 @@ func registryDecodeInstruction(accounts []*solana.AccountMeta, data []byte) (int func DecodeInstruction(accounts []*solana.AccountMeta, data []byte) (*Instruction, error) { // FIXME: can't we dedupe this in some ways? It's copied in all of the programs' folders. var inst Instruction - if err := bin.NewDecoder(data).Decode(&inst); err != nil { + if err := bin.NewBinDecoder(data).Decode(&inst); err != nil { return nil, fmt.Errorf("unable to decode instruction for serum program: %w", err) } @@ -62,11 +62,13 @@ type Instruction struct { Version uint8 } +var _ bin.EncoderDecoder = &Instruction{} + func (i *Instruction) TextEncode(encoder *text.Encoder, option *text.Option) error { return encoder.Encode(i.Impl, option) } -func (i *Instruction) UnmarshalBinary(decoder *bin.Decoder) (err error) { +func (i *Instruction) UnmarshalWithDecoder(decoder *bin.Decoder) (err error) { i.Version, err = decoder.ReadUint8() if err != nil { return fmt.Errorf("unable to read version: %w", err) @@ -74,7 +76,7 @@ func (i *Instruction) UnmarshalBinary(decoder *bin.Decoder) (err error) { return i.BaseVariant.UnmarshalBinaryVariant(decoder, InstructionDefVariant) } -func (i *Instruction) MarshalBinary(encoder *bin.Encoder) error { +func (i *Instruction) MarshalWithEncoder(encoder *bin.Encoder) error { err := encoder.WriteUint8(i.Version) if err != nil { return fmt.Errorf("unable to write instruction version: %w", err) diff --git a/programs/serum/instruction_test.go b/programs/serum/instruction_test.go index d64d6e3f..e57b41a6 100644 --- a/programs/serum/instruction_test.go +++ b/programs/serum/instruction_test.go @@ -63,7 +63,7 @@ func TestDecodeInstruction(t *testing.T) { data, err := hex.DecodeString(test.hexData) require.NoError(t, err) var instruction *Instruction - err = bin.NewDecoder(data).Decode(&instruction) + err = bin.NewBinDecoder(data).Decode(&instruction) require.NoError(t, err) assert.Equal(t, test.expectInstruction, instruction) }) diff --git a/programs/serum/queue.go b/programs/serum/queue.go index 07860853..bb4bc9ec 100644 --- a/programs/serum/queue.go +++ b/programs/serum/queue.go @@ -35,11 +35,13 @@ type RequestQueue struct { } func (r *RequestQueue) Decode(data []byte) error { - decoder := bin.NewDecoder(data) + decoder := bin.NewBinDecoder(data) return decoder.Decode(&r) } -func (q *RequestQueue) UnmarshalBinary(decoder *bin.Decoder) (err error) { +var _ bin.EncoderDecoder = &RequestQueue{} + +func (q *RequestQueue) UnmarshalWithDecoder(decoder *bin.Decoder) (err error) { if err = decoder.SkipBytes(5); err != nil { return err } @@ -81,7 +83,7 @@ func (q *RequestQueue) UnmarshalBinary(decoder *bin.Decoder) (err error) { } // TODO: fill up later -func (q *RequestQueue) MarshalBinary(encoder *bin.Encoder) error { +func (q *RequestQueue) MarshalWithEncoder(encoder *bin.Encoder) error { return nil } @@ -187,12 +189,14 @@ type EventQueue struct { EndPadding [7]byte `json:"-"` } +var _ bin.EncoderDecoder = &EventQueue{} + func (q *EventQueue) Decode(data []byte) error { - decoder := bin.NewDecoder(data) + decoder := bin.NewBinDecoder(data) return decoder.Decode(&q) } -func (q *EventQueue) UnmarshalBinary(decoder *bin.Decoder) (err error) { +func (q *EventQueue) UnmarshalWithDecoder(decoder *bin.Decoder) (err error) { if err = decoder.SkipBytes(5); err != nil { return err } @@ -234,7 +238,7 @@ func (q *EventQueue) UnmarshalBinary(decoder *bin.Decoder) (err error) { } // TODO: fill up later -func (q *EventQueue) MarshalBinary(encoder *bin.Encoder) error { +func (q *EventQueue) MarshalWithEncoder(encoder *bin.Encoder) error { return nil } diff --git a/programs/serum/rpc.go b/programs/serum/rpc.go index 84cca2ce..baba0fa1 100644 --- a/programs/serum/rpc.go +++ b/programs/serum/rpc.go @@ -112,7 +112,7 @@ func StreamOpenOrders(client *ws.Client) error { res := d var f *AccountFlag - err = bin.NewDecoder(res.Value.Account.Data.GetBinary()).Decode(&f) + err = bin.NewBinDecoder(res.Value.Account.Data.GetBinary()).Decode(&f) if err != nil { fmt.Println("***********************************", err) zlog.Debug("unable to decoce account flag for account... skipping", diff --git a/programs/serum/types.go b/programs/serum/types.go index d8bd5a03..b6e19185 100644 --- a/programs/serum/types.go +++ b/programs/serum/types.go @@ -95,7 +95,7 @@ type MarketV2 struct { } func (m *MarketV2) Decode(in []byte) error { - decoder := bin.NewDecoder(in) + decoder := bin.NewBinDecoder(in) err := decoder.Decode(&m) if err != nil { return fmt.Errorf("unpack: %w", err) @@ -160,10 +160,13 @@ type Slab struct { bin.BaseVariant } -func (s *Slab) UnmarshalBinary(decoder *bin.Decoder) error { +var _ bin.EncoderDecoder = &Slab{} + +func (s *Slab) UnmarshalWithDecoder(decoder *bin.Decoder) error { return s.BaseVariant.UnmarshalBinaryVariant(decoder, SlabFactoryImplDef) } -func (s *Slab) MarshalBinary(encoder *bin.Encoder) error { + +func (s *Slab) MarshalWithEncoder(encoder *bin.Encoder) error { err := encoder.WriteUint32(s.TypeID, binary.LittleEndian) if err != nil { return err @@ -305,7 +308,7 @@ func (o *OpenOrders) GetOrder(index uint32) *Order { } func (o *OpenOrders) Decode(in []byte) error { - decoder := bin.NewDecoder(in) + decoder := bin.NewBinDecoder(in) err := decoder.Decode(&o) if err != nil { return fmt.Errorf("unpack: %w", err) diff --git a/programs/serum/types_test.go b/programs/serum/types_test.go index bd2fa1ac..2b35feef 100644 --- a/programs/serum/types_test.go +++ b/programs/serum/types_test.go @@ -22,7 +22,7 @@ func TestAccountFlag_Decoder(t *testing.T) { require.NoError(t, err) var f *AccountFlag - err = bin.NewDecoder(data).Decode(&f) + err = bin.NewBinDecoder(data).Decode(&f) require.NoError(t, err) assert.Equal(t, f.Is(AccountFlagInitialized), true, "initialized") @@ -39,7 +39,7 @@ func TestAccountFlag_Decoder(t *testing.T) { require.NoError(t, err) var f2 *AccountFlag - err = bin.NewDecoder(data).Decode(&f2) + err = bin.NewBinDecoder(data).Decode(&f2) require.NoError(t, err) assert.Equal(t, f2.Is(AccountFlagInitialized), true, "initialized") @@ -60,7 +60,7 @@ func TestDecoder_Market(t *testing.T) { fmt.Println(hex.EncodeToString(data)) var m *MarketV2 - err = bin.NewDecoder(data).Decode(&m) + err = bin.NewBinDecoder(data).Decode(&m) require.NoError(t, err) assert.Equal(t, true, m.AccountFlags.Is(AccountFlagInitialized)) @@ -77,7 +77,7 @@ func TestDecoder_Orderbook(t *testing.T) { data, err := hex.DecodeString(string(cnt)) require.NoError(t, err) - decoder := bin.NewDecoder(data) + decoder := bin.NewBinDecoder(data) var ob *Orderbook err = decoder.Decode(&ob) require.NoError(t, err) @@ -210,7 +210,7 @@ func TestDecoder_Slabs(t *testing.T) { cnt, err := hex.DecodeString(test.slabData) require.NoError(t, err) - decoder := bin.NewDecoder(cnt) + decoder := bin.NewBinDecoder(cnt) var slab *Slab err = decoder.Decode(&slab) require.NoError(t, err) diff --git a/programs/system/instructions.go b/programs/system/instructions.go index 4a49986a..0e4a81cf 100644 --- a/programs/system/instructions.go +++ b/programs/system/instructions.go @@ -109,6 +109,8 @@ type Instruction struct { bin.BaseVariant } +var _ bin.EncoderDecoder = &Instruction{} + var ( // TODO: each instruction must be here: _ solana.AccountsGettable = &CreateAccount{} @@ -178,7 +180,7 @@ func (i *Instruction) ProgramID() solana.PublicKey { func (i *Instruction) Data() ([]byte, error) { buf := new(bytes.Buffer) - if err := bin.NewEncoder(buf).Encode(i); err != nil { + if err := bin.NewBinEncoder(buf).Encode(i); err != nil { return nil, fmt.Errorf("unable to encode instruction: %w", err) } return buf.Bytes(), nil @@ -188,11 +190,11 @@ func (i *Instruction) TextEncode(encoder *text.Encoder, option *text.Option) err return encoder.Encode(i.Impl, option) } -func (i *Instruction) UnmarshalBinary(decoder *bin.Decoder) error { +func (i *Instruction) UnmarshalWithDecoder(decoder *bin.Decoder) error { return i.BaseVariant.UnmarshalBinaryVariant(decoder, InstructionImplDef) } -func (i *Instruction) MarshalBinary(encoder *bin.Encoder) error { +func (i *Instruction) MarshalWithEncoder(encoder *bin.Encoder) error { err := encoder.WriteUint32(i.TypeID, binary.LittleEndian) if err != nil { return fmt.Errorf("unable to write variant type: %w", err) @@ -210,7 +212,7 @@ func registryDecodeInstruction(accounts []*solana.AccountMeta, data []byte) (int func DecodeInstruction(accounts []*solana.AccountMeta, data []byte) (*Instruction, error) { var inst *Instruction - if err := bin.NewDecoder(data).Decode(&inst); err != nil { + if err := bin.NewBinDecoder(data).Decode(&inst); err != nil { return nil, fmt.Errorf("unable to decode instruction: %w", err) } diff --git a/programs/token/instructions.go b/programs/token/instructions.go index fbc5ef93..d4f6eba5 100644 --- a/programs/token/instructions.go +++ b/programs/token/instructions.go @@ -39,7 +39,7 @@ func registryDecodeInstruction(accounts []*solana.AccountMeta, data []byte) (int func DecodeInstruction(accounts []*solana.AccountMeta, data []byte) (*Instruction, error) { var inst Instruction - if err := bin.NewDecoder(data).Decode(&inst); err != nil { + if err := bin.NewBinDecoder(data).Decode(&inst); err != nil { return nil, fmt.Errorf("unable to decode instruction for serum program: %w", err) } @@ -76,11 +76,13 @@ type Instruction struct { bin.BaseVariant } -func (i *Instruction) UnmarshalBinary(decoder *bin.Decoder) (err error) { +var _ bin.EncoderDecoder = &Instruction{} + +func (i *Instruction) UnmarshalWithDecoder(decoder *bin.Decoder) (err error) { return i.BaseVariant.UnmarshalBinaryVariant(decoder, InstructionDefVariant) } -func (i *Instruction) MarshalBinary(encoder *bin.Encoder) error { +func (i *Instruction) MarshalWithEncoder(encoder *bin.Encoder) error { err := encoder.WriteUint8(uint8(i.TypeID)) if err != nil { return fmt.Errorf("unable to write variant type: %w", err) diff --git a/programs/token/types.go b/programs/token/types.go index 8c67a9ad..81e2d57e 100644 --- a/programs/token/types.go +++ b/programs/token/types.go @@ -50,7 +50,7 @@ type Mint struct { } func (m *Mint) Decode(in []byte) error { - decoder := bin.NewDecoder(in) + decoder := bin.NewBinDecoder(in) err := decoder.Decode(&m) if err != nil { return fmt.Errorf("unpack: %w", err) diff --git a/programs/token/types_test.go b/programs/token/types_test.go index 841163d4..159e08fd 100644 --- a/programs/token/types_test.go +++ b/programs/token/types_test.go @@ -41,7 +41,7 @@ func TestAccount(t *testing.T) { // 01000000 // is initialized, is native + padding // 0000000000000000 // delegate amount var out Account - err := bin.NewDecoder(data).Decode(&out) + err := bin.NewBinDecoder(data).Decode(&out) require.NoError(t, err) expect := Account{ @@ -59,7 +59,7 @@ func TestAccount(t *testing.T) { assert.JSONEq(t, string(expectJSON), string(outJSON)) buf := &bytes.Buffer{} - assert.NoError(t, bin.NewEncoder(buf).Encode(out)) + assert.NoError(t, bin.NewBinEncoder(buf).Encode(out)) assert.Equal(t, b58data, base58.Encode(buf.Bytes())) } diff --git a/programs/tokenregistry/instruction.go b/programs/tokenregistry/instruction.go index 935b099e..fb40fc2f 100644 --- a/programs/tokenregistry/instruction.go +++ b/programs/tokenregistry/instruction.go @@ -25,7 +25,7 @@ func registryDecodeInstruction(accounts []*solana.AccountMeta, data []byte) (int func DecodeInstruction(accounts []*solana.AccountMeta, data []byte) (*Instruction, error) { var inst Instruction - if err := bin.NewDecoder(data).Decode(&inst); err != nil { + if err := bin.NewBinDecoder(data).Decode(&inst); err != nil { return nil, fmt.Errorf("unable to decode instruction for serum program: %w", err) } @@ -62,6 +62,8 @@ type Instruction struct { bin.BaseVariant } +var _ bin.EncoderDecoder = &Instruction{} + func (i *Instruction) Accounts() (out []*solana.AccountMeta) { switch i.TypeID { case 0: @@ -77,7 +79,7 @@ func (i *Instruction) ProgramID() solana.PublicKey { func (i *Instruction) Data() ([]byte, error) { buf := new(bytes.Buffer) - if err := bin.NewEncoder(buf).Encode(i); err != nil { + if err := bin.NewBinEncoder(buf).Encode(i); err != nil { return nil, fmt.Errorf("unable to encode instruction: %w", err) } return buf.Bytes(), nil @@ -91,11 +93,11 @@ func (i *Instruction) TextEncode(encoder *text.Encoder, option *text.Option) err return encoder.Encode(i.Impl, option) } -func (i *Instruction) UnmarshalBinary(decoder *bin.Decoder) (err error) { +func (i *Instruction) UnmarshalWithDecoder(decoder *bin.Decoder) (err error) { return i.BaseVariant.UnmarshalBinaryVariant(decoder, InstructionDefVariant) } -func (i *Instruction) MarshalBinary(encoder *bin.Encoder) error { +func (i *Instruction) MarshalWithEncoder(encoder *bin.Encoder) error { err := encoder.WriteUint32(i.TypeID, binary.LittleEndian) if err != nil { return fmt.Errorf("unable to write variant type: %w", err) diff --git a/programs/tokenregistry/types.go b/programs/tokenregistry/types.go index 7b7c552f..87c7d93f 100644 --- a/programs/tokenregistry/types.go +++ b/programs/tokenregistry/types.go @@ -37,7 +37,7 @@ type TokenMeta struct { func DecodeTokenMeta(in []byte) (*TokenMeta, error) { var t *TokenMeta - decoder := bin.NewDecoder(in) + decoder := bin.NewBinDecoder(in) err := decoder.Decode(&t) if err != nil { return nil, fmt.Errorf("unpack: %w", err) diff --git a/rpc/examples/getAccountInfo/getAccountInfo.go b/rpc/examples/getAccountInfo/getAccountInfo.go index 31dab4d1..78bbc2fe 100644 --- a/rpc/examples/getAccountInfo/getAccountInfo.go +++ b/rpc/examples/getAccountInfo/getAccountInfo.go @@ -29,7 +29,7 @@ func main() { var mint token.Mint // Account{}.Data.GetBinary() returns the *decoded* binary data // regardless the original encoding (it can handle them all). - err = bin.NewDecoder(resp.Value.Data.GetBinary()).Decode(&mint) + err = bin.NewBinDecoder(resp.Value.Data.GetBinary()).Decode(&mint) if err != nil { panic(err) } @@ -77,7 +77,7 @@ func main() { spew.Dump(resp) var mint token.Mint - err = bin.NewDecoder(resp.Value.Data.GetBinary()).Decode(&mint) + err = bin.NewBinDecoder(resp.Value.Data.GetBinary()).Decode(&mint) if err != nil { panic(err) } diff --git a/rpc/getAccountInfo.go b/rpc/getAccountInfo.go index 54e68c4d..8b404bc5 100644 --- a/rpc/getAccountInfo.go +++ b/rpc/getAccountInfo.go @@ -41,7 +41,7 @@ func (cl *Client) GetAccountDataIn(ctx context.Context, account solana.PublicKey if err != nil { return err } - return bin.NewDecoder(resp.Value.Data.GetBinary()).Decode(inVar) + return bin.NewBinDecoder(resp.Value.Data.GetBinary()).Decode(inVar) } type GetAccountInfoOpts struct { diff --git a/transaction.go b/transaction.go index 94d050fe..011dd45c 100644 --- a/transaction.go +++ b/transaction.go @@ -273,7 +273,7 @@ func (tx *Transaction) MarshalBinary() ([]byte, error) { } var signatureCount []byte - bin.EncodeLength(&signatureCount, len(tx.Signatures)) + 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 { @@ -284,9 +284,17 @@ func (tx *Transaction) MarshalBinary() ([]byte, error) { return output, nil } -func (tx *Transaction) UnmarshalBinary(decoder *bin.Decoder) (err error) { +func (tx *Transaction) MarshalWithEncoder(encoder *bin.Encoder) error { + out, err := tx.MarshalBinary() + if err != nil { + return err + } + return encoder.WriteBytes(out, false) +} + +func (tx *Transaction) UnmarshalWithDecoder(decoder *bin.Decoder) (err error) { { - numSignatures, err := bin.DecodeLengthFromByteReader(decoder) + numSignatures, err := bin.DecodeCompactU16LengthFromByteReader(decoder) if err != nil { return err } @@ -302,7 +310,7 @@ func (tx *Transaction) UnmarshalBinary(decoder *bin.Decoder) (err error) { } } { - err := tx.Message.UnmarshalBinary(decoder) + err := tx.Message.UnmarshalWithDecoder(decoder) if err != nil { return err } diff --git a/transaction_test.go b/transaction_test.go index 754cb7b7..6fc5b120 100644 --- a/transaction_test.go +++ b/transaction_test.go @@ -4,6 +4,7 @@ import ( "encoding/base64" "testing" + bin "github.com/dfuse-io/binary" "github.com/magiconair/properties/assert" "github.com/mr-tron/base58" "github.com/stretchr/testify/require" @@ -98,7 +99,7 @@ func TestTransactionDecode(t *testing.T) { data, err := base64.StdEncoding.DecodeString(encoded) require.NoError(t, err) - tx, err := TransactionFromData(data) + tx, err := TransactionFromDecoder(bin.NewBinDecoder(data)) require.NoError(t, err) require.NotNil(t, tx) diff --git a/types.go b/types.go index 11705751..7a4a9fd5 100644 --- a/types.go +++ b/types.go @@ -33,6 +33,8 @@ type Transaction struct { Message Message `json:"message"` } +var _ bin.EncoderDecoder = &Transaction{} + func (t *Transaction) TouchAccount(account PublicKey) bool { return t.Message.TouchAccount(account) } func (t *Transaction) IsSigner(account PublicKey) bool { return t.Message.IsSigner(account) } func (t *Transaction) IsWritable(account PublicKey) bool { return t.Message.IsWritable(account) } @@ -59,6 +61,8 @@ type Message struct { Instructions []CompiledInstruction `json:"instructions"` } +var _ bin.EncoderDecoder = &Message{} + func (mx *Message) EncodeToTree(txTree treeout.Branches) { txTree.Child(text.Sf("RecentBlockhash: %s", mx.RecentBlockhash)) @@ -86,28 +90,36 @@ func (mx *Message) MarshalBinary() ([]byte, error) { mx.Header.NumReadonlyUnsignedAccounts, } - bin.EncodeLength(&buf, len(mx.AccountKeys)) + bin.EncodeCompactU16Length(&buf, len(mx.AccountKeys)) for _, key := range mx.AccountKeys { buf = append(buf, key[:]...) } buf = append(buf, mx.RecentBlockhash[:]...) - bin.EncodeLength(&buf, len(mx.Instructions)) + bin.EncodeCompactU16Length(&buf, len(mx.Instructions)) for _, instruction := range mx.Instructions { buf = append(buf, byte(instruction.ProgramIDIndex)) - bin.EncodeLength(&buf, len(instruction.Accounts)) + bin.EncodeCompactU16Length(&buf, len(instruction.Accounts)) for _, accountIdx := range instruction.Accounts { buf = append(buf, byte(accountIdx)) } - bin.EncodeLength(&buf, len(instruction.Data)) + bin.EncodeCompactU16Length(&buf, len(instruction.Data)) buf = append(buf, instruction.Data...) } return buf, nil } -func (mx *Message) UnmarshalBinary(decoder *bin.Decoder) (err error) { +func (mx *Message) MarshalWithEncoder(encoder *bin.Encoder) error { + out, err := mx.MarshalBinary() + if err != nil { + return err + } + return encoder.WriteBytes(out, false) +} + +func (mx *Message) UnmarshalWithDecoder(decoder *bin.Decoder) (err error) { { mx.Header.NumRequiredSignatures, err = decoder.ReadUint8() if err != nil { @@ -123,7 +135,7 @@ func (mx *Message) UnmarshalBinary(decoder *bin.Decoder) (err error) { } } { - numAccountKeys, err := bin.DecodeLengthFromByteReader(decoder) + numAccountKeys, err := bin.DecodeCompactU16LengthFromByteReader(decoder) if err != nil { return err } @@ -147,7 +159,7 @@ func (mx *Message) UnmarshalBinary(decoder *bin.Decoder) (err error) { mx.RecentBlockhash = recentBlockhash } { - numInstructions, err := bin.DecodeLengthFromByteReader(decoder) + numInstructions, err := bin.DecodeCompactU16LengthFromByteReader(decoder) if err != nil { return err } @@ -160,7 +172,7 @@ func (mx *Message) UnmarshalBinary(decoder *bin.Decoder) (err error) { compInst.ProgramIDIndex = uint16(programIDIndex) { - numAccounts, err := bin.DecodeLengthFromByteReader(decoder) + numAccounts, err := bin.DecodeCompactU16LengthFromByteReader(decoder) if err != nil { return err } @@ -174,7 +186,7 @@ func (mx *Message) UnmarshalBinary(decoder *bin.Decoder) (err error) { } } { - dataLen, err := bin.DecodeLengthFromByteReader(decoder) + dataLen, err := bin.DecodeCompactU16LengthFromByteReader(decoder) if err != nil { return err } @@ -291,17 +303,17 @@ func (ci *CompiledInstruction) ResolveInstructionAccounts(message *Message) (out return } -func TransactionFromData(in []byte) (*Transaction, error) { +func TransactionFromDecoder(decoder *bin.Decoder) (*Transaction, error) { var out *Transaction - decoder := bin.NewDecoder(in) err := decoder.Decode(&out) if err != nil { return nil, err } return out, nil } -func MustTransactionFromData(in []byte) *Transaction { - out, err := TransactionFromData(in) + +func MustTransactionFromData(decoder *bin.Decoder) *Transaction { + out, err := TransactionFromDecoder(decoder) if err != nil { panic(err) } diff --git a/types_test.go b/types_test.go index cd9428a7..f51f4a6d 100644 --- a/types_test.go +++ b/types_test.go @@ -34,7 +34,7 @@ func TestCompiledInstructions(t *testing.T) { Data: Base58([]byte{1, 2, 3, 4, 5}), } buf := &bytes.Buffer{} - encoder := bin.NewEncoder(buf) + encoder := bin.NewBinEncoder(buf) err := encoder.Encode(ci) require.NoError(t, err) assert.Equal(t, []byte{0x5, 0x0, 0x3, 0x5, 0x2, 0x0, 0x5, 0x0, 0x8, 0x0, 0x1, 0x2, 0x3, 0x4, 0x5}, buf.Bytes()) From 64b6fb4d4ff473b37a7b336f608d42aee1b9cc76 Mon Sep 17 00:00:00 2001 From: Slavomir Date: Sat, 21 Aug 2021 18:14:47 +0200 Subject: [PATCH 24/34] Bump github.com/gagliardetto/binary --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 638bbbc2..af3a8acd 100644 --- a/go.mod +++ b/go.mod @@ -50,4 +50,4 @@ require ( gopkg.in/yaml.v2 v2.4.0 // indirect ) -replace github.com/dfuse-io/binary => github.com/gagliardetto/binary v0.2.0 +replace github.com/dfuse-io/binary => github.com/gagliardetto/binary v0.3.0 diff --git a/go.sum b/go.sum index 2d5c64a3..2a8e8280 100644 --- a/go.sum +++ b/go.sum @@ -87,8 +87,8 @@ github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/gagliardetto/binary v0.2.0 h1:OPQjHRDg6pZVtj59k4BlhYJMgYqkL6V12bnnnY1zInk= -github.com/gagliardetto/binary v0.2.0/go.mod h1:GDFX6qH3BQZPWTeYaA4ZW98T94zs2skRoG3oMz/0jw0= +github.com/gagliardetto/binary v0.3.0 h1:SN0+zufFFc5CFYl5T0/wRuUOjx2YkhqKbx82WuSmSL4= +github.com/gagliardetto/binary v0.3.0/go.mod h1:GDFX6qH3BQZPWTeYaA4ZW98T94zs2skRoG3oMz/0jw0= github.com/gagliardetto/treeout v0.1.2 h1:WXO7LDJTwINO37OQfNlf7s095Z1bAiwN2ACaZQic33Q= github.com/gagliardetto/treeout v0.1.2/go.mod h1:loUefvXTrlRG5rYmJmExNryyBRh8f89VZhmMOyCyqok= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= From bacc6bc74b06f4b3174c8b6941c9b7020e44f00c Mon Sep 17 00:00:00 2001 From: Slavomir Date: Sun, 22 Aug 2021 16:31:26 +0200 Subject: [PATCH 25/34] Fix text formatters --- text/format/format.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/text/format/format.go b/text/format/format.go index 193a3a54..5b9b37e7 100644 --- a/text/format/format.go +++ b/text/format/format.go @@ -1,21 +1,22 @@ package format import ( + "github.com/davecgh/go-spew/spew" "github.com/gagliardetto/solana-go" "github.com/gagliardetto/solana-go/text" . "github.com/gagliardetto/solana-go/text" ) func Program(name string, programID solana.PublicKey) string { - return IndigoBG("Program") + ": " + Bold("System") + " " + text.ColorizeBG(programID.String()) + return IndigoBG("Program") + ": " + Bold(name) + " " + text.ColorizeBG(programID.String()) } func Instruction(name string) string { - return Purple(Bold("Instruction")) + ": " + Bold("Transfer") + return Purple(Bold("Instruction")) + ": " + Bold(name) } func Param(name string, value interface{}) string { - return Sf(CC(Shakespeare(name), ": %s"), Lime(S(value))) + return Sf(CC(Shakespeare(name), ": %s"), Lime(spew.Sdump(value))) } func Account(name string, pubKey solana.PublicKey) string { From e9ce078fed1985f72f57bfe82fefd60f378188e0 Mon Sep 17 00:00:00 2001 From: Slavomir Date: Sun, 22 Aug 2021 16:31:34 +0200 Subject: [PATCH 26/34] Add LocalNet_RPC --- rpc/endpoints.go | 1 + 1 file changed, 1 insertion(+) diff --git a/rpc/endpoints.go b/rpc/endpoints.go index 3cae2c1b..d60116ea 100644 --- a/rpc/endpoints.go +++ b/rpc/endpoints.go @@ -19,6 +19,7 @@ const ( TestNet_RPC = protocolHTTPS + hostTestNet MainNetBeta_RPC = protocolHTTPS + hostMainNetBeta MainNetBetaSerum_RPC = protocolHTTPS + hostMainNetBetaSerum + LocalNet_RPC = "http://127.0.0.1:8899" ) const ( From 84deab07ec545aab395a3320c46123e3288d1fa9 Mon Sep 17 00:00:00 2001 From: Slavomir Date: Sun, 22 Aug 2021 16:34:26 +0200 Subject: [PATCH 27/34] Text formatters: trim space on spew.Sdump output --- text/format/format.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/text/format/format.go b/text/format/format.go index 5b9b37e7..0be9433e 100644 --- a/text/format/format.go +++ b/text/format/format.go @@ -1,6 +1,8 @@ package format import ( + "strings" + "github.com/davecgh/go-spew/spew" "github.com/gagliardetto/solana-go" "github.com/gagliardetto/solana-go/text" @@ -16,7 +18,7 @@ func Instruction(name string) string { } func Param(name string, value interface{}) string { - return Sf(CC(Shakespeare(name), ": %s"), Lime(spew.Sdump(value))) + return Sf(CC(Shakespeare(name), ": %s"), Lime(strings.TrimSpace(spew.Sdump(value)))) } func Account(name string, pubKey solana.PublicKey) string { From f642f24524cd092b441783bef6948b286e5f63da Mon Sep 17 00:00:00 2001 From: Slavomir Date: Sun, 22 Aug 2021 18:45:19 +0200 Subject: [PATCH 28/34] Add meta formatter --- text/format/format.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/text/format/format.go b/text/format/format.go index 0be9433e..d92ace1e 100644 --- a/text/format/format.go +++ b/text/format/format.go @@ -24,3 +24,19 @@ func Param(name string, value interface{}) string { func Account(name string, pubKey solana.PublicKey) string { return Shakespeare(name) + ": " + text.ColorizeBG(pubKey.String()) } + +func Meta(name string, meta *solana.AccountMeta) string { + out := Shakespeare(name) + ": " + text.ColorizeBG(meta.PublicKey.String()) + out += " [" + if meta.IsWritable { + out += "WRITE" + } + if meta.IsWritable { + if meta.IsWritable { + out += ", " + } + out += "SIGN" + } + out += "] " + return out +} From 3f027cc64e29bd9490cc8d22b7f560006e666a91 Mon Sep 17 00:00:00 2001 From: Slavomir Date: Sun, 22 Aug 2021 18:45:55 +0200 Subject: [PATCH 29/34] Set fee payer account when it's not among the accounts of the transaction. --- transaction.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/transaction.go b/transaction.go index 011dd45c..192611ee 100644 --- a/transaction.go +++ b/transaction.go @@ -201,6 +201,16 @@ func NewTransaction(instructions []Instruction, recentBlockHash Hash, opts ...Tr itr++ } + if feePayerIndex < 0 { + // fee payer is not part of accounts we want to add it + feePayerAccount := &AccountMeta{ + PublicKey: feePayer, + IsSigner: true, + IsWritable: true, + } + finalAccounts[0] = feePayerAccount + } + message := Message{ RecentBlockhash: recentBlockHash, } From ef67b408d2862043f48452452cb23b1b7a435bce Mon Sep 17 00:00:00 2001 From: Slavomir Date: Wed, 25 Aug 2021 22:38:28 +0200 Subject: [PATCH 30/34] Add Bytes() method on PublicKey --- keys.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/keys.go b/keys.go index 871bb28b..460f671e 100644 --- a/keys.go +++ b/keys.go @@ -157,6 +157,10 @@ func (p PublicKey) ToPointer() *PublicKey { return &p } +func (p PublicKey) Bytes() []byte { + return []byte(p[:]) +} + var zeroPublicKey = PublicKey{} // IsZero returns whether the public key is zero. From f4fb6389da81b38a67d83d1400c6cff5896aa9e1 Mon Sep 17 00:00:00 2001 From: Slavomir Date: Wed, 25 Aug 2021 22:39:08 +0200 Subject: [PATCH 31/34] Use int64 for SeedSize param in the CreateAccountWithSeed instruction --- programs/system/CreateAccountWithSeed.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/programs/system/CreateAccountWithSeed.go b/programs/system/CreateAccountWithSeed.go index 870ddade..2955d943 100644 --- a/programs/system/CreateAccountWithSeed.go +++ b/programs/system/CreateAccountWithSeed.go @@ -49,7 +49,7 @@ type CreateAccountWithSeed struct { // Base public key. Base solana.PublicKey - SeedSize int `bin:"sizeof=Seed"` + SeedSize int64 `bin:"sizeof=Seed"` // String of ASCII chars, no longer than solana.MAX_SEED_LEN Seed string From 3853032c9cfdeeb8397904f9ad02addfac7bbae8 Mon Sep 17 00:00:00 2001 From: Slavomir Date: Tue, 31 Aug 2021 16:27:21 +0200 Subject: [PATCH 32/34] Upgrade bin package; use generated system program client. --- cmd/slnc/cmd/token_registry_register.go | 18 +- go.mod | 7 +- go.sum | 8 +- programs/serum/instruction.go | 2 +- programs/serum/instruction_test.go | 7 +- programs/serum/types.go | 2 +- programs/serum/types_test.go | 13 +- programs/system/AdvanceNonceAccount.go | 152 ++++++--- programs/system/AdvanceNonceAccount_test.go | 31 ++ programs/system/Allocate.go | 146 ++++++-- programs/system/AllocateWithSeed.go | 257 ++++++++++++-- programs/system/AllocateWithSeed_test.go | 31 ++ programs/system/Allocate_test.go | 31 ++ programs/system/Assign.go | 142 ++++++-- programs/system/AssignWithSeed.go | 225 ++++++++++-- programs/system/AssignWithSeed_test.go | 31 ++ programs/system/Assign_test.go | 31 ++ programs/system/AuthorizeNonceAccount.go | 166 +++++++-- programs/system/AuthorizeNonceAccount_test.go | 31 ++ programs/system/CreateAccount.go | 247 +++++++++----- programs/system/CreateAccountWithSeed.go | 323 +++++++++++++++--- programs/system/CreateAccountWithSeed_test.go | 31 ++ programs/system/CreateAccount_test.go | 31 ++ programs/system/InitializeNonceAccount.go | 191 +++++++++-- .../system/InitializeNonceAccount_test.go | 31 ++ programs/system/Transfer.go | 171 +++++++--- programs/system/TransferWithSeed.go | 245 +++++++++++-- programs/system/TransferWithSeed_test.go | 31 ++ programs/system/Transfer_test.go | 31 ++ programs/system/WithdrawNonceAccount.go | 223 ++++++++++-- programs/system/WithdrawNonceAccount_test.go | 31 ++ programs/system/accounts.go | 1 + programs/system/instructions.go | 217 ++++++------ programs/system/instructions_test.go | 23 -- programs/system/testing_utils.go | 18 + programs/system/types.go | 1 + programs/token/instructions.go | 2 +- programs/tokenregistry/instruction.go | 6 +- 38 files changed, 2558 insertions(+), 627 deletions(-) create mode 100644 programs/system/AdvanceNonceAccount_test.go create mode 100644 programs/system/AllocateWithSeed_test.go create mode 100644 programs/system/Allocate_test.go create mode 100644 programs/system/AssignWithSeed_test.go create mode 100644 programs/system/Assign_test.go create mode 100644 programs/system/AuthorizeNonceAccount_test.go create mode 100644 programs/system/CreateAccountWithSeed_test.go create mode 100644 programs/system/CreateAccount_test.go create mode 100644 programs/system/InitializeNonceAccount_test.go create mode 100644 programs/system/TransferWithSeed_test.go create mode 100644 programs/system/Transfer_test.go create mode 100644 programs/system/WithdrawNonceAccount_test.go create mode 100644 programs/system/accounts.go delete mode 100644 programs/system/instructions_test.go create mode 100644 programs/system/testing_utils.go create mode 100644 programs/system/types.go diff --git a/cmd/slnc/cmd/token_registry_register.go b/cmd/slnc/cmd/token_registry_register.go index 1d02a961..7c1bd984 100644 --- a/cmd/slnc/cmd/token_registry_register.go +++ b/cmd/slnc/cmd/token_registry_register.go @@ -103,10 +103,24 @@ var tokenRegistryRegisterCmd = &cobra.Command{ tokenRegistryProgramID := tokenregistry.ProgramID() - createAccountInstruction := system.NewCreateAccountInstruction(uint64(lamport), tokenregistry.TOKEN_META_SIZE, tokenRegistryProgramID, registrarPubKey, tokenMetaAccount.PublicKey()) + createAccountInstruction := system.NewCreateAccountInstruction( + lamport, + tokenregistry.TOKEN_META_SIZE, + tokenRegistryProgramID, + registrarPubKey, + tokenMetaAccount.PublicKey(), + ). + Build() registerTokenInstruction := tokenregistry.NewRegisterTokenInstruction(logo, name, symbol, website, tokenMetaAccount.PublicKey(), registrarPubKey, tokenAddress) - trx, err := solana.NewTransaction([]solana.Instruction{createAccountInstruction, registerTokenInstruction}, blockHashResult.Value.Blockhash, solana.TransactionPayer(registrarPubKey)) + trx, err := solana.NewTransaction( + []solana.Instruction{ + createAccountInstruction, + registerTokenInstruction, + }, + blockHashResult.Value.Blockhash, + solana.TransactionPayer(registrarPubKey), + ) if err != nil { return fmt.Errorf("unable to craft transaction: %w", err) } diff --git a/go.mod b/go.mod index af3a8acd..6112fd9b 100644 --- a/go.mod +++ b/go.mod @@ -19,11 +19,11 @@ require ( github.com/fatih/color v1.7.0 github.com/gagliardetto/treeout v0.1.2 github.com/google/go-cmp v0.5.1 + github.com/google/gofuzz v1.0.0 github.com/gorilla/rpc v1.2.0 github.com/gorilla/websocket v1.4.2 github.com/json-iterator/go v1.1.11 github.com/klauspost/compress v1.13.1 - github.com/kr/pretty v0.2.1 // indirect github.com/logrusorgru/aurora v2.0.3+incompatible github.com/magiconair/properties v1.8.1 github.com/mr-tron/base58 v1.2.0 @@ -47,7 +47,8 @@ require ( golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf // indirect golang.org/x/tools v0.0.0-20200601175630-2caf76543d99 // indirect google.golang.org/api v0.29.0 - gopkg.in/yaml.v2 v2.4.0 // indirect ) -replace github.com/dfuse-io/binary => github.com/gagliardetto/binary v0.3.0 +replace github.com/dfuse-io/binary => github.com/gagliardetto/binary v0.4.0 + +replace github.com/google/gofuzz => github.com/gagliardetto/gofuzz v1.2.1 diff --git a/go.sum b/go.sum index 2a8e8280..88be28f4 100644 --- a/go.sum +++ b/go.sum @@ -87,8 +87,10 @@ github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/gagliardetto/binary v0.3.0 h1:SN0+zufFFc5CFYl5T0/wRuUOjx2YkhqKbx82WuSmSL4= -github.com/gagliardetto/binary v0.3.0/go.mod h1:GDFX6qH3BQZPWTeYaA4ZW98T94zs2skRoG3oMz/0jw0= +github.com/gagliardetto/binary v0.4.0 h1:dxLndYArHtdZYbLYwnpLY86mlAa9gWgxG2zSDPFINjM= +github.com/gagliardetto/binary v0.4.0/go.mod h1:55fxN6CKhVnsBhSr3Hmyn7i2igseIzN9/NC+gHvv42k= +github.com/gagliardetto/gofuzz v1.2.1 h1:fHBiDgCYYb8kBRqyI+bhU59/IKATHArFUAY3iVUrdPA= +github.com/gagliardetto/gofuzz v1.2.1/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/gagliardetto/treeout v0.1.2 h1:WXO7LDJTwINO37OQfNlf7s095Z1bAiwN2ACaZQic33Q= github.com/gagliardetto/treeout v0.1.2/go.mod h1:loUefvXTrlRG5rYmJmExNryyBRh8f89VZhmMOyCyqok= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= @@ -134,7 +136,6 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.1 h1:JFrFEBb2xKufg6XkJsJr+WbKb4FQlURi5RUcBveYu9k= github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= @@ -494,6 +495,7 @@ golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= diff --git a/programs/serum/instruction.go b/programs/serum/instruction.go index 17bcc2a9..6cce7f65 100644 --- a/programs/serum/instruction.go +++ b/programs/serum/instruction.go @@ -82,7 +82,7 @@ func (i *Instruction) MarshalWithEncoder(encoder *bin.Encoder) error { return fmt.Errorf("unable to write instruction version: %w", err) } - err = encoder.WriteUint32(i.TypeID, binary.LittleEndian) + err = encoder.WriteUint32(i.TypeID.Uint32(), binary.LittleEndian) if err != nil { return fmt.Errorf("unable to write variant type: %w", err) } diff --git a/programs/serum/instruction_test.go b/programs/serum/instruction_test.go index e57b41a6..249d25e7 100644 --- a/programs/serum/instruction_test.go +++ b/programs/serum/instruction_test.go @@ -1,6 +1,7 @@ package serum import ( + "encoding/binary" "encoding/hex" "testing" @@ -20,7 +21,7 @@ func TestDecodeInstruction(t *testing.T) { hexData: "000900000001000000b80600000000000010eb09000000000000000000168106e091da511601000000", expectInstruction: &Instruction{ BaseVariant: bin.BaseVariant{ - TypeID: 9, + TypeID: bin.TypeIDFromUint32(9, binary.LittleEndian), Impl: &InstructionNewOrderV2{ Side: SideAsk, LimitPrice: 1720, @@ -38,7 +39,7 @@ func TestDecodeInstruction(t *testing.T) { hexData: "0002000000ffff", expectInstruction: &Instruction{ BaseVariant: bin.BaseVariant{ - TypeID: 2, + TypeID: bin.TypeIDFromUint32(2, binary.LittleEndian), Impl: &InstructionMatchOrder{ Limit: 65535, }, @@ -51,7 +52,7 @@ func TestDecodeInstruction(t *testing.T) { hexData: "0005000000", expectInstruction: &Instruction{ BaseVariant: bin.BaseVariant{ - TypeID: 5, + TypeID: bin.TypeIDFromUint32(5, binary.LittleEndian), Impl: &InstructionSettleFunds{}, }, Version: 0, diff --git a/programs/serum/types.go b/programs/serum/types.go index b6e19185..caf9bd00 100644 --- a/programs/serum/types.go +++ b/programs/serum/types.go @@ -167,7 +167,7 @@ func (s *Slab) UnmarshalWithDecoder(decoder *bin.Decoder) error { } func (s *Slab) MarshalWithEncoder(encoder *bin.Encoder) error { - err := encoder.WriteUint32(s.TypeID, binary.LittleEndian) + err := encoder.WriteUint32(s.TypeID.Uint32(), binary.LittleEndian) if err != nil { return err } diff --git a/programs/serum/types_test.go b/programs/serum/types_test.go index 2b35feef..82042a69 100644 --- a/programs/serum/types_test.go +++ b/programs/serum/types_test.go @@ -2,6 +2,7 @@ package serum import ( "encoding/base64" + "encoding/binary" "encoding/hex" "fmt" "io/ioutil" @@ -89,7 +90,7 @@ func TestDecoder_Orderbook(t *testing.T) { assert.Equal(t, 101, len(ob.Nodes)) assert.Equal(t, &Slab{ BaseVariant: bin.BaseVariant{ - TypeID: 1, + TypeID: bin.TypeIDFromUint32(1, binary.LittleEndian), Impl: &SlabInnerNode{ PrefixLen: 57, Key: bin.Uint128{ @@ -108,7 +109,7 @@ func TestDecoder_Orderbook(t *testing.T) { }, ob.Nodes[0]) assert.Equal(t, &Slab{ BaseVariant: bin.BaseVariant{ - TypeID: 3, + TypeID: bin.TypeIDFromUint32(3, binary.LittleEndian), Impl: &SlabFreeNode{ Next: 2, Padding: [64]byte{ @@ -125,7 +126,7 @@ func TestDecoder_Orderbook(t *testing.T) { }, ob.Nodes[1]) assert.Equal(t, &Slab{ BaseVariant: bin.BaseVariant{ - TypeID: 2, + TypeID: bin.TypeIDFromUint32(2, binary.LittleEndian), Impl: &SlabLeafNode{ OwnerSlot: 1, FeeTier: 5, @@ -154,7 +155,7 @@ func TestDecoder_Slabs(t *testing.T) { slabData: "0100000035000000010babffffffffff4105000000000000400000003f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000", expectSlab: &Slab{ BaseVariant: bin.BaseVariant{ - TypeID: 1, + TypeID: bin.TypeIDFromUint32(1, binary.LittleEndian), Impl: &SlabInnerNode{ PrefixLen: 53, Key: bin.Uint128{ @@ -175,7 +176,7 @@ func TestDecoder_Slabs(t *testing.T) { slabData: "0200000014060000b2cea5ffffffffff23070000000000005ae01b52d00a090c6dc6fce8e37a225815cff2223a99c6dfdad5aae56d3db670e62c000000000000140b0fadcf8fcebf", expectSlab: &Slab{ BaseVariant: bin.BaseVariant{ - TypeID: 2, + TypeID: bin.TypeIDFromUint32(2, binary.LittleEndian), Impl: &SlabLeafNode{ OwnerSlot: 20, FeeTier: 6, @@ -196,7 +197,7 @@ func TestDecoder_Slabs(t *testing.T) { slabData: "030000003400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", expectSlab: &Slab{ BaseVariant: bin.BaseVariant{ - TypeID: 3, + TypeID: bin.TypeIDFromUint32(3, binary.LittleEndian), Impl: &SlabFreeNode{ Next: 52, }, diff --git a/programs/system/AdvanceNonceAccount.go b/programs/system/AdvanceNonceAccount.go index 26e7443b..aa741939 100644 --- a/programs/system/AdvanceNonceAccount.go +++ b/programs/system/AdvanceNonceAccount.go @@ -1,81 +1,131 @@ package system import ( + "encoding/binary" "fmt" - - bin "github.com/dfuse-io/binary" - solana "github.com/gagliardetto/solana-go" + ag_binary "github.com/dfuse-io/binary" + ag_solanago "github.com/gagliardetto/solana-go" + ag_format "github.com/gagliardetto/solana-go/text/format" + ag_treeout "github.com/gagliardetto/treeout" ) -func NewAdvanceNonceAccountInstruction( - nonceAccount solana.PublicKey, - nonceAuthority solana.PublicKey, -) *Instruction { - return NewAdvanceNonceAccountBuilder(). - WithNonceAccount(nonceAccount). - WithNonceAuthority(nonceAuthority). - Build() -} - -// Consumes a stored nonce, replacing it with a successor. +// Consumes a stored nonce, replacing it with a successor type AdvanceNonceAccount struct { - // [0] = [WRITE] Nonce account. - // [1] = [] RecentBlockhashes sysvar. - // [2] = [SIGNER] Nonce authority. - solana.AccountMetaSlice `bin:"-"` + + // [0] = [WRITE] NonceAccount + // ··········· Nonce account + // + // [1] = [] $(SysVarRecentBlockHashesPubkey) + // ··········· RecentBlockhashes sysvar + // + // [2] = [SIGNER] NonceAuthorityAccount + // ··········· Nonce authority + ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"` } -// NewAdvanceNonceAccountBuilder initializes a new AdvanceNonceAccount builder. -func NewAdvanceNonceAccountBuilder() *AdvanceNonceAccount { - nb := &AdvanceNonceAccount{ - AccountMetaSlice: make(solana.AccountMetaSlice, 3), +// NewAdvanceNonceAccountInstructionBuilder creates a new `AdvanceNonceAccount` instruction builder. +func NewAdvanceNonceAccountInstructionBuilder() *AdvanceNonceAccount { + nd := &AdvanceNonceAccount{ + AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 3), } - nb.AccountMetaSlice[1] = solana.Meta(solana.SysVarRecentBlockHashesPubkey) - return nb + nd.AccountMetaSlice[1] = ag_solanago.Meta(ag_solanago.SysVarRecentBlockHashesPubkey) + return nd } -func (ins *AdvanceNonceAccount) WithNonceAccount(nonceAccount solana.PublicKey) *AdvanceNonceAccount { - ins.AccountMetaSlice[0] = solana.Meta(nonceAccount).WRITE() - return ins +// Nonce account +func (inst *AdvanceNonceAccount) SetNonceAccount(nonceAccount ag_solanago.PublicKey) *AdvanceNonceAccount { + inst.AccountMetaSlice[0] = ag_solanago.Meta(nonceAccount).WRITE() + return inst } -func (ins *AdvanceNonceAccount) GetNonceAccount() *solana.PublicKey { - ac := ins.AccountMetaSlice[0] - if ac == nil { - return nil - } - return &ac.PublicKey +func (inst *AdvanceNonceAccount) GetNonceAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice[0] +} + +// RecentBlockhashes sysvar +func (inst *AdvanceNonceAccount) SetSysVarRecentBlockHashesPubkeyAccount(SysVarRecentBlockHashesPubkey ag_solanago.PublicKey) *AdvanceNonceAccount { + inst.AccountMetaSlice[1] = ag_solanago.Meta(SysVarRecentBlockHashesPubkey) + return inst +} + +func (inst *AdvanceNonceAccount) GetSysVarRecentBlockHashesPubkeyAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice[1] +} + +// Nonce authority +func (inst *AdvanceNonceAccount) SetNonceAuthorityAccount(nonceAuthorityAccount ag_solanago.PublicKey) *AdvanceNonceAccount { + inst.AccountMetaSlice[2] = ag_solanago.Meta(nonceAuthorityAccount).SIGNER() + return inst } -func (ins *AdvanceNonceAccount) WithNonceAuthority(nonceAuthority solana.PublicKey) *AdvanceNonceAccount { - ins.AccountMetaSlice[2] = solana.Meta(nonceAuthority).SIGNER() - return ins +func (inst *AdvanceNonceAccount) GetNonceAuthorityAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice[2] } -func (ins *AdvanceNonceAccount) GetNonceAuthority() *solana.PublicKey { - ac := ins.AccountMetaSlice[2] - if ac == nil { - return nil +func (inst AdvanceNonceAccount) Build() *Instruction { + return &Instruction{BaseVariant: ag_binary.BaseVariant{ + Impl: inst, + TypeID: ag_binary.TypeIDFromUint32(Instruction_AdvanceNonceAccount, binary.LittleEndian), + }} +} + +// ValidateAndBuild validates the instruction parameters and accounts; +// if there is a validation error, it returns the error. +// Otherwise, it builds and returns the instruction. +func (inst AdvanceNonceAccount) ValidateAndBuild() (*Instruction, error) { + if err := inst.Validate(); err != nil { + return nil, err } - return &ac.PublicKey + return inst.Build(), nil } -func (ins *AdvanceNonceAccount) Validate() error { - for accIndex, acc := range ins.AccountMetaSlice { +func (inst *AdvanceNonceAccount) Validate() error { + // Check whether all accounts are set: + for accIndex, acc := range inst.AccountMetaSlice { if acc == nil { - return fmt.Errorf("ins.AccountMetaSlice[%v] is nil", accIndex) + return fmt.Errorf("ins.AccountMetaSlice[%v] is not set", accIndex) } } return nil } -func (ins *AdvanceNonceAccount) Build() *Instruction { - return &Instruction{ - BaseVariant: bin.BaseVariant{ +func (inst *AdvanceNonceAccount) EncodeToTree(parent ag_treeout.Branches) { + parent.Child(ag_format.Program(ProgramName, ProgramID)). + // + ParentFunc(func(programBranch ag_treeout.Branches) { + programBranch.Child(ag_format.Instruction("AdvanceNonceAccount")). + // + ParentFunc(func(instructionBranch ag_treeout.Branches) { - TypeID: Instruction_AdvanceNonceAccount, + // Parameters of the instruction: + instructionBranch.Child("Params").ParentFunc(func(paramsBranch ag_treeout.Branches) {}) - Impl: ins, - }, - } + // Accounts of the instruction: + instructionBranch.Child("Accounts").ParentFunc(func(accountsBranch ag_treeout.Branches) { + accountsBranch.Child(ag_format.Meta("NonceAccount", inst.AccountMetaSlice[0])) + accountsBranch.Child(ag_format.Meta("$(SysVarRecentBlockHashesPubkey)", inst.AccountMetaSlice[1])) + accountsBranch.Child(ag_format.Meta("NonceAuthorityAccount", inst.AccountMetaSlice[2])) + }) + }) + }) +} + +func (inst AdvanceNonceAccount) MarshalWithEncoder(encoder *ag_binary.Encoder) error { + return nil +} + +func (inst *AdvanceNonceAccount) UnmarshalWithDecoder(decoder *ag_binary.Decoder) error { + return nil +} + +// NewAdvanceNonceAccountInstruction declares a new AdvanceNonceAccount instruction with the provided parameters and accounts. +func NewAdvanceNonceAccountInstruction( + // Accounts: + nonceAccount ag_solanago.PublicKey, + SysVarRecentBlockHashesPubkey ag_solanago.PublicKey, + nonceAuthorityAccount ag_solanago.PublicKey) *AdvanceNonceAccount { + return NewAdvanceNonceAccountInstructionBuilder(). + SetNonceAccount(nonceAccount). + SetSysVarRecentBlockHashesPubkeyAccount(SysVarRecentBlockHashesPubkey). + SetNonceAuthorityAccount(nonceAuthorityAccount) } diff --git a/programs/system/AdvanceNonceAccount_test.go b/programs/system/AdvanceNonceAccount_test.go new file mode 100644 index 00000000..516ffd29 --- /dev/null +++ b/programs/system/AdvanceNonceAccount_test.go @@ -0,0 +1,31 @@ +package system + +import ( + "bytes" + ag_gofuzz "github.com/google/gofuzz" + ag_require "github.com/stretchr/testify/require" + "strconv" + "testing" +) + +func TestEncodeDecode_AdvanceNonceAccount(t *testing.T) { + fu := ag_gofuzz.New().NilChance(0) + for i := 0; i < 1; i++ { + t.Run("AdvanceNonceAccount"+strconv.Itoa(i), func(t *testing.T) { + { + params := new(AdvanceNonceAccount) + fu.Fuzz(params) + params.AccountMetaSlice = nil + buf := new(bytes.Buffer) + err := encodeT(*params, buf) + ag_require.NoError(t, err) + // + got := new(AdvanceNonceAccount) + err = decodeT(got, buf.Bytes()) + got.AccountMetaSlice = nil + ag_require.NoError(t, err) + ag_require.Equal(t, params, got) + } + }) + } +} diff --git a/programs/system/Allocate.go b/programs/system/Allocate.go index ce8508d4..1b97f82f 100644 --- a/programs/system/Allocate.go +++ b/programs/system/Allocate.go @@ -1,33 +1,133 @@ package system import ( - bin "github.com/dfuse-io/binary" - solana "github.com/gagliardetto/solana-go" + "encoding/binary" + "errors" + "fmt" + ag_binary "github.com/dfuse-io/binary" + ag_solanago "github.com/gagliardetto/solana-go" + ag_format "github.com/gagliardetto/solana-go/text/format" + ag_treeout "github.com/gagliardetto/treeout" ) -func NewAllocateInstruction( - space uint64, - accountPubKey solana.PublicKey, -) *Instruction { - return &Instruction{ - BaseVariant: bin.BaseVariant{ - - TypeID: Instruction_Allocate, - - Impl: &Allocate{ - Space: bin.Uint64(space), - AccountMetaSlice: []*solana.AccountMeta{ - solana.Meta(accountPubKey).WRITE().SIGNER(), - }, - }, - }, +// Allocate space in a (possibly new) account without funding +type Allocate struct { + // Number of bytes of memory to allocate + Space *uint64 + + // [0] = [WRITE, SIGNER] NewAccount + // ··········· New account + ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"` +} + +// NewAllocateInstructionBuilder creates a new `Allocate` instruction builder. +func NewAllocateInstructionBuilder() *Allocate { + nd := &Allocate{ + AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 1), } + return nd } -type Allocate struct { - // Number of bytes of memory to allocate. - Space bin.Uint64 +// Number of bytes of memory to allocate +func (inst *Allocate) SetSpace(space uint64) *Allocate { + inst.Space = &space + return inst +} + +// New account +func (inst *Allocate) SetNewAccount(newAccount ag_solanago.PublicKey) *Allocate { + inst.AccountMetaSlice[0] = ag_solanago.Meta(newAccount).WRITE().SIGNER() + return inst +} + +func (inst *Allocate) GetNewAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice[0] +} + +func (inst Allocate) Build() *Instruction { + return &Instruction{BaseVariant: ag_binary.BaseVariant{ + Impl: inst, + TypeID: ag_binary.TypeIDFromUint32(Instruction_Allocate, binary.LittleEndian), + }} +} + +// ValidateAndBuild validates the instruction parameters and accounts; +// if there is a validation error, it returns the error. +// Otherwise, it builds and returns the instruction. +func (inst Allocate) ValidateAndBuild() (*Instruction, error) { + if err := inst.Validate(); err != nil { + return nil, err + } + return inst.Build(), nil +} - // [0] = [WRITE, SIGNER] New account. - solana.AccountMetaSlice `bin:"-"` +func (inst *Allocate) Validate() error { + // Check whether all (required) parameters are set: + { + if inst.Space == nil { + return errors.New("Space 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 *Allocate) EncodeToTree(parent ag_treeout.Branches) { + parent.Child(ag_format.Program(ProgramName, ProgramID)). + // + ParentFunc(func(programBranch ag_treeout.Branches) { + programBranch.Child(ag_format.Instruction("Allocate")). + // + ParentFunc(func(instructionBranch ag_treeout.Branches) { + + // Parameters of the instruction: + instructionBranch.Child("Params").ParentFunc(func(paramsBranch ag_treeout.Branches) { + paramsBranch.Child(ag_format.Param("Space", *inst.Space)) + }) + + // Accounts of the instruction: + instructionBranch.Child("Accounts").ParentFunc(func(accountsBranch ag_treeout.Branches) { + accountsBranch.Child(ag_format.Meta("NewAccount", inst.AccountMetaSlice[0])) + }) + }) + }) +} + +func (inst Allocate) MarshalWithEncoder(encoder *ag_binary.Encoder) error { + // Serialize `Space` param: + { + err := encoder.Encode(*inst.Space) + if err != nil { + return err + } + } + return nil +} + +func (inst *Allocate) UnmarshalWithDecoder(decoder *ag_binary.Decoder) error { + // Deserialize `Space` param: + { + err := decoder.Decode(&inst.Space) + if err != nil { + return err + } + } + return nil +} + +// NewAllocateInstruction declares a new Allocate instruction with the provided parameters and accounts. +func NewAllocateInstruction( + // Parameters: + space uint64, + // Accounts: + newAccount ag_solanago.PublicKey) *Allocate { + return NewAllocateInstructionBuilder(). + SetSpace(space). + SetNewAccount(newAccount) } diff --git a/programs/system/AllocateWithSeed.go b/programs/system/AllocateWithSeed.go index ee614bd5..463dc7f9 100644 --- a/programs/system/AllocateWithSeed.go +++ b/programs/system/AllocateWithSeed.go @@ -1,45 +1,236 @@ package system import ( - bin "github.com/dfuse-io/binary" - solana "github.com/gagliardetto/solana-go" + "encoding/binary" + "errors" + "fmt" + ag_binary "github.com/dfuse-io/binary" + ag_solanago "github.com/gagliardetto/solana-go" + ag_format "github.com/gagliardetto/solana-go/text/format" + ag_treeout "github.com/gagliardetto/treeout" ) -func NewAllocateWithSeedInstruction( - accountPubKey solana.PublicKey, - basePubKey solana.PublicKey, - seed string, - space uint64, - owner solana.PublicKey, -) *Instruction { - return &Instruction{ - BaseVariant: bin.BaseVariant{ +// Allocate space for and assign an account at an address derived from a base public key and a seed +type AllocateWithSeed struct { + // Base public key + Base *ag_solanago.PublicKey - TypeID: Instruction_AllocateWithSeed, + // String of ASCII chars, no longer than pubkey::MAX_SEED_LEN + Seed *string - Impl: &AllocateWithSeed{ - Base: basePubKey, - Seed: seed, - Space: bin.Uint64(space), - Owner: owner, + // Number of bytes of memory to allocate + Space *uint64 + + // Owner program account address + Owner *ag_solanago.PublicKey + + // [0] = [WRITE] AllocatedAccount + // ··········· Allocated account + // + // [1] = [SIGNER] BaseAccount + // ··········· Base account + ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"` +} - AccountMetaSlice: []*solana.AccountMeta{ - solana.Meta(accountPubKey).WRITE(), - solana.Meta(basePubKey).SIGNER(), - }, - }, - }, +// NewAllocateWithSeedInstructionBuilder creates a new `AllocateWithSeed` instruction builder. +func NewAllocateWithSeedInstructionBuilder() *AllocateWithSeed { + nd := &AllocateWithSeed{ + AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 2), } + return nd } -type AllocateWithSeed struct { - Base solana.PublicKey - SeedSize int `bin:"sizeof=Seed"` - Seed string - Space bin.Uint64 - Owner solana.PublicKey - - // [0] = [WRITE] Allocated account. - // [1] = [SIGNER] Base account. - solana.AccountMetaSlice `bin:"-"` +// Base public key +func (inst *AllocateWithSeed) SetBase(base ag_solanago.PublicKey) *AllocateWithSeed { + inst.Base = &base + return inst +} + +// String of ASCII chars, no longer than pubkey::MAX_SEED_LEN +func (inst *AllocateWithSeed) SetSeed(seed string) *AllocateWithSeed { + inst.Seed = &seed + return inst +} + +// Number of bytes of memory to allocate +func (inst *AllocateWithSeed) SetSpace(space uint64) *AllocateWithSeed { + inst.Space = &space + return inst +} + +// Owner program account address +func (inst *AllocateWithSeed) SetOwner(owner ag_solanago.PublicKey) *AllocateWithSeed { + inst.Owner = &owner + return inst +} + +// Allocated account +func (inst *AllocateWithSeed) SetAllocatedAccount(allocatedAccount ag_solanago.PublicKey) *AllocateWithSeed { + inst.AccountMetaSlice[0] = ag_solanago.Meta(allocatedAccount).WRITE() + return inst +} + +func (inst *AllocateWithSeed) GetAllocatedAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice[0] +} + +// Base account +func (inst *AllocateWithSeed) SetBaseAccount(baseAccount ag_solanago.PublicKey) *AllocateWithSeed { + inst.AccountMetaSlice[1] = ag_solanago.Meta(baseAccount).SIGNER() + return inst +} + +func (inst *AllocateWithSeed) GetBaseAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice[1] +} + +func (inst AllocateWithSeed) Build() *Instruction { + return &Instruction{BaseVariant: ag_binary.BaseVariant{ + Impl: inst, + TypeID: ag_binary.TypeIDFromUint32(Instruction_AllocateWithSeed, binary.LittleEndian), + }} +} + +// ValidateAndBuild validates the instruction parameters and accounts; +// if there is a validation error, it returns the error. +// Otherwise, it builds and returns the instruction. +func (inst AllocateWithSeed) ValidateAndBuild() (*Instruction, error) { + if err := inst.Validate(); err != nil { + return nil, err + } + return inst.Build(), nil +} + +func (inst *AllocateWithSeed) Validate() error { + // Check whether all (required) parameters are set: + { + if inst.Base == nil { + return errors.New("Base parameter is not set") + } + if inst.Seed == nil { + return errors.New("Seed parameter is not set") + } + if inst.Space == nil { + return errors.New("Space parameter is not set") + } + if inst.Owner == nil { + return errors.New("Owner 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 *AllocateWithSeed) EncodeToTree(parent ag_treeout.Branches) { + parent.Child(ag_format.Program(ProgramName, ProgramID)). + // + ParentFunc(func(programBranch ag_treeout.Branches) { + programBranch.Child(ag_format.Instruction("AllocateWithSeed")). + // + ParentFunc(func(instructionBranch ag_treeout.Branches) { + + // Parameters of the instruction: + instructionBranch.Child("Params").ParentFunc(func(paramsBranch ag_treeout.Branches) { + paramsBranch.Child(ag_format.Param("Base", *inst.Base)) + paramsBranch.Child(ag_format.Param("Seed", *inst.Seed)) + paramsBranch.Child(ag_format.Param("Space", *inst.Space)) + paramsBranch.Child(ag_format.Param("Owner", *inst.Owner)) + }) + + // Accounts of the instruction: + instructionBranch.Child("Accounts").ParentFunc(func(accountsBranch ag_treeout.Branches) { + accountsBranch.Child(ag_format.Meta("AllocatedAccount", inst.AccountMetaSlice[0])) + accountsBranch.Child(ag_format.Meta("BaseAccount", inst.AccountMetaSlice[1])) + }) + }) + }) +} + +func (inst AllocateWithSeed) MarshalWithEncoder(encoder *ag_binary.Encoder) error { + // Serialize `Base` param: + { + err := encoder.Encode(*inst.Base) + if err != nil { + return err + } + } + // Serialize `Seed` param: + { + err := encoder.Encode(*inst.Seed) + if err != nil { + return err + } + } + // Serialize `Space` param: + { + err := encoder.Encode(*inst.Space) + if err != nil { + return err + } + } + // Serialize `Owner` param: + { + err := encoder.Encode(*inst.Owner) + if err != nil { + return err + } + } + return nil +} + +func (inst *AllocateWithSeed) UnmarshalWithDecoder(decoder *ag_binary.Decoder) error { + // Deserialize `Base` param: + { + err := decoder.Decode(&inst.Base) + if err != nil { + return err + } + } + // Deserialize `Seed` param: + { + err := decoder.Decode(&inst.Seed) + if err != nil { + return err + } + } + // Deserialize `Space` param: + { + err := decoder.Decode(&inst.Space) + if err != nil { + return err + } + } + // Deserialize `Owner` param: + { + err := decoder.Decode(&inst.Owner) + if err != nil { + return err + } + } + return nil +} + +// NewAllocateWithSeedInstruction declares a new AllocateWithSeed instruction with the provided parameters and accounts. +func NewAllocateWithSeedInstruction( + // Parameters: + base ag_solanago.PublicKey, + seed string, + space uint64, + owner ag_solanago.PublicKey, + // Accounts: + allocatedAccount ag_solanago.PublicKey, + baseAccount ag_solanago.PublicKey) *AllocateWithSeed { + return NewAllocateWithSeedInstructionBuilder(). + SetBase(base). + SetSeed(seed). + SetSpace(space). + SetOwner(owner). + SetAllocatedAccount(allocatedAccount). + SetBaseAccount(baseAccount) } diff --git a/programs/system/AllocateWithSeed_test.go b/programs/system/AllocateWithSeed_test.go new file mode 100644 index 00000000..86ed2561 --- /dev/null +++ b/programs/system/AllocateWithSeed_test.go @@ -0,0 +1,31 @@ +package system + +import ( + "bytes" + ag_gofuzz "github.com/google/gofuzz" + ag_require "github.com/stretchr/testify/require" + "strconv" + "testing" +) + +func TestEncodeDecode_AllocateWithSeed(t *testing.T) { + fu := ag_gofuzz.New().NilChance(0) + for i := 0; i < 1; i++ { + t.Run("AllocateWithSeed"+strconv.Itoa(i), func(t *testing.T) { + { + params := new(AllocateWithSeed) + fu.Fuzz(params) + params.AccountMetaSlice = nil + buf := new(bytes.Buffer) + err := encodeT(*params, buf) + ag_require.NoError(t, err) + // + got := new(AllocateWithSeed) + err = decodeT(got, buf.Bytes()) + got.AccountMetaSlice = nil + ag_require.NoError(t, err) + ag_require.Equal(t, params, got) + } + }) + } +} diff --git a/programs/system/Allocate_test.go b/programs/system/Allocate_test.go new file mode 100644 index 00000000..33f0a4e2 --- /dev/null +++ b/programs/system/Allocate_test.go @@ -0,0 +1,31 @@ +package system + +import ( + "bytes" + ag_gofuzz "github.com/google/gofuzz" + ag_require "github.com/stretchr/testify/require" + "strconv" + "testing" +) + +func TestEncodeDecode_Allocate(t *testing.T) { + fu := ag_gofuzz.New().NilChance(0) + for i := 0; i < 1; i++ { + t.Run("Allocate"+strconv.Itoa(i), func(t *testing.T) { + { + params := new(Allocate) + fu.Fuzz(params) + params.AccountMetaSlice = nil + buf := new(bytes.Buffer) + err := encodeT(*params, buf) + ag_require.NoError(t, err) + // + got := new(Allocate) + err = decodeT(got, buf.Bytes()) + got.AccountMetaSlice = nil + ag_require.NoError(t, err) + ag_require.Equal(t, params, got) + } + }) + } +} diff --git a/programs/system/Assign.go b/programs/system/Assign.go index 3c08c8bc..33d4e0fb 100644 --- a/programs/system/Assign.go +++ b/programs/system/Assign.go @@ -1,37 +1,133 @@ package system import ( - bin "github.com/dfuse-io/binary" - solana "github.com/gagliardetto/solana-go" + "encoding/binary" + "errors" + "fmt" + ag_binary "github.com/dfuse-io/binary" + ag_solanago "github.com/gagliardetto/solana-go" + ag_format "github.com/gagliardetto/solana-go/text/format" + ag_treeout "github.com/gagliardetto/treeout" ) -func NewAssignInstruction( - // The account that is being assigned. - accountPubkey solana.PublicKey, - // The program that is becoming the new owner of the account. - assignToProgramID solana.PublicKey, -) *Instruction { - return &Instruction{ - BaseVariant: bin.BaseVariant{ +// Assign account to a program +type Assign struct { + // Owner program account + Owner *ag_solanago.PublicKey + + // [0] = [WRITE, SIGNER] AssignedAccount + // ··········· Assigned account public key + ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"` +} + +// NewAssignInstructionBuilder creates a new `Assign` instruction builder. +func NewAssignInstructionBuilder() *Assign { + nd := &Assign{ + AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 1), + } + return nd +} - TypeID: Instruction_Assign, +// Owner program account +func (inst *Assign) SetOwner(owner ag_solanago.PublicKey) *Assign { + inst.Owner = &owner + return inst +} - Impl: &Assign{ +// Assigned account public key +func (inst *Assign) SetAssignedAccount(assignedAccount ag_solanago.PublicKey) *Assign { + inst.AccountMetaSlice[0] = ag_solanago.Meta(assignedAccount).WRITE().SIGNER() + return inst +} - NewOwner: assignToProgramID, +func (inst *Assign) GetAssignedAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice[0] +} - AccountMetaSlice: []*solana.AccountMeta{ - solana.Meta(accountPubkey).WRITE().SIGNER(), - }, - }, - }, +func (inst Assign) Build() *Instruction { + return &Instruction{BaseVariant: ag_binary.BaseVariant{ + Impl: inst, + TypeID: ag_binary.TypeIDFromUint32(Instruction_Assign, binary.LittleEndian), + }} +} + +// ValidateAndBuild validates the instruction parameters and accounts; +// if there is a validation error, it returns the error. +// Otherwise, it builds and returns the instruction. +func (inst Assign) ValidateAndBuild() (*Instruction, error) { + if err := inst.Validate(); err != nil { + return nil, err } + return inst.Build(), nil } -type Assign struct { - // Owner program account. - NewOwner solana.PublicKey +func (inst *Assign) Validate() error { + // Check whether all (required) parameters are set: + { + if inst.Owner == nil { + return errors.New("Owner parameter is not set") + } + } - // [0] = [WRITE, SIGNER] Assigned account public key. - solana.AccountMetaSlice `bin:"-"` + // 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 *Assign) EncodeToTree(parent ag_treeout.Branches) { + parent.Child(ag_format.Program(ProgramName, ProgramID)). + // + ParentFunc(func(programBranch ag_treeout.Branches) { + programBranch.Child(ag_format.Instruction("Assign")). + // + ParentFunc(func(instructionBranch ag_treeout.Branches) { + + // Parameters of the instruction: + instructionBranch.Child("Params").ParentFunc(func(paramsBranch ag_treeout.Branches) { + paramsBranch.Child(ag_format.Param("Owner", *inst.Owner)) + }) + + // Accounts of the instruction: + instructionBranch.Child("Accounts").ParentFunc(func(accountsBranch ag_treeout.Branches) { + accountsBranch.Child(ag_format.Meta("AssignedAccount", inst.AccountMetaSlice[0])) + }) + }) + }) +} + +func (inst Assign) MarshalWithEncoder(encoder *ag_binary.Encoder) error { + // Serialize `Owner` param: + { + err := encoder.Encode(*inst.Owner) + if err != nil { + return err + } + } + return nil +} + +func (inst *Assign) UnmarshalWithDecoder(decoder *ag_binary.Decoder) error { + // Deserialize `Owner` param: + { + err := decoder.Decode(&inst.Owner) + if err != nil { + return err + } + } + return nil +} + +// NewAssignInstruction declares a new Assign instruction with the provided parameters and accounts. +func NewAssignInstruction( + // Parameters: + owner ag_solanago.PublicKey, + // Accounts: + assignedAccount ag_solanago.PublicKey) *Assign { + return NewAssignInstructionBuilder(). + SetOwner(owner). + SetAssignedAccount(assignedAccount) } diff --git a/programs/system/AssignWithSeed.go b/programs/system/AssignWithSeed.go index 51910794..e233e0d5 100644 --- a/programs/system/AssignWithSeed.go +++ b/programs/system/AssignWithSeed.go @@ -1,42 +1,207 @@ package system import ( - bin "github.com/dfuse-io/binary" - solana "github.com/gagliardetto/solana-go" + "encoding/binary" + "errors" + "fmt" + ag_binary "github.com/dfuse-io/binary" + ag_solanago "github.com/gagliardetto/solana-go" + ag_format "github.com/gagliardetto/solana-go/text/format" + ag_treeout "github.com/gagliardetto/treeout" ) -func NewAssignWithSeedInstruction( - accountPubKey solana.PublicKey, - basePubKey solana.PublicKey, - seed string, - owner solana.PublicKey, -) *Instruction { - return &Instruction{ - BaseVariant: bin.BaseVariant{ +// Assign account to a program based on a seed +type AssignWithSeed struct { + // Base public key + Base *ag_solanago.PublicKey - TypeID: Instruction_AssignWithSeed, + // String of ASCII chars, no longer than pubkey::MAX_SEED_LEN + Seed *string - Impl: &AssignWithSeed{ - Base: basePubKey, - Seed: seed, - Owner: owner, + // Owner program account + Owner *ag_solanago.PublicKey - AccountMetaSlice: []*solana.AccountMeta{ - solana.Meta(accountPubKey).WRITE(), - solana.Meta(basePubKey).SIGNER(), - }, - }, - }, + // [0] = [WRITE] AssignedAccount + // ··········· Assigned account + // + // [1] = [SIGNER] BaseAccount + // ··········· Base account + ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"` +} + +// NewAssignWithSeedInstructionBuilder creates a new `AssignWithSeed` instruction builder. +func NewAssignWithSeedInstructionBuilder() *AssignWithSeed { + nd := &AssignWithSeed{ + AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 2), } + return nd } -type AssignWithSeed struct { - Base solana.PublicKey - SeedSize int `bin:"sizeof=Seed"` - Seed string - Owner solana.PublicKey - - // [0] = [WRITE] Assigned account. - // [1] = [SIGNER] Base account. - solana.AccountMetaSlice `bin:"-"` +// Base public key +func (inst *AssignWithSeed) SetBase(base ag_solanago.PublicKey) *AssignWithSeed { + inst.Base = &base + return inst +} + +// String of ASCII chars, no longer than pubkey::MAX_SEED_LEN +func (inst *AssignWithSeed) SetSeed(seed string) *AssignWithSeed { + inst.Seed = &seed + return inst +} + +// Owner program account +func (inst *AssignWithSeed) SetOwner(owner ag_solanago.PublicKey) *AssignWithSeed { + inst.Owner = &owner + return inst +} + +// Assigned account +func (inst *AssignWithSeed) SetAssignedAccount(assignedAccount ag_solanago.PublicKey) *AssignWithSeed { + inst.AccountMetaSlice[0] = ag_solanago.Meta(assignedAccount).WRITE() + return inst +} + +func (inst *AssignWithSeed) GetAssignedAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice[0] +} + +// Base account +func (inst *AssignWithSeed) SetBaseAccount(baseAccount ag_solanago.PublicKey) *AssignWithSeed { + inst.AccountMetaSlice[1] = ag_solanago.Meta(baseAccount).SIGNER() + return inst +} + +func (inst *AssignWithSeed) GetBaseAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice[1] +} + +func (inst AssignWithSeed) Build() *Instruction { + return &Instruction{BaseVariant: ag_binary.BaseVariant{ + Impl: inst, + TypeID: ag_binary.TypeIDFromUint32(Instruction_AssignWithSeed, binary.LittleEndian), + }} +} + +// ValidateAndBuild validates the instruction parameters and accounts; +// if there is a validation error, it returns the error. +// Otherwise, it builds and returns the instruction. +func (inst AssignWithSeed) ValidateAndBuild() (*Instruction, error) { + if err := inst.Validate(); err != nil { + return nil, err + } + return inst.Build(), nil +} + +func (inst *AssignWithSeed) Validate() error { + // Check whether all (required) parameters are set: + { + if inst.Base == nil { + return errors.New("Base parameter is not set") + } + if inst.Seed == nil { + return errors.New("Seed parameter is not set") + } + if inst.Owner == nil { + return errors.New("Owner 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 *AssignWithSeed) EncodeToTree(parent ag_treeout.Branches) { + parent.Child(ag_format.Program(ProgramName, ProgramID)). + // + ParentFunc(func(programBranch ag_treeout.Branches) { + programBranch.Child(ag_format.Instruction("AssignWithSeed")). + // + ParentFunc(func(instructionBranch ag_treeout.Branches) { + + // Parameters of the instruction: + instructionBranch.Child("Params").ParentFunc(func(paramsBranch ag_treeout.Branches) { + paramsBranch.Child(ag_format.Param("Base", *inst.Base)) + paramsBranch.Child(ag_format.Param("Seed", *inst.Seed)) + paramsBranch.Child(ag_format.Param("Owner", *inst.Owner)) + }) + + // Accounts of the instruction: + instructionBranch.Child("Accounts").ParentFunc(func(accountsBranch ag_treeout.Branches) { + accountsBranch.Child(ag_format.Meta("AssignedAccount", inst.AccountMetaSlice[0])) + accountsBranch.Child(ag_format.Meta("BaseAccount", inst.AccountMetaSlice[1])) + }) + }) + }) +} + +func (inst AssignWithSeed) MarshalWithEncoder(encoder *ag_binary.Encoder) error { + // Serialize `Base` param: + { + err := encoder.Encode(*inst.Base) + if err != nil { + return err + } + } + // Serialize `Seed` param: + { + err := encoder.Encode(*inst.Seed) + if err != nil { + return err + } + } + // Serialize `Owner` param: + { + err := encoder.Encode(*inst.Owner) + if err != nil { + return err + } + } + return nil +} + +func (inst *AssignWithSeed) UnmarshalWithDecoder(decoder *ag_binary.Decoder) error { + // Deserialize `Base` param: + { + err := decoder.Decode(&inst.Base) + if err != nil { + return err + } + } + // Deserialize `Seed` param: + { + err := decoder.Decode(&inst.Seed) + if err != nil { + return err + } + } + // Deserialize `Owner` param: + { + err := decoder.Decode(&inst.Owner) + if err != nil { + return err + } + } + return nil +} + +// NewAssignWithSeedInstruction declares a new AssignWithSeed instruction with the provided parameters and accounts. +func NewAssignWithSeedInstruction( + // Parameters: + base ag_solanago.PublicKey, + seed string, + owner ag_solanago.PublicKey, + // Accounts: + assignedAccount ag_solanago.PublicKey, + baseAccount ag_solanago.PublicKey) *AssignWithSeed { + return NewAssignWithSeedInstructionBuilder(). + SetBase(base). + SetSeed(seed). + SetOwner(owner). + SetAssignedAccount(assignedAccount). + SetBaseAccount(baseAccount) } diff --git a/programs/system/AssignWithSeed_test.go b/programs/system/AssignWithSeed_test.go new file mode 100644 index 00000000..a1272d68 --- /dev/null +++ b/programs/system/AssignWithSeed_test.go @@ -0,0 +1,31 @@ +package system + +import ( + "bytes" + ag_gofuzz "github.com/google/gofuzz" + ag_require "github.com/stretchr/testify/require" + "strconv" + "testing" +) + +func TestEncodeDecode_AssignWithSeed(t *testing.T) { + fu := ag_gofuzz.New().NilChance(0) + for i := 0; i < 1; i++ { + t.Run("AssignWithSeed"+strconv.Itoa(i), func(t *testing.T) { + { + params := new(AssignWithSeed) + fu.Fuzz(params) + params.AccountMetaSlice = nil + buf := new(bytes.Buffer) + err := encodeT(*params, buf) + ag_require.NoError(t, err) + // + got := new(AssignWithSeed) + err = decodeT(got, buf.Bytes()) + got.AccountMetaSlice = nil + ag_require.NoError(t, err) + ag_require.Equal(t, params, got) + } + }) + } +} diff --git a/programs/system/Assign_test.go b/programs/system/Assign_test.go new file mode 100644 index 00000000..1b264c59 --- /dev/null +++ b/programs/system/Assign_test.go @@ -0,0 +1,31 @@ +package system + +import ( + "bytes" + ag_gofuzz "github.com/google/gofuzz" + ag_require "github.com/stretchr/testify/require" + "strconv" + "testing" +) + +func TestEncodeDecode_Assign(t *testing.T) { + fu := ag_gofuzz.New().NilChance(0) + for i := 0; i < 1; i++ { + t.Run("Assign"+strconv.Itoa(i), func(t *testing.T) { + { + params := new(Assign) + fu.Fuzz(params) + params.AccountMetaSlice = nil + buf := new(bytes.Buffer) + err := encodeT(*params, buf) + ag_require.NoError(t, err) + // + got := new(Assign) + err = decodeT(got, buf.Bytes()) + got.AccountMetaSlice = nil + ag_require.NoError(t, err) + ag_require.Equal(t, params, got) + } + }) + } +} diff --git a/programs/system/AuthorizeNonceAccount.go b/programs/system/AuthorizeNonceAccount.go index 15e12847..f1a5e79a 100644 --- a/programs/system/AuthorizeNonceAccount.go +++ b/programs/system/AuthorizeNonceAccount.go @@ -1,37 +1,149 @@ package system import ( - bin "github.com/dfuse-io/binary" - solana "github.com/gagliardetto/solana-go" + "encoding/binary" + "errors" + "fmt" + ag_binary "github.com/dfuse-io/binary" + ag_solanago "github.com/gagliardetto/solana-go" + ag_format "github.com/gagliardetto/solana-go/text/format" + ag_treeout "github.com/gagliardetto/treeout" ) -func NewAuthorizeNonceAccountInstruction( - authorizePubKey solana.PublicKey, - - noncePubKey solana.PublicKey, - nonceAuthority solana.PublicKey, -) *Instruction { - return &Instruction{ - BaseVariant: bin.BaseVariant{ - - TypeID: Instruction_AuthorizeNonceAccount, - - Impl: &AuthorizeNonceAccount{ - PubKey: authorizePubKey, - AccountMetaSlice: []*solana.AccountMeta{ - solana.Meta(noncePubKey).WRITE(), - solana.Meta(nonceAuthority).SIGNER(), - }, - }, - }, +// Change the entity authorized to execute nonce instructions on the account +type AuthorizeNonceAccount struct { + // The Pubkey parameter identifies the entity to authorize. + Authorized *ag_solanago.PublicKey + + // [0] = [WRITE] NonceAccount + // ··········· Nonce account + // + // [1] = [SIGNER] NonceAuthorityAccount + // ··········· Nonce authority + ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"` +} + +// NewAuthorizeNonceAccountInstructionBuilder creates a new `AuthorizeNonceAccount` instruction builder. +func NewAuthorizeNonceAccountInstructionBuilder() *AuthorizeNonceAccount { + nd := &AuthorizeNonceAccount{ + AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 2), } + return nd } -type AuthorizeNonceAccount struct { - // The Pubkey parameter identifies the entity to authorize. - PubKey solana.PublicKey +// The Pubkey parameter identifies the entity to authorize. +func (inst *AuthorizeNonceAccount) SetAuthorized(authorized ag_solanago.PublicKey) *AuthorizeNonceAccount { + inst.Authorized = &authorized + return inst +} + +// Nonce account +func (inst *AuthorizeNonceAccount) SetNonceAccount(nonceAccount ag_solanago.PublicKey) *AuthorizeNonceAccount { + inst.AccountMetaSlice[0] = ag_solanago.Meta(nonceAccount).WRITE() + return inst +} + +func (inst *AuthorizeNonceAccount) GetNonceAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice[0] +} + +// Nonce authority +func (inst *AuthorizeNonceAccount) SetNonceAuthorityAccount(nonceAuthorityAccount ag_solanago.PublicKey) *AuthorizeNonceAccount { + inst.AccountMetaSlice[1] = ag_solanago.Meta(nonceAuthorityAccount).SIGNER() + return inst +} + +func (inst *AuthorizeNonceAccount) GetNonceAuthorityAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice[1] +} + +func (inst AuthorizeNonceAccount) Build() *Instruction { + return &Instruction{BaseVariant: ag_binary.BaseVariant{ + Impl: inst, + TypeID: ag_binary.TypeIDFromUint32(Instruction_AuthorizeNonceAccount, binary.LittleEndian), + }} +} - // [0] = [WRITE] Nonce account. - // [1] = [SIGNER] Nonce authority. - solana.AccountMetaSlice `bin:"-"` +// ValidateAndBuild validates the instruction parameters and accounts; +// if there is a validation error, it returns the error. +// Otherwise, it builds and returns the instruction. +func (inst AuthorizeNonceAccount) ValidateAndBuild() (*Instruction, error) { + if err := inst.Validate(); err != nil { + return nil, err + } + return inst.Build(), nil +} + +func (inst *AuthorizeNonceAccount) Validate() error { + // Check whether all (required) parameters are set: + { + if inst.Authorized == nil { + return errors.New("Authorized 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 *AuthorizeNonceAccount) EncodeToTree(parent ag_treeout.Branches) { + parent.Child(ag_format.Program(ProgramName, ProgramID)). + // + ParentFunc(func(programBranch ag_treeout.Branches) { + programBranch.Child(ag_format.Instruction("AuthorizeNonceAccount")). + // + ParentFunc(func(instructionBranch ag_treeout.Branches) { + + // Parameters of the instruction: + instructionBranch.Child("Params").ParentFunc(func(paramsBranch ag_treeout.Branches) { + paramsBranch.Child(ag_format.Param("Authorized", *inst.Authorized)) + }) + + // Accounts of the instruction: + instructionBranch.Child("Accounts").ParentFunc(func(accountsBranch ag_treeout.Branches) { + accountsBranch.Child(ag_format.Meta("NonceAccount", inst.AccountMetaSlice[0])) + accountsBranch.Child(ag_format.Meta("NonceAuthorityAccount", inst.AccountMetaSlice[1])) + }) + }) + }) +} + +func (inst AuthorizeNonceAccount) MarshalWithEncoder(encoder *ag_binary.Encoder) error { + // Serialize `Authorized` param: + { + err := encoder.Encode(*inst.Authorized) + if err != nil { + return err + } + } + return nil +} + +func (inst *AuthorizeNonceAccount) UnmarshalWithDecoder(decoder *ag_binary.Decoder) error { + // Deserialize `Authorized` param: + { + err := decoder.Decode(&inst.Authorized) + if err != nil { + return err + } + } + return nil +} + +// NewAuthorizeNonceAccountInstruction declares a new AuthorizeNonceAccount instruction with the provided parameters and accounts. +func NewAuthorizeNonceAccountInstruction( + // Parameters: + authorized ag_solanago.PublicKey, + // Accounts: + nonceAccount ag_solanago.PublicKey, + nonceAuthorityAccount ag_solanago.PublicKey) *AuthorizeNonceAccount { + return NewAuthorizeNonceAccountInstructionBuilder(). + SetAuthorized(authorized). + SetNonceAccount(nonceAccount). + SetNonceAuthorityAccount(nonceAuthorityAccount) } diff --git a/programs/system/AuthorizeNonceAccount_test.go b/programs/system/AuthorizeNonceAccount_test.go new file mode 100644 index 00000000..30379b37 --- /dev/null +++ b/programs/system/AuthorizeNonceAccount_test.go @@ -0,0 +1,31 @@ +package system + +import ( + "bytes" + ag_gofuzz "github.com/google/gofuzz" + ag_require "github.com/stretchr/testify/require" + "strconv" + "testing" +) + +func TestEncodeDecode_AuthorizeNonceAccount(t *testing.T) { + fu := ag_gofuzz.New().NilChance(0) + for i := 0; i < 1; i++ { + t.Run("AuthorizeNonceAccount"+strconv.Itoa(i), func(t *testing.T) { + { + params := new(AuthorizeNonceAccount) + fu.Fuzz(params) + params.AccountMetaSlice = nil + buf := new(bytes.Buffer) + err := encodeT(*params, buf) + ag_require.NoError(t, err) + // + got := new(AuthorizeNonceAccount) + err = decodeT(got, buf.Bytes()) + got.AccountMetaSlice = nil + ag_require.NoError(t, err) + ag_require.Equal(t, params, got) + } + }) + } +} diff --git a/programs/system/CreateAccount.go b/programs/system/CreateAccount.go index aeb62ae4..d531230e 100644 --- a/programs/system/CreateAccount.go +++ b/programs/system/CreateAccount.go @@ -1,114 +1,207 @@ package system import ( + "encoding/binary" + "errors" "fmt" - - bin "github.com/dfuse-io/binary" - solana "github.com/gagliardetto/solana-go" + ag_binary "github.com/dfuse-io/binary" + ag_solanago "github.com/gagliardetto/solana-go" + ag_format "github.com/gagliardetto/solana-go/text/format" + ag_treeout "github.com/gagliardetto/treeout" ) -func NewCreateAccountInstruction( - lamports uint64, - space uint64, - owner solana.PublicKey, +// Create a new account +type CreateAccount struct { + // Number of lamports to transfer to the new account + Lamports *uint64 - fundingAccount solana.PublicKey, - newAccount solana.PublicKey, -) *Instruction { - return NewCreateAccountBuilder(). - WithLamports(lamports). - WithSpace(space). - WithOwner(owner). - WithFundingAccount(fundingAccount). - WithNewAccount(newAccount). - Build() + // Number of bytes of memory to allocate + Space *uint64 + + // Address of program that will own the new account + Owner *ag_solanago.PublicKey + + // [0] = [WRITE, SIGNER] FundingAccount + // ··········· Funding account + // + // [1] = [WRITE, SIGNER] NewAccount + // ··········· New account + ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"` } -// Create a new account. -type CreateAccount struct { - // Number of lamports to transfer to the new account. - Lamports bin.Uint64 - // Number of bytes of memory to allocate. - Space bin.Uint64 - // Address of program that will own the new account. - Owner solana.PublicKey - - // [0] = [WRITE, SIGNER] Funding account. - // [1] = [WRITE, SIGNER] New account. - solana.AccountMetaSlice `bin:"-"` -} - -// NewCreateAccountBuilder initializes a new CreateAccount builder. -func NewCreateAccountBuilder() *CreateAccount { - return &CreateAccount{ - AccountMetaSlice: make(solana.AccountMetaSlice, 2), +// NewCreateAccountInstructionBuilder creates a new `CreateAccount` instruction builder. +func NewCreateAccountInstructionBuilder() *CreateAccount { + nd := &CreateAccount{ + AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 2), } + return nd } -// WithLamports sets the number of lamports to transfer to the new account. -func (ins *CreateAccount) WithLamports(lamports uint64) *CreateAccount { - ins.Lamports = bin.Uint64(lamports) - return ins +// Number of lamports to transfer to the new account +func (inst *CreateAccount) SetLamports(lamports uint64) *CreateAccount { + inst.Lamports = &lamports + return inst } -// WithSpace sets the number of bytes of memory to allocate. -func (ins *CreateAccount) WithSpace(space uint64) *CreateAccount { - ins.Space = bin.Uint64(space) - return ins +// Number of bytes of memory to allocate +func (inst *CreateAccount) SetSpace(space uint64) *CreateAccount { + inst.Space = &space + return inst } -// WithOwner sets the address of program that will own the new account. -func (ins *CreateAccount) WithOwner(owner solana.PublicKey) *CreateAccount { - ins.Owner = owner - return ins +// Address of program that will own the new account +func (inst *CreateAccount) SetOwner(owner ag_solanago.PublicKey) *CreateAccount { + inst.Owner = &owner + return inst } -// WithFundingAccount sets the account that will fund the new account. -func (ins *CreateAccount) WithFundingAccount(fundingAccount solana.PublicKey) *CreateAccount { - ins.AccountMetaSlice[0] = solana.Meta(fundingAccount).WRITE().SIGNER() - return ins +// Funding account +func (inst *CreateAccount) SetFundingAccount(fundingAccount ag_solanago.PublicKey) *CreateAccount { + inst.AccountMetaSlice[0] = ag_solanago.Meta(fundingAccount).WRITE().SIGNER() + return inst } -// GetFundingAccount gets the account that will fund the new account. -func (ins *CreateAccount) GetFundingAccount() *solana.PublicKey { - ac := ins.AccountMetaSlice[0] - if ac == nil { - return nil - } - return &ac.PublicKey +func (inst *CreateAccount) GetFundingAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice[0] } -// WithNewAccount sets the new account that will be created. -func (ins *CreateAccount) WithNewAccount(newAccount solana.PublicKey) *CreateAccount { - ins.AccountMetaSlice[1] = solana.Meta(newAccount).WRITE().SIGNER() - return ins +// New account +func (inst *CreateAccount) SetNewAccount(newAccount ag_solanago.PublicKey) *CreateAccount { + inst.AccountMetaSlice[1] = ag_solanago.Meta(newAccount).WRITE().SIGNER() + return inst } -// GetNewAccount gets the new account. -func (ins *CreateAccount) GetNewAccount() *solana.PublicKey { - ac := ins.AccountMetaSlice[1] - if ac == nil { - return nil +func (inst *CreateAccount) GetNewAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice[1] +} + +func (inst CreateAccount) Build() *Instruction { + return &Instruction{BaseVariant: ag_binary.BaseVariant{ + Impl: inst, + TypeID: ag_binary.TypeIDFromUint32(Instruction_CreateAccount, binary.LittleEndian), + }} +} + +// ValidateAndBuild validates the instruction parameters and accounts; +// if there is a validation error, it returns the error. +// Otherwise, it builds and returns the instruction. +func (inst CreateAccount) ValidateAndBuild() (*Instruction, error) { + if err := inst.Validate(); err != nil { + return nil, err } - return &ac.PublicKey + return inst.Build(), nil } -func (ins *CreateAccount) Validate() error { - for accIndex, acc := range ins.AccountMetaSlice { +func (inst *CreateAccount) Validate() error { + // Check whether all (required) parameters are set: + { + if inst.Lamports == nil { + return errors.New("Lamports parameter is not set") + } + if inst.Space == nil { + return errors.New("Space parameter is not set") + } + if inst.Owner == nil { + return errors.New("Owner 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 nil", accIndex) + return fmt.Errorf("ins.AccountMetaSlice[%v] is not set", accIndex) } } return nil } -func (ins *CreateAccount) Build() *Instruction { - return &Instruction{ - BaseVariant: bin.BaseVariant{ +func (inst *CreateAccount) EncodeToTree(parent ag_treeout.Branches) { + parent.Child(ag_format.Program(ProgramName, ProgramID)). + // + ParentFunc(func(programBranch ag_treeout.Branches) { + programBranch.Child(ag_format.Instruction("CreateAccount")). + // + ParentFunc(func(instructionBranch ag_treeout.Branches) { + + // Parameters of the instruction: + instructionBranch.Child("Params").ParentFunc(func(paramsBranch ag_treeout.Branches) { + paramsBranch.Child(ag_format.Param("Lamports", *inst.Lamports)) + paramsBranch.Child(ag_format.Param("Space", *inst.Space)) + paramsBranch.Child(ag_format.Param("Owner", *inst.Owner)) + }) + + // Accounts of the instruction: + instructionBranch.Child("Accounts").ParentFunc(func(accountsBranch ag_treeout.Branches) { + accountsBranch.Child(ag_format.Meta("FundingAccount", inst.AccountMetaSlice[0])) + accountsBranch.Child(ag_format.Meta("NewAccount", inst.AccountMetaSlice[1])) + }) + }) + }) +} - TypeID: Instruction_CreateAccount, +func (inst CreateAccount) MarshalWithEncoder(encoder *ag_binary.Encoder) error { + // Serialize `Lamports` param: + { + err := encoder.Encode(*inst.Lamports) + if err != nil { + return err + } + } + // Serialize `Space` param: + { + err := encoder.Encode(*inst.Space) + if err != nil { + return err + } + } + // Serialize `Owner` param: + { + err := encoder.Encode(*inst.Owner) + if err != nil { + return err + } + } + return nil +} - Impl: ins, - }, +func (inst *CreateAccount) UnmarshalWithDecoder(decoder *ag_binary.Decoder) error { + // Deserialize `Lamports` param: + { + err := decoder.Decode(&inst.Lamports) + if err != nil { + return err + } } + // Deserialize `Space` param: + { + err := decoder.Decode(&inst.Space) + if err != nil { + return err + } + } + // Deserialize `Owner` param: + { + err := decoder.Decode(&inst.Owner) + if err != nil { + return err + } + } + return nil +} + +// NewCreateAccountInstruction declares a new CreateAccount instruction with the provided parameters and accounts. +func NewCreateAccountInstruction( + // Parameters: + lamports uint64, + space uint64, + owner ag_solanago.PublicKey, + // Accounts: + fundingAccount ag_solanago.PublicKey, + newAccount ag_solanago.PublicKey) *CreateAccount { + return NewCreateAccountInstructionBuilder(). + SetLamports(lamports). + SetSpace(space). + SetOwner(owner). + SetFundingAccount(fundingAccount). + SetNewAccount(newAccount) } diff --git a/programs/system/CreateAccountWithSeed.go b/programs/system/CreateAccountWithSeed.go index 2955d943..e12a369b 100644 --- a/programs/system/CreateAccountWithSeed.go +++ b/programs/system/CreateAccountWithSeed.go @@ -1,70 +1,281 @@ package system import ( - bin "github.com/dfuse-io/binary" - solana "github.com/gagliardetto/solana-go" + "encoding/binary" + "errors" + "fmt" + ag_binary "github.com/dfuse-io/binary" + ag_solanago "github.com/gagliardetto/solana-go" + ag_format "github.com/gagliardetto/solana-go/text/format" + ag_treeout "github.com/gagliardetto/treeout" ) -func NewCreateAccountWithSeedInstruction( - base solana.PublicKey, - seed string, - lamports uint64, - space uint64, - owner solana.PublicKey, - - fundingAccount solana.PublicKey, - newAccount solana.PublicKey, -) *Instruction { - return &Instruction{ - BaseVariant: bin.BaseVariant{ - - TypeID: Instruction_CreateAccountWithSeed, - - Impl: &CreateAccountWithSeed{ - - Base: base, - Seed: seed, - Lamports: bin.Uint64(lamports), - Space: bin.Uint64(space), - Owner: owner, - - AccountMetaSlice: func() solana.AccountMetaSlice { - res := solana.AccountMetaSlice{ - solana.Meta(fundingAccount).WRITE().SIGNER(), - solana.Meta(newAccount).WRITE(), - } - - if !base.Equals(fundingAccount) { - res.Append(solana.Meta(base).SIGNER()) - } - - return res - }(), - }, - }, +// Create a new account at an address derived from a base pubkey and a seed +type CreateAccountWithSeed struct { + // Base public key + Base *ag_solanago.PublicKey + + // String of ASCII chars, no longer than Pubkey::MAX_SEED_LEN + Seed *string + + // Number of lamports to transfer to the new account + Lamports *uint64 + + // Number of bytes of memory to allocate + Space *uint64 + + // Owner program account address + Owner *ag_solanago.PublicKey + + // [0] = [WRITE, SIGNER] FundingAccount + // ··········· Funding account + // + // [1] = [WRITE] CreatedAccount + // ··········· Created account + // + // [2] = [SIGNER] BaseAccount + // ··········· Base account + ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"` +} + +// NewCreateAccountWithSeedInstructionBuilder creates a new `CreateAccountWithSeed` instruction builder. +func NewCreateAccountWithSeedInstructionBuilder() *CreateAccountWithSeed { + nd := &CreateAccountWithSeed{ + AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 3), } + return nd } -type CreateAccountWithSeed struct { - // Base public key. - Base solana.PublicKey +// Base public key +func (inst *CreateAccountWithSeed) SetBase(base ag_solanago.PublicKey) *CreateAccountWithSeed { + inst.Base = &base + return inst +} + +// String of ASCII chars, no longer than Pubkey::MAX_SEED_LEN +func (inst *CreateAccountWithSeed) SetSeed(seed string) *CreateAccountWithSeed { + inst.Seed = &seed + return inst +} - SeedSize int64 `bin:"sizeof=Seed"` - // String of ASCII chars, no longer than solana.MAX_SEED_LEN - Seed string +// Number of lamports to transfer to the new account +func (inst *CreateAccountWithSeed) SetLamports(lamports uint64) *CreateAccountWithSeed { + inst.Lamports = &lamports + return inst +} - // Number of lamports to transfer to the new account. - Lamports bin.Uint64 +// Number of bytes of memory to allocate +func (inst *CreateAccountWithSeed) SetSpace(space uint64) *CreateAccountWithSeed { + inst.Space = &space + return inst +} - // Number of bytes of memory to allocate - Space bin.Uint64 +// Owner program account address +func (inst *CreateAccountWithSeed) SetOwner(owner ag_solanago.PublicKey) *CreateAccountWithSeed { + inst.Owner = &owner + return inst +} + +// Funding account +func (inst *CreateAccountWithSeed) SetFundingAccount(fundingAccount ag_solanago.PublicKey) *CreateAccountWithSeed { + inst.AccountMetaSlice[0] = ag_solanago.Meta(fundingAccount).WRITE().SIGNER() + return inst +} + +func (inst *CreateAccountWithSeed) GetFundingAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice[0] +} + +// Created account +func (inst *CreateAccountWithSeed) SetCreatedAccount(createdAccount ag_solanago.PublicKey) *CreateAccountWithSeed { + inst.AccountMetaSlice[1] = ag_solanago.Meta(createdAccount).WRITE() + return inst +} + +func (inst *CreateAccountWithSeed) GetCreatedAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice[1] +} + +// Base account +func (inst *CreateAccountWithSeed) SetBaseAccount(baseAccount ag_solanago.PublicKey) *CreateAccountWithSeed { + inst.AccountMetaSlice[2] = ag_solanago.Meta(baseAccount).SIGNER() + return inst +} + +func (inst *CreateAccountWithSeed) GetBaseAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice[2] +} + +func (inst CreateAccountWithSeed) Build() *Instruction { + return &Instruction{BaseVariant: ag_binary.BaseVariant{ + Impl: inst, + TypeID: ag_binary.TypeIDFromUint32(Instruction_CreateAccountWithSeed, binary.LittleEndian), + }} +} + +// ValidateAndBuild validates the instruction parameters and accounts; +// if there is a validation error, it returns the error. +// Otherwise, it builds and returns the instruction. +func (inst CreateAccountWithSeed) ValidateAndBuild() (*Instruction, error) { + if err := inst.Validate(); err != nil { + return nil, err + } + return inst.Build(), nil +} + +func (inst *CreateAccountWithSeed) Validate() error { + // Check whether all (required) parameters are set: + { + if inst.Base == nil { + return errors.New("Base parameter is not set") + } + if inst.Seed == nil { + return errors.New("Seed parameter is not set") + } + if inst.Lamports == nil { + return errors.New("Lamports parameter is not set") + } + if inst.Space == nil { + return errors.New("Space parameter is not set") + } + if inst.Owner == nil { + return errors.New("Owner 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 *CreateAccountWithSeed) EncodeToTree(parent ag_treeout.Branches) { + parent.Child(ag_format.Program(ProgramName, ProgramID)). + // + ParentFunc(func(programBranch ag_treeout.Branches) { + programBranch.Child(ag_format.Instruction("CreateAccountWithSeed")). + // + ParentFunc(func(instructionBranch ag_treeout.Branches) { + + // Parameters of the instruction: + instructionBranch.Child("Params").ParentFunc(func(paramsBranch ag_treeout.Branches) { + paramsBranch.Child(ag_format.Param("Base", *inst.Base)) + paramsBranch.Child(ag_format.Param("Seed", *inst.Seed)) + paramsBranch.Child(ag_format.Param("Lamports", *inst.Lamports)) + paramsBranch.Child(ag_format.Param("Space", *inst.Space)) + paramsBranch.Child(ag_format.Param("Owner", *inst.Owner)) + }) + + // Accounts of the instruction: + instructionBranch.Child("Accounts").ParentFunc(func(accountsBranch ag_treeout.Branches) { + accountsBranch.Child(ag_format.Meta("FundingAccount", inst.AccountMetaSlice[0])) + accountsBranch.Child(ag_format.Meta("CreatedAccount", inst.AccountMetaSlice[1])) + accountsBranch.Child(ag_format.Meta("BaseAccount", inst.AccountMetaSlice[2])) + }) + }) + }) +} + +func (inst CreateAccountWithSeed) MarshalWithEncoder(encoder *ag_binary.Encoder) error { + // Serialize `Base` param: + { + err := encoder.Encode(*inst.Base) + if err != nil { + return err + } + } + // Serialize `Seed` param: + { + err := encoder.Encode(*inst.Seed) + if err != nil { + return err + } + } + // Serialize `Lamports` param: + { + err := encoder.Encode(*inst.Lamports) + if err != nil { + return err + } + } + // Serialize `Space` param: + { + err := encoder.Encode(*inst.Space) + if err != nil { + return err + } + } + // Serialize `Owner` param: + { + err := encoder.Encode(*inst.Owner) + if err != nil { + return err + } + } + return nil +} - // Owner program account address. - Owner solana.PublicKey +func (inst *CreateAccountWithSeed) UnmarshalWithDecoder(decoder *ag_binary.Decoder) error { + // Deserialize `Base` param: + { + err := decoder.Decode(&inst.Base) + if err != nil { + return err + } + } + // Deserialize `Seed` param: + { + err := decoder.Decode(&inst.Seed) + if err != nil { + return err + } + } + // Deserialize `Lamports` param: + { + err := decoder.Decode(&inst.Lamports) + if err != nil { + return err + } + } + // Deserialize `Space` param: + { + err := decoder.Decode(&inst.Space) + if err != nil { + return err + } + } + // Deserialize `Owner` param: + { + err := decoder.Decode(&inst.Owner) + if err != nil { + return err + } + } + return nil +} - // [0] = [WRITE, SIGNER] Funding account. - // [1] = [WRITE] Created account. - // [2] = [SIGNER] Base account; the account matching the base Pubkey below must be provided as a signer, - // but may be the same as the funding account and provided as account 0 - solana.AccountMetaSlice `bin:"-"` +// NewCreateAccountWithSeedInstruction declares a new CreateAccountWithSeed instruction with the provided parameters and accounts. +func NewCreateAccountWithSeedInstruction( + // Parameters: + base ag_solanago.PublicKey, + seed string, + lamports uint64, + space uint64, + owner ag_solanago.PublicKey, + // Accounts: + fundingAccount ag_solanago.PublicKey, + createdAccount ag_solanago.PublicKey, + baseAccount ag_solanago.PublicKey) *CreateAccountWithSeed { + return NewCreateAccountWithSeedInstructionBuilder(). + SetBase(base). + SetSeed(seed). + SetLamports(lamports). + SetSpace(space). + SetOwner(owner). + SetFundingAccount(fundingAccount). + SetCreatedAccount(createdAccount). + SetBaseAccount(baseAccount) } diff --git a/programs/system/CreateAccountWithSeed_test.go b/programs/system/CreateAccountWithSeed_test.go new file mode 100644 index 00000000..aa2ac740 --- /dev/null +++ b/programs/system/CreateAccountWithSeed_test.go @@ -0,0 +1,31 @@ +package system + +import ( + "bytes" + ag_gofuzz "github.com/google/gofuzz" + ag_require "github.com/stretchr/testify/require" + "strconv" + "testing" +) + +func TestEncodeDecode_CreateAccountWithSeed(t *testing.T) { + fu := ag_gofuzz.New().NilChance(0) + for i := 0; i < 1; i++ { + t.Run("CreateAccountWithSeed"+strconv.Itoa(i), func(t *testing.T) { + { + params := new(CreateAccountWithSeed) + fu.Fuzz(params) + params.AccountMetaSlice = nil + buf := new(bytes.Buffer) + err := encodeT(*params, buf) + ag_require.NoError(t, err) + // + got := new(CreateAccountWithSeed) + err = decodeT(got, buf.Bytes()) + got.AccountMetaSlice = nil + ag_require.NoError(t, err) + ag_require.Equal(t, params, got) + } + }) + } +} diff --git a/programs/system/CreateAccount_test.go b/programs/system/CreateAccount_test.go new file mode 100644 index 00000000..fdda187a --- /dev/null +++ b/programs/system/CreateAccount_test.go @@ -0,0 +1,31 @@ +package system + +import ( + "bytes" + ag_gofuzz "github.com/google/gofuzz" + ag_require "github.com/stretchr/testify/require" + "strconv" + "testing" +) + +func TestEncodeDecode_CreateAccount(t *testing.T) { + fu := ag_gofuzz.New().NilChance(0) + for i := 0; i < 1; i++ { + t.Run("CreateAccount"+strconv.Itoa(i), func(t *testing.T) { + { + params := new(CreateAccount) + fu.Fuzz(params) + params.AccountMetaSlice = nil + buf := new(bytes.Buffer) + err := encodeT(*params, buf) + ag_require.NoError(t, err) + // + got := new(CreateAccount) + err = decodeT(got, buf.Bytes()) + got.AccountMetaSlice = nil + ag_require.NoError(t, err) + ag_require.Equal(t, params, got) + } + }) + } +} diff --git a/programs/system/InitializeNonceAccount.go b/programs/system/InitializeNonceAccount.go index c00385f5..6ddbb0c7 100644 --- a/programs/system/InitializeNonceAccount.go +++ b/programs/system/InitializeNonceAccount.go @@ -1,40 +1,169 @@ package system import ( - bin "github.com/dfuse-io/binary" - solana "github.com/gagliardetto/solana-go" + "encoding/binary" + "errors" + "fmt" + ag_binary "github.com/dfuse-io/binary" + ag_solanago "github.com/gagliardetto/solana-go" + ag_format "github.com/gagliardetto/solana-go/text/format" + ag_treeout "github.com/gagliardetto/treeout" ) -func NewInitializeNonceAccountInstruction( - authPubKey solana.PublicKey, - noncePubKey solana.PublicKey, -) *Instruction { - return &Instruction{ - BaseVariant: bin.BaseVariant{ - - TypeID: Instruction_InitializeNonceAccount, - - Impl: &InitializeNonceAccount{ - AuthPubKey: authPubKey, - AccountMetaSlice: []*solana.AccountMeta{ - solana.Meta(noncePubKey).WRITE(), - solana.Meta(solana.SysVarRecentBlockHashesPubkey), - solana.Meta(solana.SysVarRentPubkey), - }, - }, - }, +// Drive state of Uninitalized nonce account to Initialized, setting the nonce value +type InitializeNonceAccount struct { + // The Pubkey parameter specifies the entity authorized to execute nonce instruction on the account. + // No signatures are required to execute this instruction, enabling derived nonce account addresses. + Authorized *ag_solanago.PublicKey + + // [0] = [WRITE] NonceAccount + // ··········· Nonce account + // + // [1] = [] $(SysVarRecentBlockHashesPubkey) + // ··········· RecentBlockhashes sysvar + // + // [2] = [] $(SysVarRentPubkey) + // ··········· Rent sysvar + ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"` +} + +// NewInitializeNonceAccountInstructionBuilder creates a new `InitializeNonceAccount` instruction builder. +func NewInitializeNonceAccountInstructionBuilder() *InitializeNonceAccount { + nd := &InitializeNonceAccount{ + AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 3), } + nd.AccountMetaSlice[1] = ag_solanago.Meta(ag_solanago.SysVarRecentBlockHashesPubkey) + nd.AccountMetaSlice[2] = ag_solanago.Meta(ag_solanago.SysVarRentPubkey) + return nd } -type InitializeNonceAccount struct { - // The Pubkey parameter specifies the entity - // authorized to execute nonce instruction on the account. - // No signatures are required to execute this instruction, - // enabling derived nonce account addresses - AuthPubKey solana.PublicKey - - // [0] = [WRITE] Nonce account. - // [1] = [] RecentBlockhashes sysvar. - // [2] = [] Rent sysvar. - solana.AccountMetaSlice `bin:"-"` +// The Pubkey parameter specifies the entity authorized to execute nonce instruction on the account. +// No signatures are required to execute this instruction, enabling derived nonce account addresses. +func (inst *InitializeNonceAccount) SetAuthorized(authorized ag_solanago.PublicKey) *InitializeNonceAccount { + inst.Authorized = &authorized + return inst +} + +// Nonce account +func (inst *InitializeNonceAccount) SetNonceAccount(nonceAccount ag_solanago.PublicKey) *InitializeNonceAccount { + inst.AccountMetaSlice[0] = ag_solanago.Meta(nonceAccount).WRITE() + return inst +} + +func (inst *InitializeNonceAccount) GetNonceAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice[0] +} + +// RecentBlockhashes sysvar +func (inst *InitializeNonceAccount) SetSysVarRecentBlockHashesPubkeyAccount(SysVarRecentBlockHashesPubkey ag_solanago.PublicKey) *InitializeNonceAccount { + inst.AccountMetaSlice[1] = ag_solanago.Meta(SysVarRecentBlockHashesPubkey) + return inst +} + +func (inst *InitializeNonceAccount) GetSysVarRecentBlockHashesPubkeyAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice[1] +} + +// Rent sysvar +func (inst *InitializeNonceAccount) SetSysVarRentPubkeyAccount(SysVarRentPubkey ag_solanago.PublicKey) *InitializeNonceAccount { + inst.AccountMetaSlice[2] = ag_solanago.Meta(SysVarRentPubkey) + return inst +} + +func (inst *InitializeNonceAccount) GetSysVarRentPubkeyAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice[2] +} + +func (inst InitializeNonceAccount) Build() *Instruction { + return &Instruction{BaseVariant: ag_binary.BaseVariant{ + Impl: inst, + TypeID: ag_binary.TypeIDFromUint32(Instruction_InitializeNonceAccount, binary.LittleEndian), + }} +} + +// ValidateAndBuild validates the instruction parameters and accounts; +// if there is a validation error, it returns the error. +// Otherwise, it builds and returns the instruction. +func (inst InitializeNonceAccount) ValidateAndBuild() (*Instruction, error) { + if err := inst.Validate(); err != nil { + return nil, err + } + return inst.Build(), nil +} + +func (inst *InitializeNonceAccount) Validate() error { + // Check whether all (required) parameters are set: + { + if inst.Authorized == nil { + return errors.New("Authorized 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 *InitializeNonceAccount) EncodeToTree(parent ag_treeout.Branches) { + parent.Child(ag_format.Program(ProgramName, ProgramID)). + // + ParentFunc(func(programBranch ag_treeout.Branches) { + programBranch.Child(ag_format.Instruction("InitializeNonceAccount")). + // + ParentFunc(func(instructionBranch ag_treeout.Branches) { + + // Parameters of the instruction: + instructionBranch.Child("Params").ParentFunc(func(paramsBranch ag_treeout.Branches) { + paramsBranch.Child(ag_format.Param("Authorized", *inst.Authorized)) + }) + + // Accounts of the instruction: + instructionBranch.Child("Accounts").ParentFunc(func(accountsBranch ag_treeout.Branches) { + accountsBranch.Child(ag_format.Meta("NonceAccount", inst.AccountMetaSlice[0])) + accountsBranch.Child(ag_format.Meta("$(SysVarRecentBlockHashesPubkey)", inst.AccountMetaSlice[1])) + accountsBranch.Child(ag_format.Meta("$(SysVarRentPubkey)", inst.AccountMetaSlice[2])) + }) + }) + }) +} + +func (inst InitializeNonceAccount) MarshalWithEncoder(encoder *ag_binary.Encoder) error { + // Serialize `Authorized` param: + { + err := encoder.Encode(*inst.Authorized) + if err != nil { + return err + } + } + return nil +} + +func (inst *InitializeNonceAccount) UnmarshalWithDecoder(decoder *ag_binary.Decoder) error { + // Deserialize `Authorized` param: + { + err := decoder.Decode(&inst.Authorized) + if err != nil { + return err + } + } + return nil +} + +// NewInitializeNonceAccountInstruction declares a new InitializeNonceAccount instruction with the provided parameters and accounts. +func NewInitializeNonceAccountInstruction( + // Parameters: + authorized ag_solanago.PublicKey, + // Accounts: + nonceAccount ag_solanago.PublicKey, + SysVarRecentBlockHashesPubkey ag_solanago.PublicKey, + SysVarRentPubkey ag_solanago.PublicKey) *InitializeNonceAccount { + return NewInitializeNonceAccountInstructionBuilder(). + SetAuthorized(authorized). + SetNonceAccount(nonceAccount). + SetSysVarRecentBlockHashesPubkeyAccount(SysVarRecentBlockHashesPubkey). + SetSysVarRentPubkeyAccount(SysVarRentPubkey) } diff --git a/programs/system/InitializeNonceAccount_test.go b/programs/system/InitializeNonceAccount_test.go new file mode 100644 index 00000000..89d835c5 --- /dev/null +++ b/programs/system/InitializeNonceAccount_test.go @@ -0,0 +1,31 @@ +package system + +import ( + "bytes" + ag_gofuzz "github.com/google/gofuzz" + ag_require "github.com/stretchr/testify/require" + "strconv" + "testing" +) + +func TestEncodeDecode_InitializeNonceAccount(t *testing.T) { + fu := ag_gofuzz.New().NilChance(0) + for i := 0; i < 1; i++ { + t.Run("InitializeNonceAccount"+strconv.Itoa(i), func(t *testing.T) { + { + params := new(InitializeNonceAccount) + fu.Fuzz(params) + params.AccountMetaSlice = nil + buf := new(bytes.Buffer) + err := encodeT(*params, buf) + ag_require.NoError(t, err) + // + got := new(InitializeNonceAccount) + err = decodeT(got, buf.Bytes()) + got.AccountMetaSlice = nil + ag_require.NoError(t, err) + ag_require.Equal(t, params, got) + } + }) + } +} diff --git a/programs/system/Transfer.go b/programs/system/Transfer.go index e6403c05..e75f3519 100644 --- a/programs/system/Transfer.go +++ b/programs/system/Transfer.go @@ -1,58 +1,149 @@ package system import ( - bin "github.com/dfuse-io/binary" - solana "github.com/gagliardetto/solana-go" - "github.com/gagliardetto/solana-go/text/format" - "github.com/gagliardetto/treeout" + "encoding/binary" + "errors" + "fmt" + ag_binary "github.com/dfuse-io/binary" + ag_solanago "github.com/gagliardetto/solana-go" + ag_format "github.com/gagliardetto/solana-go/text/format" + ag_treeout "github.com/gagliardetto/treeout" ) -func NewTransferInstruction( - lamports uint64, - from solana.PublicKey, - to solana.PublicKey, -) *Instruction { - return &Instruction{ - BaseVariant: bin.BaseVariant{ - - TypeID: Instruction_Transfer, - - Impl: &Transfer{ - Lamports: bin.Uint64(lamports), - - AccountMetaSlice: []*solana.AccountMeta{ - solana.Meta(from).WRITE().SIGNER(), - solana.Meta(to).WRITE(), - }, - }, - }, +// Transfer lamports +type Transfer struct { + // Number of lamports to transfer to the new account + Lamports *uint64 + + // [0] = [WRITE, SIGNER] FundingAccount + // ··········· Funding account + // + // [1] = [WRITE] RecipientAccount + // ··········· Recipient account + ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"` +} + +// NewTransferInstructionBuilder creates a new `Transfer` instruction builder. +func NewTransferInstructionBuilder() *Transfer { + nd := &Transfer{ + AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 2), } + return nd } -type Transfer struct { - Lamports bin.Uint64 +// Number of lamports to transfer to the new account +func (inst *Transfer) SetLamports(lamports uint64) *Transfer { + inst.Lamports = &lamports + return inst +} + +// Funding account +func (inst *Transfer) SetFundingAccount(fundingAccount ag_solanago.PublicKey) *Transfer { + inst.AccountMetaSlice[0] = ag_solanago.Meta(fundingAccount).WRITE().SIGNER() + return inst +} + +func (inst *Transfer) GetFundingAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice[0] +} + +// Recipient account +func (inst *Transfer) SetRecipientAccount(recipientAccount ag_solanago.PublicKey) *Transfer { + inst.AccountMetaSlice[1] = ag_solanago.Meta(recipientAccount).WRITE() + return inst +} + +func (inst *Transfer) GetRecipientAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice[1] +} + +func (inst Transfer) Build() *Instruction { + return &Instruction{BaseVariant: ag_binary.BaseVariant{ + Impl: inst, + TypeID: ag_binary.TypeIDFromUint32(Instruction_Transfer, binary.LittleEndian), + }} +} - // [0] = [WRITE, SIGNER] Funding account. - // [1] = [WRITE] Recipient account. - solana.AccountMetaSlice `bin:"-" text:"-"` +// ValidateAndBuild validates the instruction parameters and accounts; +// if there is a validation error, it returns the error. +// Otherwise, it builds and returns the instruction. +func (inst Transfer) ValidateAndBuild() (*Instruction, error) { + if err := inst.Validate(); err != nil { + return nil, err + } + return inst.Build(), nil } -func (inst *Transfer) EncodeToTree(parent treeout.Branches) { - parent.Child(format.Program(ProgramName, ProgramID)). +func (inst *Transfer) Validate() error { + // Check whether all (required) parameters are set: + { + 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 *Transfer) EncodeToTree(parent ag_treeout.Branches) { + parent.Child(ag_format.Program(ProgramName, ProgramID)). // - ParentFunc(func(programBranch treeout.Branches) { - programBranch.Child(format.Instruction("Transfer")). + ParentFunc(func(programBranch ag_treeout.Branches) { + programBranch.Child(ag_format.Instruction("Transfer")). // - ParentFunc(func(instructionBranch treeout.Branches) { - // - instructionBranch.Child("Params").ParentFunc(func(paramsBranch treeout.Branches) { - paramsBranch.Child(format.Param("Lamports", inst.Lamports)) + ParentFunc(func(instructionBranch ag_treeout.Branches) { + + // Parameters of the instruction: + instructionBranch.Child("Params").ParentFunc(func(paramsBranch ag_treeout.Branches) { + paramsBranch.Child(ag_format.Param("Lamports", *inst.Lamports)) }) - // - instructionBranch.Child("Accounts").ParentFunc(func(accountsBranch treeout.Branches) { - accountsBranch.Child(format.Account("FundingAccount", inst.AccountMetaSlice[0].PublicKey)) - accountsBranch.Child(format.Account("RecipientAccount", inst.AccountMetaSlice[1].PublicKey)) + + // Accounts of the instruction: + instructionBranch.Child("Accounts").ParentFunc(func(accountsBranch ag_treeout.Branches) { + accountsBranch.Child(ag_format.Meta("FundingAccount", inst.AccountMetaSlice[0])) + accountsBranch.Child(ag_format.Meta("RecipientAccount", inst.AccountMetaSlice[1])) }) }) }) } + +func (inst Transfer) MarshalWithEncoder(encoder *ag_binary.Encoder) error { + // Serialize `Lamports` param: + { + err := encoder.Encode(*inst.Lamports) + if err != nil { + return err + } + } + return nil +} + +func (inst *Transfer) UnmarshalWithDecoder(decoder *ag_binary.Decoder) error { + // Deserialize `Lamports` param: + { + err := decoder.Decode(&inst.Lamports) + if err != nil { + return err + } + } + return nil +} + +// NewTransferInstruction declares a new Transfer instruction with the provided parameters and accounts. +func NewTransferInstruction( + // Parameters: + lamports uint64, + // Accounts: + fundingAccount ag_solanago.PublicKey, + recipientAccount ag_solanago.PublicKey) *Transfer { + return NewTransferInstructionBuilder(). + SetLamports(lamports). + SetFundingAccount(fundingAccount). + SetRecipientAccount(recipientAccount) +} diff --git a/programs/system/TransferWithSeed.go b/programs/system/TransferWithSeed.go index 45eca04a..50c1cb50 100644 --- a/programs/system/TransferWithSeed.go +++ b/programs/system/TransferWithSeed.go @@ -1,46 +1,223 @@ package system import ( - bin "github.com/dfuse-io/binary" - solana "github.com/gagliardetto/solana-go" + "encoding/binary" + "errors" + "fmt" + ag_binary "github.com/dfuse-io/binary" + ag_solanago "github.com/gagliardetto/solana-go" + ag_format "github.com/gagliardetto/solana-go/text/format" + ag_treeout "github.com/gagliardetto/treeout" ) -func NewTransferWithSeedInstruction( - from solana.PublicKey, - to solana.PublicKey, - basePubKey solana.PublicKey, - owner solana.PublicKey, - seed string, - lamports uint64, -) *Instruction { - return &Instruction{ - BaseVariant: bin.BaseVariant{ +// Transfer lamports from a derived address +type TransferWithSeed struct { + // Amount to transfer + Lamports *uint64 - TypeID: Instruction_TransferWithSeed, + // Seed to use to derive the funding account address + FromSeed *string - Impl: &TransferWithSeed{ - Lamports: bin.Uint64(lamports), - Seed: seed, - Owner: owner, + // Owner to use to derive the funding account address + FromOwner *ag_solanago.PublicKey - AccountMetaSlice: []*solana.AccountMeta{ - solana.Meta(from).WRITE(), - solana.Meta(basePubKey).SIGNER(), - solana.Meta(to).WRITE(), - }, - }, - }, + // [0] = [WRITE] FundingAccount + // ··········· Funding account + // + // [1] = [SIGNER] BaseForFundingAccount + // ··········· Base for funding account + // + // [2] = [WRITE] RecipientAccount + // ··········· Recipient account + ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"` +} + +// NewTransferWithSeedInstructionBuilder creates a new `TransferWithSeed` instruction builder. +func NewTransferWithSeedInstructionBuilder() *TransferWithSeed { + nd := &TransferWithSeed{ + AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 3), } + return nd } -type TransferWithSeed struct { - Lamports bin.Uint64 - SeedSize int `bin:"sizeof=Seed"` - Seed string - Owner solana.PublicKey - - // [0] = [WRITE] Funding account. - // [1] = [SIGNER] Base for funding account. - // [2] = [WRITE] Recipient account. - solana.AccountMetaSlice `bin:"-"` +// Amount to transfer +func (inst *TransferWithSeed) SetLamports(lamports uint64) *TransferWithSeed { + inst.Lamports = &lamports + return inst +} + +// Seed to use to derive the funding account address +func (inst *TransferWithSeed) SetFromSeed(from_seed string) *TransferWithSeed { + inst.FromSeed = &from_seed + return inst +} + +// Owner to use to derive the funding account address +func (inst *TransferWithSeed) SetFromOwner(from_owner ag_solanago.PublicKey) *TransferWithSeed { + inst.FromOwner = &from_owner + return inst +} + +// Funding account +func (inst *TransferWithSeed) SetFundingAccount(fundingAccount ag_solanago.PublicKey) *TransferWithSeed { + inst.AccountMetaSlice[0] = ag_solanago.Meta(fundingAccount).WRITE() + return inst +} + +func (inst *TransferWithSeed) GetFundingAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice[0] +} + +// Base for funding account +func (inst *TransferWithSeed) SetBaseForFundingAccount(baseForFundingAccount ag_solanago.PublicKey) *TransferWithSeed { + inst.AccountMetaSlice[1] = ag_solanago.Meta(baseForFundingAccount).SIGNER() + return inst +} + +func (inst *TransferWithSeed) GetBaseForFundingAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice[1] +} + +// Recipient account +func (inst *TransferWithSeed) SetRecipientAccount(recipientAccount ag_solanago.PublicKey) *TransferWithSeed { + inst.AccountMetaSlice[2] = ag_solanago.Meta(recipientAccount).WRITE() + return inst +} + +func (inst *TransferWithSeed) GetRecipientAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice[2] +} + +func (inst TransferWithSeed) Build() *Instruction { + return &Instruction{BaseVariant: ag_binary.BaseVariant{ + Impl: inst, + TypeID: ag_binary.TypeIDFromUint32(Instruction_TransferWithSeed, binary.LittleEndian), + }} +} + +// ValidateAndBuild validates the instruction parameters and accounts; +// if there is a validation error, it returns the error. +// Otherwise, it builds and returns the instruction. +func (inst TransferWithSeed) ValidateAndBuild() (*Instruction, error) { + if err := inst.Validate(); err != nil { + return nil, err + } + return inst.Build(), nil +} + +func (inst *TransferWithSeed) Validate() error { + // Check whether all (required) parameters are set: + { + if inst.Lamports == nil { + return errors.New("Lamports parameter is not set") + } + if inst.FromSeed == nil { + return errors.New("FromSeed parameter is not set") + } + if inst.FromOwner == nil { + return errors.New("FromOwner 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 *TransferWithSeed) EncodeToTree(parent ag_treeout.Branches) { + parent.Child(ag_format.Program(ProgramName, ProgramID)). + // + ParentFunc(func(programBranch ag_treeout.Branches) { + programBranch.Child(ag_format.Instruction("TransferWithSeed")). + // + ParentFunc(func(instructionBranch ag_treeout.Branches) { + + // Parameters of the instruction: + instructionBranch.Child("Params").ParentFunc(func(paramsBranch ag_treeout.Branches) { + paramsBranch.Child(ag_format.Param("Lamports", *inst.Lamports)) + paramsBranch.Child(ag_format.Param("FromSeed", *inst.FromSeed)) + paramsBranch.Child(ag_format.Param("FromOwner", *inst.FromOwner)) + }) + + // Accounts of the instruction: + instructionBranch.Child("Accounts").ParentFunc(func(accountsBranch ag_treeout.Branches) { + accountsBranch.Child(ag_format.Meta("FundingAccount", inst.AccountMetaSlice[0])) + accountsBranch.Child(ag_format.Meta("BaseForFundingAccount", inst.AccountMetaSlice[1])) + accountsBranch.Child(ag_format.Meta("RecipientAccount", inst.AccountMetaSlice[2])) + }) + }) + }) +} + +func (inst TransferWithSeed) MarshalWithEncoder(encoder *ag_binary.Encoder) error { + // Serialize `Lamports` param: + { + err := encoder.Encode(*inst.Lamports) + if err != nil { + return err + } + } + // Serialize `FromSeed` param: + { + err := encoder.Encode(*inst.FromSeed) + if err != nil { + return err + } + } + // Serialize `FromOwner` param: + { + err := encoder.Encode(*inst.FromOwner) + if err != nil { + return err + } + } + return nil +} + +func (inst *TransferWithSeed) UnmarshalWithDecoder(decoder *ag_binary.Decoder) error { + // Deserialize `Lamports` param: + { + err := decoder.Decode(&inst.Lamports) + if err != nil { + return err + } + } + // Deserialize `FromSeed` param: + { + err := decoder.Decode(&inst.FromSeed) + if err != nil { + return err + } + } + // Deserialize `FromOwner` param: + { + err := decoder.Decode(&inst.FromOwner) + if err != nil { + return err + } + } + return nil +} + +// NewTransferWithSeedInstruction declares a new TransferWithSeed instruction with the provided parameters and accounts. +func NewTransferWithSeedInstruction( + // Parameters: + lamports uint64, + from_seed string, + from_owner ag_solanago.PublicKey, + // Accounts: + fundingAccount ag_solanago.PublicKey, + baseForFundingAccount ag_solanago.PublicKey, + recipientAccount ag_solanago.PublicKey) *TransferWithSeed { + return NewTransferWithSeedInstructionBuilder(). + SetLamports(lamports). + SetFromSeed(from_seed). + SetFromOwner(from_owner). + SetFundingAccount(fundingAccount). + SetBaseForFundingAccount(baseForFundingAccount). + SetRecipientAccount(recipientAccount) } diff --git a/programs/system/TransferWithSeed_test.go b/programs/system/TransferWithSeed_test.go new file mode 100644 index 00000000..da7f96dc --- /dev/null +++ b/programs/system/TransferWithSeed_test.go @@ -0,0 +1,31 @@ +package system + +import ( + "bytes" + ag_gofuzz "github.com/google/gofuzz" + ag_require "github.com/stretchr/testify/require" + "strconv" + "testing" +) + +func TestEncodeDecode_TransferWithSeed(t *testing.T) { + fu := ag_gofuzz.New().NilChance(0) + for i := 0; i < 1; i++ { + t.Run("TransferWithSeed"+strconv.Itoa(i), func(t *testing.T) { + { + params := new(TransferWithSeed) + fu.Fuzz(params) + params.AccountMetaSlice = nil + buf := new(bytes.Buffer) + err := encodeT(*params, buf) + ag_require.NoError(t, err) + // + got := new(TransferWithSeed) + err = decodeT(got, buf.Bytes()) + got.AccountMetaSlice = nil + ag_require.NoError(t, err) + ag_require.Equal(t, params, got) + } + }) + } +} diff --git a/programs/system/Transfer_test.go b/programs/system/Transfer_test.go new file mode 100644 index 00000000..3ae89055 --- /dev/null +++ b/programs/system/Transfer_test.go @@ -0,0 +1,31 @@ +package system + +import ( + "bytes" + ag_gofuzz "github.com/google/gofuzz" + ag_require "github.com/stretchr/testify/require" + "strconv" + "testing" +) + +func TestEncodeDecode_Transfer(t *testing.T) { + fu := ag_gofuzz.New().NilChance(0) + for i := 0; i < 1; i++ { + t.Run("Transfer"+strconv.Itoa(i), func(t *testing.T) { + { + params := new(Transfer) + fu.Fuzz(params) + params.AccountMetaSlice = nil + buf := new(bytes.Buffer) + err := encodeT(*params, buf) + ag_require.NoError(t, err) + // + got := new(Transfer) + err = decodeT(got, buf.Bytes()) + got.AccountMetaSlice = nil + ag_require.NoError(t, err) + ag_require.Equal(t, params, got) + } + }) + } +} diff --git a/programs/system/WithdrawNonceAccount.go b/programs/system/WithdrawNonceAccount.go index 6c2cecde..26b7f1b1 100644 --- a/programs/system/WithdrawNonceAccount.go +++ b/programs/system/WithdrawNonceAccount.go @@ -1,44 +1,199 @@ package system import ( - bin "github.com/dfuse-io/binary" - solana "github.com/gagliardetto/solana-go" + "encoding/binary" + "errors" + "fmt" + ag_binary "github.com/dfuse-io/binary" + ag_solanago "github.com/gagliardetto/solana-go" + ag_format "github.com/gagliardetto/solana-go/text/format" + ag_treeout "github.com/gagliardetto/treeout" ) -func NewWithdrawNonceAccountInstruction( - lamports uint64, +// Withdraw funds from a nonce account +type WithdrawNonceAccount struct { + // The u64 parameter is the lamports to withdraw, which must leave the account balance above the rent exempt reserve or at zero. + Lamports *uint64 - nonceAccount solana.PublicKey, - recipientAccount solana.PublicKey, - nonceAuthority solana.PublicKey, -) *Instruction { - return &Instruction{ - BaseVariant: bin.BaseVariant{ - - TypeID: Instruction_WithdrawNonceAccount, - - Impl: &WithdrawNonceAccount{ - Lamports: bin.Uint64(lamports), - AccountMetaSlice: []*solana.AccountMeta{ - solana.Meta(nonceAccount).WRITE(), - solana.Meta(recipientAccount).WRITE(), - solana.Meta(solana.SysVarRecentBlockHashesPubkey), - solana.Meta(solana.SysVarRentPubkey), - solana.Meta(nonceAuthority).SIGNER(), - }, - }, - }, + // [0] = [WRITE] NonceAccount + // ··········· Nonce account + // + // [1] = [WRITE] RecipientAccount + // ··········· Recipient account + // + // [2] = [] $(SysVarRecentBlockHashesPubkey) + // ··········· RecentBlockhashes sysvar + // + // [3] = [] $(SysVarRentPubkey) + // ··········· Rent sysvar + // + // [4] = [SIGNER] NonceAuthorityAccount + // ··········· Nonce authority + ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"` +} + +// NewWithdrawNonceAccountInstructionBuilder creates a new `WithdrawNonceAccount` instruction builder. +func NewWithdrawNonceAccountInstructionBuilder() *WithdrawNonceAccount { + nd := &WithdrawNonceAccount{ + AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 5), } + nd.AccountMetaSlice[2] = ag_solanago.Meta(ag_solanago.SysVarRecentBlockHashesPubkey) + nd.AccountMetaSlice[3] = ag_solanago.Meta(ag_solanago.SysVarRentPubkey) + return nd } -type WithdrawNonceAccount struct { - // The u64 parameter is the lamports to withdraw, which must leave the account balance above the rent exempt reserve or at zero. - Lamports bin.Uint64 - - // [0] = [WRITE] Nonce account. - // [1] = [WRITE] Recipient account. - // [2] = [] RecentBlockhashes sysvar. - // [3] = [] Rent sysvar. - // [4] = [SIGNER] Nonce authority. - solana.AccountMetaSlice `bin:"-"` +// The u64 parameter is the lamports to withdraw, which must leave the account balance above the rent exempt reserve or at zero. +func (inst *WithdrawNonceAccount) SetLamports(lamports uint64) *WithdrawNonceAccount { + inst.Lamports = &lamports + return inst +} + +// Nonce account +func (inst *WithdrawNonceAccount) SetNonceAccount(nonceAccount ag_solanago.PublicKey) *WithdrawNonceAccount { + inst.AccountMetaSlice[0] = ag_solanago.Meta(nonceAccount).WRITE() + return inst +} + +func (inst *WithdrawNonceAccount) GetNonceAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice[0] +} + +// Recipient account +func (inst *WithdrawNonceAccount) SetRecipientAccount(recipientAccount ag_solanago.PublicKey) *WithdrawNonceAccount { + inst.AccountMetaSlice[1] = ag_solanago.Meta(recipientAccount).WRITE() + return inst +} + +func (inst *WithdrawNonceAccount) GetRecipientAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice[1] +} + +// RecentBlockhashes sysvar +func (inst *WithdrawNonceAccount) SetSysVarRecentBlockHashesPubkeyAccount(SysVarRecentBlockHashesPubkey ag_solanago.PublicKey) *WithdrawNonceAccount { + inst.AccountMetaSlice[2] = ag_solanago.Meta(SysVarRecentBlockHashesPubkey) + return inst +} + +func (inst *WithdrawNonceAccount) GetSysVarRecentBlockHashesPubkeyAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice[2] +} + +// Rent sysvar +func (inst *WithdrawNonceAccount) SetSysVarRentPubkeyAccount(SysVarRentPubkey ag_solanago.PublicKey) *WithdrawNonceAccount { + inst.AccountMetaSlice[3] = ag_solanago.Meta(SysVarRentPubkey) + return inst +} + +func (inst *WithdrawNonceAccount) GetSysVarRentPubkeyAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice[3] +} + +// Nonce authority +func (inst *WithdrawNonceAccount) SetNonceAuthorityAccount(nonceAuthorityAccount ag_solanago.PublicKey) *WithdrawNonceAccount { + inst.AccountMetaSlice[4] = ag_solanago.Meta(nonceAuthorityAccount).SIGNER() + return inst +} + +func (inst *WithdrawNonceAccount) GetNonceAuthorityAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice[4] +} + +func (inst WithdrawNonceAccount) Build() *Instruction { + return &Instruction{BaseVariant: ag_binary.BaseVariant{ + Impl: inst, + TypeID: ag_binary.TypeIDFromUint32(Instruction_WithdrawNonceAccount, binary.LittleEndian), + }} +} + +// ValidateAndBuild validates the instruction parameters and accounts; +// if there is a validation error, it returns the error. +// Otherwise, it builds and returns the instruction. +func (inst WithdrawNonceAccount) ValidateAndBuild() (*Instruction, error) { + if err := inst.Validate(); err != nil { + return nil, err + } + return inst.Build(), nil +} + +func (inst *WithdrawNonceAccount) Validate() error { + // Check whether all (required) parameters are set: + { + 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 *WithdrawNonceAccount) EncodeToTree(parent ag_treeout.Branches) { + parent.Child(ag_format.Program(ProgramName, ProgramID)). + // + ParentFunc(func(programBranch ag_treeout.Branches) { + programBranch.Child(ag_format.Instruction("WithdrawNonceAccount")). + // + ParentFunc(func(instructionBranch ag_treeout.Branches) { + + // Parameters of the instruction: + instructionBranch.Child("Params").ParentFunc(func(paramsBranch ag_treeout.Branches) { + paramsBranch.Child(ag_format.Param("Lamports", *inst.Lamports)) + }) + + // Accounts of the instruction: + instructionBranch.Child("Accounts").ParentFunc(func(accountsBranch ag_treeout.Branches) { + accountsBranch.Child(ag_format.Meta("NonceAccount", inst.AccountMetaSlice[0])) + accountsBranch.Child(ag_format.Meta("RecipientAccount", inst.AccountMetaSlice[1])) + accountsBranch.Child(ag_format.Meta("$(SysVarRecentBlockHashesPubkey)", inst.AccountMetaSlice[2])) + accountsBranch.Child(ag_format.Meta("$(SysVarRentPubkey)", inst.AccountMetaSlice[3])) + accountsBranch.Child(ag_format.Meta("NonceAuthorityAccount", inst.AccountMetaSlice[4])) + }) + }) + }) +} + +func (inst WithdrawNonceAccount) MarshalWithEncoder(encoder *ag_binary.Encoder) error { + // Serialize `Lamports` param: + { + err := encoder.Encode(*inst.Lamports) + if err != nil { + return err + } + } + return nil +} + +func (inst *WithdrawNonceAccount) UnmarshalWithDecoder(decoder *ag_binary.Decoder) error { + // Deserialize `Lamports` param: + { + err := decoder.Decode(&inst.Lamports) + if err != nil { + return err + } + } + return nil +} + +// NewWithdrawNonceAccountInstruction declares a new WithdrawNonceAccount instruction with the provided parameters and accounts. +func NewWithdrawNonceAccountInstruction( + // Parameters: + lamports uint64, + // Accounts: + nonceAccount ag_solanago.PublicKey, + recipientAccount ag_solanago.PublicKey, + SysVarRecentBlockHashesPubkey ag_solanago.PublicKey, + SysVarRentPubkey ag_solanago.PublicKey, + nonceAuthorityAccount ag_solanago.PublicKey) *WithdrawNonceAccount { + return NewWithdrawNonceAccountInstructionBuilder(). + SetLamports(lamports). + SetNonceAccount(nonceAccount). + SetRecipientAccount(recipientAccount). + SetSysVarRecentBlockHashesPubkeyAccount(SysVarRecentBlockHashesPubkey). + SetSysVarRentPubkeyAccount(SysVarRentPubkey). + SetNonceAuthorityAccount(nonceAuthorityAccount) } diff --git a/programs/system/WithdrawNonceAccount_test.go b/programs/system/WithdrawNonceAccount_test.go new file mode 100644 index 00000000..0ec4aac9 --- /dev/null +++ b/programs/system/WithdrawNonceAccount_test.go @@ -0,0 +1,31 @@ +package system + +import ( + "bytes" + ag_gofuzz "github.com/google/gofuzz" + ag_require "github.com/stretchr/testify/require" + "strconv" + "testing" +) + +func TestEncodeDecode_WithdrawNonceAccount(t *testing.T) { + fu := ag_gofuzz.New().NilChance(0) + for i := 0; i < 1; i++ { + t.Run("WithdrawNonceAccount"+strconv.Itoa(i), func(t *testing.T) { + { + params := new(WithdrawNonceAccount) + fu.Fuzz(params) + params.AccountMetaSlice = nil + buf := new(bytes.Buffer) + err := encodeT(*params, buf) + ag_require.NoError(t, err) + // + got := new(WithdrawNonceAccount) + err = decodeT(got, buf.Bytes()) + got.AccountMetaSlice = nil + ag_require.NoError(t, err) + ag_require.Equal(t, params, got) + } + }) + } +} diff --git a/programs/system/accounts.go b/programs/system/accounts.go new file mode 100644 index 00000000..9b140a34 --- /dev/null +++ b/programs/system/accounts.go @@ -0,0 +1 @@ +package system diff --git a/programs/system/instructions.go b/programs/system/instructions.go index 0e4a81cf..c087326c 100644 --- a/programs/system/instructions.go +++ b/programs/system/instructions.go @@ -1,16 +1,5 @@ -// Copyright 2020 dfuse Platform Inc. -// -// 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. +// Create new accounts, allocate account data, assign accounts to owning programs, +// transfer lamports from System Program owned accounts and pay transacation fees. package system @@ -18,62 +7,67 @@ import ( "bytes" "encoding/binary" "fmt" - - "github.com/davecgh/go-spew/spew" - bin "github.com/dfuse-io/binary" - solana "github.com/gagliardetto/solana-go" - "github.com/gagliardetto/solana-go/text" - "github.com/gagliardetto/treeout" + ag_spew "github.com/davecgh/go-spew/spew" + ag_binary "github.com/dfuse-io/binary" + ag_solanago "github.com/gagliardetto/solana-go" + ag_text "github.com/gagliardetto/solana-go/text" + ag_treeout "github.com/gagliardetto/treeout" ) -var ( - ProgramID = solana.MustPublicKeyFromBase58("11111111111111111111111111111111") -) +var ProgramID ag_solanago.PublicKey = ag_solanago.MustPublicKeyFromBase58("11111111111111111111111111111111") + +func SetProgramID(pubkey ag_solanago.PublicKey) { + ProgramID = pubkey + ag_solanago.RegisterInstructionDecoder(ProgramID, registryDecodeInstruction) +} const ProgramName = "System" func init() { - solana.RegisterInstructionDecoder(ProgramID, registryDecodeInstruction) + if !ProgramID.IsZero() { + ag_solanago.RegisterInstructionDecoder(ProgramID, registryDecodeInstruction) + } } const ( - // Create a new account. + // Create a new account Instruction_CreateAccount uint32 = iota - // Assign account to a program. + // Assign account to a program Instruction_Assign - // Transfer lamports. + // Transfer lamports Instruction_Transfer - // Create a new account at an address derived from a base pubkey and a seed. + // Create a new account at an address derived from a base pubkey and a seed Instruction_CreateAccountWithSeed - // Consumes a stored nonce, replacing it with a successor. + // Consumes a stored nonce, replacing it with a successor Instruction_AdvanceNonceAccount - // Withdraw funds from a nonce account. + // Withdraw funds from a nonce account Instruction_WithdrawNonceAccount - // Drive state of Uninitalized nonce account to Initialized, setting the nonce value. + // Drive state of Uninitalized nonce account to Initialized, setting the nonce value Instruction_InitializeNonceAccount - // Change the entity authorized to execute nonce instructions on the account. + // Change the entity authorized to execute nonce instructions on the account Instruction_AuthorizeNonceAccount - // Allocate space in a (possibly new) account without funding. + // Allocate space in a (possibly new) account without funding Instruction_Allocate - // Allocate space for and assign an account at an address derived from a base public key and a seed. + // Allocate space for and assign an account at an address derived from a base public key and a seed Instruction_AllocateWithSeed - // Assign account to a program based on a seed. + // Assign account to a program based on a seed Instruction_AssignWithSeed - // Transfer lamports from a derived address. + // Transfer lamports from a derived address Instruction_TransferWithSeed ) +// InstructionIDToName returns the name of the instruction given its ID. func InstructionIDToName(id uint32) string { switch id { case Instruction_CreateAccount: @@ -106,103 +100,92 @@ func InstructionIDToName(id uint32) string { } type Instruction struct { - bin.BaseVariant + ag_binary.BaseVariant } -var _ bin.EncoderDecoder = &Instruction{} - -var ( - // TODO: each instruction must be here: - _ solana.AccountsGettable = &CreateAccount{} - _ solana.AccountsSettable = &CreateAccount{} - - _ solana.AccountsGettable = &Assign{} - _ solana.AccountsSettable = &Assign{} - - _ solana.AccountsGettable = &Transfer{} - _ solana.AccountsSettable = &Transfer{} - - _ solana.AccountsGettable = &CreateAccountWithSeed{} - _ solana.AccountsSettable = &CreateAccountWithSeed{} - - _ solana.AccountsGettable = &AdvanceNonceAccount{} - _ solana.AccountsSettable = &AdvanceNonceAccount{} - - _ solana.AccountsGettable = &WithdrawNonceAccount{} - _ solana.AccountsSettable = &WithdrawNonceAccount{} - - _ solana.AccountsGettable = &InitializeNonceAccount{} - _ solana.AccountsSettable = &InitializeNonceAccount{} - - _ solana.AccountsGettable = &AuthorizeNonceAccount{} - _ solana.AccountsSettable = &AuthorizeNonceAccount{} - - _ solana.AccountsGettable = &Allocate{} - _ solana.AccountsSettable = &Allocate{} - - _ solana.AccountsGettable = &AllocateWithSeed{} - _ solana.AccountsSettable = &AllocateWithSeed{} - - _ solana.AccountsGettable = &AssignWithSeed{} - _ solana.AccountsSettable = &AssignWithSeed{} +func (inst *Instruction) EncodeToTree(parent ag_treeout.Branches) { + if enToTree, ok := inst.Impl.(ag_text.EncodableToTree); ok { + enToTree.EncodeToTree(parent) + } else { + parent.Child(ag_spew.Sdump(inst)) + } +} - _ solana.AccountsGettable = &TransferWithSeed{} - _ solana.AccountsSettable = &TransferWithSeed{} +var InstructionImplDef = ag_binary.NewVariantDefinition( + ag_binary.Uint32TypeIDEncoding, + []ag_binary.VariantType{ + { + "CreateAccount", (*CreateAccount)(nil), + }, + { + "Assign", (*Assign)(nil), + }, + { + "Transfer", (*Transfer)(nil), + }, + { + "CreateAccountWithSeed", (*CreateAccountWithSeed)(nil), + }, + { + "AdvanceNonceAccount", (*AdvanceNonceAccount)(nil), + }, + { + "WithdrawNonceAccount", (*WithdrawNonceAccount)(nil), + }, + { + "InitializeNonceAccount", (*InitializeNonceAccount)(nil), + }, + { + "AuthorizeNonceAccount", (*AuthorizeNonceAccount)(nil), + }, + { + "Allocate", (*Allocate)(nil), + }, + { + "AllocateWithSeed", (*AllocateWithSeed)(nil), + }, + { + "AssignWithSeed", (*AssignWithSeed)(nil), + }, + { + "TransferWithSeed", (*TransferWithSeed)(nil), + }, + }, ) -func (ins *Instruction) Accounts() (out []*solana.AccountMeta) { - return ins.Impl.(solana.AccountsGettable).GetAccounts() +func (inst *Instruction) ProgramID() ag_solanago.PublicKey { + return ProgramID } -// InstructionImplDef is used for deciding binary, -// encoding and decoding json. -var InstructionImplDef = bin.NewVariantDefinition( - bin.Uint32TypeIDEncoding, - []bin.VariantType{ - // TODO: - {"create_account", (*CreateAccount)(nil)}, - {"assign", (*Assign)(nil)}, - {"transfer", (*Transfer)(nil)}, - {"create_account_with_seed", (*CreateAccountWithSeed)(nil)}, - {"advance_nonce_account", (*AdvanceNonceAccount)(nil)}, - {"withdraw_nonce_account", (*WithdrawNonceAccount)(nil)}, - {"initialize_nonce_account", (*InitializeNonceAccount)(nil)}, - {"authorize_nonce_account", (*AuthorizeNonceAccount)(nil)}, - {"allocate", (*Allocate)(nil)}, - {"allocate_with_seed", (*AllocateWithSeed)(nil)}, - {"assign_with_seed", (*AssignWithSeed)(nil)}, - {"transfer_with_seed", (*TransferWithSeed)(nil)}, - }) - -func (i *Instruction) ProgramID() solana.PublicKey { - return ProgramID +func (inst *Instruction) Accounts() (out []*ag_solanago.AccountMeta) { + return inst.Impl.(ag_solanago.AccountsGettable).GetAccounts() } -func (i *Instruction) Data() ([]byte, error) { +func (inst *Instruction) Data() ([]byte, error) { buf := new(bytes.Buffer) - if err := bin.NewBinEncoder(buf).Encode(i); err != nil { + if err := ag_binary.NewBinEncoder(buf).Encode(inst); err != nil { return nil, fmt.Errorf("unable to encode instruction: %w", err) } return buf.Bytes(), nil } -func (i *Instruction) TextEncode(encoder *text.Encoder, option *text.Option) error { - return encoder.Encode(i.Impl, option) +func (inst *Instruction) TextEncode(encoder *ag_text.Encoder, option *ag_text.Option) error { + return encoder.Encode(inst.Impl, option) } -func (i *Instruction) UnmarshalWithDecoder(decoder *bin.Decoder) error { - return i.BaseVariant.UnmarshalBinaryVariant(decoder, InstructionImplDef) +func (inst *Instruction) UnmarshalWithDecoder(decoder *ag_binary.Decoder) error { + return inst.BaseVariant.UnmarshalBinaryVariant(decoder, InstructionImplDef) } -func (i *Instruction) MarshalWithEncoder(encoder *bin.Encoder) error { - err := encoder.WriteUint32(i.TypeID, binary.LittleEndian) +func (inst *Instruction) MarshalWithEncoder(encoder *ag_binary.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(i.Impl) + return encoder.Encode(inst.Impl) } -func registryDecodeInstruction(accounts []*solana.AccountMeta, data []byte) (interface{}, error) { +func registryDecodeInstruction(accounts []*ag_solanago.AccountMeta, data []byte) (interface{}, error) { inst, err := DecodeInstruction(accounts, data) if err != nil { return nil, err @@ -210,26 +193,16 @@ func registryDecodeInstruction(accounts []*solana.AccountMeta, data []byte) (int return inst, nil } -func DecodeInstruction(accounts []*solana.AccountMeta, data []byte) (*Instruction, error) { - var inst *Instruction - if err := bin.NewBinDecoder(data).Decode(&inst); err != nil { +func DecodeInstruction(accounts []*ag_solanago.AccountMeta, data []byte) (*Instruction, error) { + inst := new(Instruction) + if err := ag_binary.NewBinDecoder(data).Decode(inst); err != nil { return nil, fmt.Errorf("unable to decode instruction: %w", err) } - - if v, ok := inst.Impl.(solana.AccountsSettable); ok { + if v, ok := inst.Impl.(ag_solanago.AccountsSettable); ok { err := v.SetAccounts(accounts) if err != nil { return nil, fmt.Errorf("unable to set accounts for instruction: %w", err) } } - return inst, nil } - -func (inst *Instruction) EncodeToTree(parent treeout.Branches) { - if enToTree, ok := inst.Impl.(text.EncodableToTree); ok { - enToTree.EncodeToTree(parent) - } else { - parent.Child(spew.Sdump(inst)) - } -} diff --git a/programs/system/instructions_test.go b/programs/system/instructions_test.go deleted file mode 100644 index d3944173..00000000 --- a/programs/system/instructions_test.go +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright 2020 dfuse Platform Inc. -// -// 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 system - -import ( - "testing" -) - -func TestSystemInstructions(t *testing.T) { - t.Skip() -} diff --git a/programs/system/testing_utils.go b/programs/system/testing_utils.go new file mode 100644 index 00000000..22e6ad8b --- /dev/null +++ b/programs/system/testing_utils.go @@ -0,0 +1,18 @@ +package system + +import ( + "bytes" + "fmt" + ag_binary "github.com/dfuse-io/binary" +) + +func encodeT(data interface{}, buf *bytes.Buffer) error { + if err := ag_binary.NewBinEncoder(buf).Encode(data); err != nil { + return fmt.Errorf("unable to encode instruction: %w", err) + } + return nil +} + +func decodeT(dst interface{}, data []byte) error { + return ag_binary.NewBinDecoder(data).Decode(dst) +} diff --git a/programs/system/types.go b/programs/system/types.go new file mode 100644 index 00000000..9b140a34 --- /dev/null +++ b/programs/system/types.go @@ -0,0 +1 @@ +package system diff --git a/programs/token/instructions.go b/programs/token/instructions.go index d4f6eba5..ae34e5f5 100644 --- a/programs/token/instructions.go +++ b/programs/token/instructions.go @@ -83,7 +83,7 @@ func (i *Instruction) UnmarshalWithDecoder(decoder *bin.Decoder) (err error) { } func (i *Instruction) MarshalWithEncoder(encoder *bin.Encoder) error { - err := encoder.WriteUint8(uint8(i.TypeID)) + err := encoder.WriteUint8(i.TypeID.Uint8()) if err != nil { return fmt.Errorf("unable to write variant type: %w", err) } diff --git a/programs/tokenregistry/instruction.go b/programs/tokenregistry/instruction.go index fb40fc2f..bddfb1a3 100644 --- a/programs/tokenregistry/instruction.go +++ b/programs/tokenregistry/instruction.go @@ -42,7 +42,7 @@ func DecodeInstruction(accounts []*solana.AccountMeta, data []byte) (*Instructio func NewRegisterTokenInstruction(logo Logo, name Name, symbol Symbol, website Website, tokenMetaKey, ownerKey, tokenKey solana.PublicKey) *Instruction { return &Instruction{ BaseVariant: bin.BaseVariant{ - TypeID: 0, + TypeID: bin.TypeIDFromUint32(0, bin.LE()), Impl: &RegisterToken{ Logo: logo, Name: name, @@ -66,7 +66,7 @@ var _ bin.EncoderDecoder = &Instruction{} func (i *Instruction) Accounts() (out []*solana.AccountMeta) { switch i.TypeID { - case 0: + case bin.TypeIDFromUint32(0, bin.LE()): accounts := i.Impl.(*RegisterToken).Accounts out = []*solana.AccountMeta{accounts.TokenMeta, accounts.Owner, accounts.Token} } @@ -98,7 +98,7 @@ func (i *Instruction) UnmarshalWithDecoder(decoder *bin.Decoder) (err error) { } func (i *Instruction) MarshalWithEncoder(encoder *bin.Encoder) error { - err := encoder.WriteUint32(i.TypeID, binary.LittleEndian) + err := encoder.WriteUint32(i.TypeID.Uint32(), binary.LittleEndian) if err != nil { return fmt.Errorf("unable to write variant type: %w", err) } From deed41d82fec61aae81628b7c9ccf42e703c0722 Mon Sep 17 00:00:00 2001 From: Slavomir Date: Tue, 31 Aug 2021 22:23:43 +0200 Subject: [PATCH 33/34] Add address derivation funcs --- constants.go | 3 - go.mod | 1 + go.sum | 2 + keys.go | 174 +++++++++++++++++++++++++++++++++++++++++++++++-- keys_test.go | 103 +++++++++++++++++++++++++++++ program_ids.go | 53 +++++++++++++++ sysvar.go | 6 +- transaction.go | 30 +-------- 8 files changed, 334 insertions(+), 38 deletions(-) create mode 100644 program_ids.go diff --git a/constants.go b/constants.go index 36ff01ae..8c97b949 100644 --- a/constants.go +++ b/constants.go @@ -3,7 +3,4 @@ package solana const ( // There are 1-billion lamports in one SOL. LAMPORTS_PER_SOL uint64 = 1000000000 - - // Maximum length of derived pubkey seed. - MAX_SEED_LENGTH uint8 = 32 ) diff --git a/go.mod b/go.mod index 6112fd9b..a93b60d0 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ retract ( require ( contrib.go.opencensus.io/exporter/stackdriver v0.13.4 // indirect + filippo.io/edwards25519 v1.0.0-rc.1 github.com/AlekSi/pointer v1.1.0 github.com/GeertJohan/go.rice v1.0.0 github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59 diff --git a/go.sum b/go.sum index 88be28f4..803f1ae7 100644 --- a/go.sum +++ b/go.sum @@ -27,6 +27,8 @@ contrib.go.opencensus.io/exporter/stackdriver v0.12.6/go.mod h1:8x999/OcIPy5ivx/ contrib.go.opencensus.io/exporter/stackdriver v0.13.4 h1:ksUxwH3OD5sxkjzEqGxNTl+Xjsmu3BnC/300MhSVTSc= contrib.go.opencensus.io/exporter/stackdriver v0.13.4/go.mod h1:aXENhDJ1Y4lIg4EUaVTwzvYETVNZk10Pu26tevFKLUc= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +filippo.io/edwards25519 v1.0.0-rc.1 h1:m0VOOB23frXZvAOK44usCgLWvtsxIoMCTBGJZlpmGfU= +filippo.io/edwards25519 v1.0.0-rc.1/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns= github.com/AlekSi/pointer v1.1.0 h1:SSDMPcXD9jSl8FPy9cRzoRaMJtm9g9ggGTxecRUbQoI= github.com/AlekSi/pointer v1.1.0/go.mod h1:y7BvfRI3wXPWKXEBhU71nbnIEEZX0QTSB2Bj48UJIZE= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= diff --git a/keys.go b/keys.go index 460f671e..9c3b83e9 100644 --- a/keys.go +++ b/keys.go @@ -4,9 +4,13 @@ import ( "crypto" "crypto/ed25519" crypto_rand "crypto/rand" + "crypto/sha256" + "errors" "fmt" "io/ioutil" + "math" + "filippo.io/edwards25519" "github.com/mr-tron/base58" ) @@ -80,7 +84,7 @@ func (k PrivateKey) PublicKey() PublicKey { return publicKey } -type PublicKey [32]byte +type PublicKey [PublicKeyLength]byte func PublicKeyFromBytes(in []byte) (out PublicKey) { byteCount := len(in) @@ -88,7 +92,7 @@ func PublicKeyFromBytes(in []byte) (out PublicKey) { return } - max := 32 + max := PublicKeyLength if byteCount < max { max = byteCount } @@ -111,8 +115,8 @@ func PublicKeyFromBase58(in string) (out PublicKey, err error) { return out, fmt.Errorf("decode: %w", err) } - if len(val) != 32 { - return out, fmt.Errorf("invalid length, expected 32, got %d", len(val)) + if len(val) != PublicKeyLength { + return out, fmt.Errorf("invalid length, expected %v, got %d", PublicKeyLength, len(val)) } copy(out[:], val) @@ -172,3 +176,165 @@ func (p PublicKey) IsZero() bool { func (p PublicKey) String() string { return base58.Encode(p[:]) } + +type PublicKeySlice []PublicKey + +// UniqueAppend appends the provided pubkey only if it is not +// already present in the slice. +// Returns true when the provided pubkey wasn't already present. +func (slice *PublicKeySlice) UniqueAppend(pubkey PublicKey) bool { + if !slice.Has(pubkey) { + slice.Append(pubkey) + return true + } + return false +} + +func (slice *PublicKeySlice) Append(pubkey PublicKey) { + *slice = append(*slice, pubkey) +} + +func (slice PublicKeySlice) Has(pubkey PublicKey) bool { + for _, key := range slice { + if key.Equals(pubkey) { + return true + } + } + return false +} + +var nativeProgramIDs = PublicKeySlice{ + BPFLoaderProgramID, + BPFLoaderDeprecatedProgramID, + FeatureProgramID, + ConfigProgramID, + StakeProgramID, + VoteProgramID, + Secp256k1ProgramID, + SystemProgramID, + SysVarClockPubkey, + SysVarEpochSchedulePubkey, + SysVarFeesPubkey, + SysVarInstructionsPubkey, + SysVarRecentBlockHashesPubkey, + SysVarRentPubkey, + SysVarRewardsPubkey, + SysVarSlotHashesPubkey, + SysVarSlotHistoryPubkey, + SysVarStakeHistoryPubkey, +} + +// https://github.com/solana-labs/solana/blob/216983c50e0a618facc39aa07472ba6d23f1b33a/sdk/program/src/pubkey.rs#L372 +func isNativeProgramID(key PublicKey) bool { + return nativeProgramIDs.Has(key) +} + +const ( + /// Number of bytes in a pubkey. + PublicKeyLength = 32 + // Maximum length of derived pubkey seed. + MaxSeedLength = 32 + // Maximum number of seeds. + MaxSeeds = 16 + // // Maximum string length of a base58 encoded pubkey. + // MaxBase58Length = 44 +) + +// Ported from https://github.com/solana-labs/solana/blob/216983c50e0a618facc39aa07472ba6d23f1b33a/sdk/program/src/pubkey.rs#L159 +func CreateWithSeed(base PublicKey, seed string, owner PublicKey) (PublicKey, error) { + if len(seed) > MaxSeedLength { + return PublicKey{}, errors.New("Max seed length exceeded") + } + + // let owner = owner.as_ref(); + // if owner.len() >= PDA_MARKER.len() { + // let slice = &owner[owner.len() - PDA_MARKER.len()..]; + // if slice == PDA_MARKER { + // return Err(PubkeyError::IllegalOwner); + // } + // } + + b := make([]byte, 0, 64+len(seed)) + b = append(b, base[:]...) + b = append(b, seed[:]...) + b = append(b, owner[:]...) + hash := sha256.Sum256(b) + return PublicKeyFromBytes(hash[:]), nil +} + +const PDA_MARKER = "ProgramDerivedAddress" + +// Create a program address. +// Ported from https://github.com/solana-labs/solana/blob/216983c50e0a618facc39aa07472ba6d23f1b33a/sdk/program/src/pubkey.rs#L204 +func CreateProgramAddress(seeds [][]byte, programID PublicKey) (PublicKey, error) { + if len(seeds) > MaxSeeds { + return PublicKey{}, errors.New("Max seed length exceeded") + } + + for _, seed := range seeds { + if len(seed) > MaxSeedLength { + return PublicKey{}, errors.New("Max seed length exceeded") + } + } + + if isNativeProgramID(programID) { + return PublicKey{}, fmt.Errorf("illegal owner: %s is a native program", programID) + } + + buf := []byte{} + for _, seed := range seeds { + buf = append(buf, seed...) + } + + buf = append(buf, programID[:]...) + buf = append(buf, []byte(PDA_MARKER)...) + hash := sha256.Sum256(buf) + + _, err := new(edwards25519.Point).SetBytes(hash[:]) + isOnCurve := err == nil + if isOnCurve { + return PublicKey{}, errors.New("invalid seeds; address must fall off the curve") + } + + return PublicKeyFromBytes(hash[:]), nil +} + +// Find a valid program address and its corresponding bump seed. +func FindProgramAddress(seed [][]byte, programID PublicKey) (PublicKey, uint8, error) { + var address PublicKey + var err error + bumpSeed := uint8(math.MaxUint8) + for bumpSeed != 0 { + address, err = CreateProgramAddress(append(seed, []byte{byte(bumpSeed)}), programID) + if err == nil { + return address, bumpSeed, nil + } + bumpSeed-- + } + return PublicKey{}, bumpSeed, errors.New("unable to find a valid program address") +} + +func FindAssociatedTokenAddress( + walletAddress PublicKey, + splTokenMintAddress PublicKey, +) (PublicKey, uint8, error) { + return findAssociatedTokenAddressAndBumpSeed( + walletAddress, + splTokenMintAddress, + SPLAssociatedTokenAccountProgramID, + ) +} + +func findAssociatedTokenAddressAndBumpSeed( + walletAddress PublicKey, + splTokenMintAddress PublicKey, + programID PublicKey, +) (PublicKey, uint8, error) { + return FindProgramAddress([][]byte{ + walletAddress[:], + SPLTokenProgramID[:], + splTokenMintAddress[:], + }, + programID, + ) +} diff --git a/keys_test.go b/keys_test.go index 08120bf0..aa27c113 100644 --- a/keys_test.go +++ b/keys_test.go @@ -140,3 +140,106 @@ func TestPublicKey_MarshalText(t *testing.T) { payload, ) } + +func TestPublicKeySlice(t *testing.T) { + slice := make(PublicKeySlice, 0) + require.False(t, slice.Has(BPFLoaderProgramID)) + + slice.Append(BPFLoaderProgramID) + require.True(t, slice.Has(BPFLoaderProgramID)) + require.Len(t, slice, 1) + + slice.UniqueAppend(BPFLoaderProgramID) + require.Len(t, slice, 1) + slice.Append(ConfigProgramID) + require.Len(t, slice, 2) + require.True(t, slice.Has(ConfigProgramID)) +} + +func TestIsNativeProgramID(t *testing.T) { + require.True(t, isNativeProgramID(ConfigProgramID)) +} + +func TestCreateWithSeed(t *testing.T) { + { + got, err := CreateWithSeed(PublicKey{}, "limber chicken: 4/45", PublicKey{}) + require.NoError(t, err) + require.True(t, got.Equals(MustPublicKeyFromBase58("9h1HyLCW5dZnBVap8C5egQ9Z6pHyjsh5MNy83iPqqRuq"))) + } +} + +func TestCreateProgramAddress(t *testing.T) { + program_id := MustPublicKeyFromBase58("BPFLoaderUpgradeab1e11111111111111111111111") + public_key := MustPublicKeyFromBase58("SeedPubey1111111111111111111111111111111111") + + { + got, err := CreateProgramAddress([][]byte{ + {}, + {1}, + }, + program_id, + ) + require.NoError(t, err) + require.True(t, got.Equals(MustPublicKeyFromBase58("BwqrghZA2htAcqq8dzP1WDAhTXYTYWj7CHxF5j7TDBAe"))) + } + + { + got, err := CreateProgramAddress([][]byte{ + []byte("☉"), + {0}, + }, + program_id, + ) + require.NoError(t, err) + require.True(t, got.Equals(MustPublicKeyFromBase58("13yWmRpaTR4r5nAktwLqMpRNr28tnVUZw26rTvPSSB19"))) + } + + { + got, err := CreateProgramAddress([][]byte{ + []byte("Talking"), + []byte("Squirrels"), + }, + program_id, + ) + require.NoError(t, err) + require.True(t, got.Equals(MustPublicKeyFromBase58("2fnQrngrQT4SeLcdToJAD96phoEjNL2man2kfRLCASVk"))) + } + + { + got, err := CreateProgramAddress([][]byte{ + public_key[:], + {1}, + }, + program_id, + ) + require.NoError(t, err) + require.True(t, got.Equals(MustPublicKeyFromBase58("976ymqVnfE32QFe6NfGDctSvVa36LWnvYxhU6G2232YL"))) + } +} + +// https://github.com/solana-labs/solana/blob/216983c50e0a618facc39aa07472ba6d23f1b33a/sdk/program/src/pubkey.rs#L590 +func TestFindProgramAddress(t *testing.T) { + for i := 0; i < 1_000; i++ { + + program_id := NewAccount().PrivateKey.PublicKey() + address, bump_seed, err := FindProgramAddress( + [][]byte{ + []byte("Lil'"), + []byte("Bits"), + }, + program_id, + ) + require.NoError(t, err) + + got, err := CreateProgramAddress( + [][]byte{ + []byte("Lil'"), + []byte("Bits"), + []byte{bump_seed}, + }, + program_id, + ) + require.NoError(t, err) + require.Equal(t, address, got) + } +} diff --git a/program_ids.go b/program_ids.go new file mode 100644 index 00000000..a37a62fd --- /dev/null +++ b/program_ids.go @@ -0,0 +1,53 @@ +package solana + +var ( + // Create new accounts, allocate account data, assign accounts to owning programs, + // transfer lamports from System Program owned accounts and pay transacation fees. + SystemProgramID = MustPublicKeyFromBase58("11111111111111111111111111111111") + + // Add configuration data to the chain and the list of public keys that are permitted to modify it. + ConfigProgramID = MustPublicKeyFromBase58("Config1111111111111111111111111111111111111") + + // Create and manage accounts representing stake and rewards for delegations to validators. + StakeProgramID = MustPublicKeyFromBase58("Stake11111111111111111111111111111111111111") + + // Create and manage accounts that track validator voting state and rewards. + VoteProgramID = MustPublicKeyFromBase58("Vote111111111111111111111111111111111111111") + + BPFLoaderDeprecatedProgramID = MustPublicKeyFromBase58("BPFLoader1111111111111111111111111111111111") + // Deploys, upgrades, and executes programs on the chain. + BPFLoaderProgramID = MustPublicKeyFromBase58("BPFLoader2111111111111111111111111111111111") + BPFLoaderUpgradeableProgramID = MustPublicKeyFromBase58("BPFLoaderUpgradeab1e11111111111111111111111") + + // Verify secp256k1 public key recovery operations (ecrecover). + Secp256k1ProgramID = MustPublicKeyFromBase58("KeccakSecp256k11111111111111111111111111111") + + FeatureProgramID = MustPublicKeyFromBase58("Feature111111111111111111111111111111111111") +) + +// SPL: +var ( + // A Token program on the Solana blockchain. + // This program defines a common implementation for Fungible and Non Fungible tokens. + SPLTokenProgramID = MustPublicKeyFromBase58("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA") + + // A Uniswap-like exchange for the Token program on the Solana blockchain, + // implementing multiple automated market maker (AMM) curves. + SPLTokenSwapProgramID = MustPublicKeyFromBase58("SwaPpA9LAaLfeLi3a68M4DjnLqgtticKg6CnyNwgAC8") + SPLTokenSwapFeeOwner = MustPublicKeyFromBase58("HfoTxFR1Tm6kGmWgYWD6J7YHVy1UwqSULUGVLXkJqaKN") + + // A lending protocol for the Token program on the Solana blockchain inspired by Aave and Compound. + SPLTokenLendingProgramID = MustPublicKeyFromBase58("LendZqTs8gn5CTSJU1jWKhKuVpjJGom45nnwPb2AMTi") + + // This program defines the convention and provides the mechanism for mapping + // the user's wallet address to the associated token accounts they hold. + SPLAssociatedTokenAccountProgramID = MustPublicKeyFromBase58("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL") + + // The Memo program is a simple program that validates a string of UTF-8 encoded characters + // and verifies that any accounts provided are signers of the transaction. + // The program also logs the memo, as well as any verified signer addresses, + // to the transaction log, so that anyone can easily observe memos + // and know they were approved by zero or more addresses + // by inspecting the transaction log from a trusted provider. + SPLMemoProgramID = MustPublicKeyFromBase58("MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr") +) diff --git a/sysvar.go b/sysvar.go index 4815c479..d8706958 100644 --- a/sysvar.go +++ b/sysvar.go @@ -35,6 +35,9 @@ var ( // The Rent burn percentage is modified by manual feature activation. SysVarRentPubkey = MustPublicKeyFromBase58("SysvarRent111111111111111111111111111111111") + // + SysVarRewardsPubkey = MustPublicKeyFromBase58("SysvarRewards111111111111111111111111111111") + // The SlotHashes sysvar contains the most recent hashes of the slot's parent banks. // It is updated every slot. SysVarSlotHashesPubkey = MustPublicKeyFromBase58("SysvarS1otHashes111111111111111111111111111") @@ -45,7 +48,4 @@ 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") - - // - SysVarRewardsPubkey = MustPublicKeyFromBase58("SysvarRewards111111111111111111111111111111") ) diff --git a/transaction.go b/transaction.go index 192611ee..b07fe92a 100644 --- a/transaction.go +++ b/transaction.go @@ -36,32 +36,6 @@ func TransactionPayer(payer PublicKey) TransactionOption { return transactionOptionFunc(func(opts *transactionOptions) { opts.payer = payer }) } -type pubkeySlice []PublicKey - -// uniqueAppend appends the provided pubkey only if it is not -// already present in the slice. -// Returns true when the provided pubkey wasn't already present. -func (slice *pubkeySlice) uniqueAppend(pubkey PublicKey) bool { - if !slice.has(pubkey) { - slice.append(pubkey) - return true - } - return false -} - -func (slice *pubkeySlice) append(pubkey PublicKey) { - *slice = append(*slice, pubkey) -} - -func (slice *pubkeySlice) has(pubkey PublicKey) bool { - for _, key := range *slice { - if key.Equals(pubkey) { - return true - } - } - return false -} - var debugNewTransaction = false type TransactionBuilder struct { @@ -134,13 +108,13 @@ func NewTransaction(instructions []Instruction, recentBlockHash Hash, opts ...Tr } } - programIDs := make(pubkeySlice, 0) + programIDs := make(PublicKeySlice, 0) accounts := []*AccountMeta{} for _, instruction := range instructions { for _, key := range instruction.Accounts() { accounts = append(accounts, key) } - programIDs.uniqueAppend(instruction.ProgramID()) + programIDs.UniqueAppend(instruction.ProgramID()) } // Add programID to the account list From 44559caa2862e8ccaa34024cbddd9455d705d0a4 Mon Sep 17 00:00:00 2001 From: Slavomir Date: Tue, 31 Aug 2021 22:24:25 +0200 Subject: [PATCH 34/34] Tag v0.4.0 release --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 3b9d1d30..71d34372 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Solana SDK library for Go -[![GoDoc](https://pkg.go.dev/badge/github.com/gagliardetto/solana-go?status.svg)](https://pkg.go.dev/github.com/gagliardetto/solana-go@v0.3.6?tab=doc) +[![GoDoc](https://pkg.go.dev/badge/github.com/gagliardetto/solana-go?status.svg)](https://pkg.go.dev/github.com/gagliardetto/solana-go@v0.4.0?tab=doc) [![GitHub tag (latest SemVer pre-release)](https://img.shields.io/github/v/tag/gagliardetto/solana-go?include_prereleases&label=release-tag)](https://github.com/gagliardetto/solana-go/releases) [![Build Status](https://github.com/gagliardetto/solana-go/workflows/tests/badge.svg?branch=main)](https://github.com/gagliardetto/solana-go/actions?query=branch%3Amain) [![TODOs](https://badgen.net/https/api.tickgit.com/badgen/github.com/gagliardetto/solana-go/main)](https://www.tickgit.com/browse?repo=github.com/gagliardetto/solana-go&branch=main) @@ -110,7 +110,7 @@ More contracts to come. ## Current development status -There is currently **no stable release**. The SDK is actively developed and latest is `v0.3.6` which is an `alpha` release. +There is currently **no stable release**. The SDK is actively developed and latest is `v0.4.0` which is an `alpha` release. The RPC and WS client implementation is based on [this RPC spec](https://github.com/solana-labs/solana/blob/dff9c88193da142693cabebfcd3bf68fa8e8b873/docs/src/developing/clients/jsonrpc-api.md). @@ -124,7 +124,7 @@ The RPC and WS client implementation is based on [this RPC spec](https://github. ```bash $ cd my-project -$ go get github.com/gagliardetto/solana-go@latest +$ go get github.com/gagliardetto/solana-go@v0.4.0 ``` ## Examples