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 diff --git a/account.go b/account.go index 4b516164..ef5aec8c 100644 --- a/account.go +++ b/account.go @@ -34,8 +34,41 @@ func (a *Account) PublicKey() PublicKey { type AccountMeta struct { PublicKey PublicKey - IsSigner bool IsWritable bool + 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, + SIGNER bool, +) *AccountMeta { + return &AccountMeta{ + PublicKey: pubKey, + IsWritable: WRITE, + IsSigner: SIGNER, + } } func (a *AccountMeta) less(act *AccountMeta) bool { @@ -50,3 +83,29 @@ 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 +} + +// 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/account_test.go b/account_test.go index d393f090..6fa9f6dc 100644 --- a/account_test.go +++ b/account_test.go @@ -77,5 +77,66 @@ 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) +} + +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/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/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/constants.go b/constants.go index 7eea24b4..8c97b949 100644 --- a/constants.go +++ b/constants.go @@ -1,5 +1,6 @@ package solana const ( + // There are 1-billion lamports in one SOL. LAMPORTS_PER_SOL uint64 = 1000000000 ) diff --git a/go.mod b/go.mod index 38ae9693..a93b60d0 100644 --- a/go.mod +++ b/go.mod @@ -9,19 +9,22 @@ 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 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/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 @@ -45,5 +48,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.4.0 + +replace github.com/google/gofuzz => github.com/gagliardetto/gofuzz v1.2.1 diff --git a/go.sum b/go.sum index 8e033ce1..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= @@ -46,6 +48,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= @@ -72,8 +76,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 +89,12 @@ 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.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= 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= @@ -130,7 +138,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= @@ -490,6 +497,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/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/keys.go b/keys.go index 7e5b0c7f..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) @@ -152,8 +156,19 @@ func (p PublicKey) Equals(pb PublicKey) bool { return p == pb } +// ToPointer returns a pointer to the pubkey. +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. +// NOTE: the System Program public key is also zero. func (p PublicKey) IsZero() bool { return p == zeroPublicKey } @@ -161,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/programs/serum/instruction.go b/programs/serum/instruction.go index 6ab0490d..6cce7f65 100644 --- a/programs/serum/instruction.go +++ b/programs/serum/instruction.go @@ -24,11 +24,11 @@ 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) } - 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) @@ -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,13 +76,13 @@ 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) } - 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 d64d6e3f..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, @@ -63,7 +64,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..caf9bd00 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,11 +160,14 @@ 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 { - err := encoder.WriteUint32(s.TypeID, binary.LittleEndian) + +func (s *Slab) MarshalWithEncoder(encoder *bin.Encoder) error { + err := encoder.WriteUint32(s.TypeID.Uint32(), 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..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" @@ -22,7 +23,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 +40,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 +61,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 +78,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) @@ -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, }, @@ -210,7 +211,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/AdvanceNonceAccount.go b/programs/system/AdvanceNonceAccount.go new file mode 100644 index 00000000..aa741939 --- /dev/null +++ b/programs/system/AdvanceNonceAccount.go @@ -0,0 +1,131 @@ +package system + +import ( + "encoding/binary" + "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" +) + +// Consumes a stored nonce, replacing it with a successor +type AdvanceNonceAccount struct { + + // [0] = [WRITE] NonceAccount + // ··········· Nonce account + // + // [1] = [] $(SysVarRecentBlockHashesPubkey) + // ··········· RecentBlockhashes sysvar + // + // [2] = [SIGNER] NonceAuthorityAccount + // ··········· Nonce authority + ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"` +} + +// NewAdvanceNonceAccountInstructionBuilder creates a new `AdvanceNonceAccount` instruction builder. +func NewAdvanceNonceAccountInstructionBuilder() *AdvanceNonceAccount { + nd := &AdvanceNonceAccount{ + AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 3), + } + nd.AccountMetaSlice[1] = ag_solanago.Meta(ag_solanago.SysVarRecentBlockHashesPubkey) + return nd +} + +// Nonce account +func (inst *AdvanceNonceAccount) SetNonceAccount(nonceAccount ag_solanago.PublicKey) *AdvanceNonceAccount { + inst.AccountMetaSlice[0] = ag_solanago.Meta(nonceAccount).WRITE() + return inst +} + +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 (inst *AdvanceNonceAccount) GetNonceAuthorityAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice[2] +} + +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 inst.Build(), nil +} + +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 not set", accIndex) + } + } + return nil +} + +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) { + + // Parameters of the instruction: + instructionBranch.Child("Params").ParentFunc(func(paramsBranch ag_treeout.Branches) {}) + + // 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 new file mode 100644 index 00000000..1b97f82f --- /dev/null +++ b/programs/system/Allocate.go @@ -0,0 +1,133 @@ +package system + +import ( + "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" +) + +// 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 +} + +// 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 +} + +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 new file mode 100644 index 00000000..463dc7f9 --- /dev/null +++ b/programs/system/AllocateWithSeed.go @@ -0,0 +1,236 @@ +package system + +import ( + "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" +) + +// 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 + + // String of ASCII chars, no longer than pubkey::MAX_SEED_LEN + Seed *string + + // 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"` +} + +// NewAllocateWithSeedInstructionBuilder creates a new `AllocateWithSeed` instruction builder. +func NewAllocateWithSeedInstructionBuilder() *AllocateWithSeed { + nd := &AllocateWithSeed{ + AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 2), + } + return nd +} + +// 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 new file mode 100644 index 00000000..33d4e0fb --- /dev/null +++ b/programs/system/Assign.go @@ -0,0 +1,133 @@ +package system + +import ( + "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" +) + +// 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 +} + +// Owner program account +func (inst *Assign) SetOwner(owner ag_solanago.PublicKey) *Assign { + inst.Owner = &owner + return inst +} + +// Assigned account public key +func (inst *Assign) SetAssignedAccount(assignedAccount ag_solanago.PublicKey) *Assign { + inst.AccountMetaSlice[0] = ag_solanago.Meta(assignedAccount).WRITE().SIGNER() + return inst +} + +func (inst *Assign) GetAssignedAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice[0] +} + +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 +} + +func (inst *Assign) Validate() error { + // Check whether all (required) parameters are 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 *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 new file mode 100644 index 00000000..e233e0d5 --- /dev/null +++ b/programs/system/AssignWithSeed.go @@ -0,0 +1,207 @@ +package system + +import ( + "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" +) + +// Assign account to a program based on a seed +type AssignWithSeed struct { + // Base public key + Base *ag_solanago.PublicKey + + // String of ASCII chars, no longer than pubkey::MAX_SEED_LEN + Seed *string + + // Owner program account + Owner *ag_solanago.PublicKey + + // [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 +} + +// 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 new file mode 100644 index 00000000..f1a5e79a --- /dev/null +++ b/programs/system/AuthorizeNonceAccount.go @@ -0,0 +1,149 @@ +package system + +import ( + "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" +) + +// 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 +} + +// 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), + }} +} + +// 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 new file mode 100644 index 00000000..d531230e --- /dev/null +++ b/programs/system/CreateAccount.go @@ -0,0 +1,207 @@ +package system + +import ( + "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" +) + +// Create a new account +type CreateAccount struct { + // Number of lamports to transfer to the new account + Lamports *uint64 + + // 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"` +} + +// NewCreateAccountInstructionBuilder creates a new `CreateAccount` instruction builder. +func NewCreateAccountInstructionBuilder() *CreateAccount { + nd := &CreateAccount{ + AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 2), + } + return nd +} + +// Number of lamports to transfer to the new account +func (inst *CreateAccount) SetLamports(lamports uint64) *CreateAccount { + inst.Lamports = &lamports + return inst +} + +// Number of bytes of memory to allocate +func (inst *CreateAccount) SetSpace(space uint64) *CreateAccount { + inst.Space = &space + return inst +} + +// Address of program that will own the new account +func (inst *CreateAccount) SetOwner(owner ag_solanago.PublicKey) *CreateAccount { + inst.Owner = &owner + return inst +} + +// Funding account +func (inst *CreateAccount) SetFundingAccount(fundingAccount ag_solanago.PublicKey) *CreateAccount { + inst.AccountMetaSlice[0] = ag_solanago.Meta(fundingAccount).WRITE().SIGNER() + return inst +} + +func (inst *CreateAccount) GetFundingAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice[0] +} + +// New account +func (inst *CreateAccount) SetNewAccount(newAccount ag_solanago.PublicKey) *CreateAccount { + inst.AccountMetaSlice[1] = ag_solanago.Meta(newAccount).WRITE().SIGNER() + return inst +} + +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 inst.Build(), nil +} + +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 not set", accIndex) + } + } + return nil +} + +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])) + }) + }) + }) +} + +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 +} + +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 new file mode 100644 index 00000000..e12a369b --- /dev/null +++ b/programs/system/CreateAccountWithSeed.go @@ -0,0 +1,281 @@ +package system + +import ( + "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" +) + +// 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 +} + +// 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 +} + +// Number of lamports to transfer to the new account +func (inst *CreateAccountWithSeed) SetLamports(lamports uint64) *CreateAccountWithSeed { + inst.Lamports = &lamports + return inst +} + +// Number of bytes of memory to allocate +func (inst *CreateAccountWithSeed) SetSpace(space uint64) *CreateAccountWithSeed { + inst.Space = &space + return inst +} + +// 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 +} + +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 +} + +// 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 new file mode 100644 index 00000000..6ddbb0c7 --- /dev/null +++ b/programs/system/InitializeNonceAccount.go @@ -0,0 +1,169 @@ +package system + +import ( + "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" +) + +// 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 +} + +// 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 new file mode 100644 index 00000000..e75f3519 --- /dev/null +++ b/programs/system/Transfer.go @@ -0,0 +1,149 @@ +package system + +import ( + "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" +) + +// 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 +} + +// 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), + }} +} + +// 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) 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 ag_treeout.Branches) { + programBranch.Child(ag_format.Instruction("Transfer")). + // + 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("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 new file mode 100644 index 00000000..50c1cb50 --- /dev/null +++ b/programs/system/TransferWithSeed.go @@ -0,0 +1,223 @@ +package system + +import ( + "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" +) + +// Transfer lamports from a derived address +type TransferWithSeed struct { + // Amount to transfer + Lamports *uint64 + + // Seed to use to derive the funding account address + FromSeed *string + + // Owner to use to derive the funding account address + FromOwner *ag_solanago.PublicKey + + // [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 +} + +// 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 new file mode 100644 index 00000000..26b7f1b1 --- /dev/null +++ b/programs/system/WithdrawNonceAccount.go @@ -0,0 +1,199 @@ +package system + +import ( + "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" +) + +// 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 + + // [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 +} + +// 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 3339c32b..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,221 +7,202 @@ import ( "bytes" "encoding/binary" "fmt" - - bin "github.com/dfuse-io/binary" - solana "github.com/gagliardetto/solana-go" - "github.com/gagliardetto/solana-go/text" + 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 PROGRAM_ID = solana.MustPublicKeyFromBase58("11111111111111111111111111111111") +var ProgramID ag_solanago.PublicKey = ag_solanago.MustPublicKeyFromBase58("11111111111111111111111111111111") -func init() { - solana.RegisterInstructionDecoder(PROGRAM_ID, registryDecodeInstruction) +func SetProgramID(pubkey ag_solanago.PublicKey) { + ProgramID = pubkey + ag_solanago.RegisterInstructionDecoder(ProgramID, registryDecodeInstruction) } -func registryDecodeInstruction(accounts []*solana.AccountMeta, data []byte) (interface{}, error) { - inst, err := DecodeInstruction(accounts, data) - if err != nil { - return nil, err +const ProgramName = "System" + +func init() { + if !ProgramID.IsZero() { + ag_solanago.RegisterInstructionDecoder(ProgramID, registryDecodeInstruction) } - return inst, nil } -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) - } +const ( + // Create a new account + Instruction_CreateAccount uint32 = iota - 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) - } - } + // Assign account to a program + Instruction_Assign - return inst, nil -} + // Transfer lamports + Instruction_Transfer -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}, - }, - }, - }, - } -} + // Create a new account at an address derived from a base pubkey and a seed + Instruction_CreateAccountWithSeed -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}, - }, - }, - }, - } -} + // Consumes a stored nonce, replacing it with a successor + Instruction_AdvanceNonceAccount -type Instruction struct { - bin.BaseVariant -} + // Withdraw funds from a nonce account + Instruction_WithdrawNonceAccount -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 -} + // Drive state of Uninitalized nonce account to Initialized, setting the nonce value + Instruction_InitializeNonceAccount -func (i *Instruction) ProgramID() solana.PublicKey { - return PROGRAM_ID -} + // Change the entity authorized to execute nonce instructions on the account + Instruction_AuthorizeNonceAccount -func (i *Instruction) Data() ([]byte, error) { - buf := new(bytes.Buffer) - if err := bin.NewEncoder(buf).Encode(i); err != nil { - return nil, fmt.Errorf("unable to encode instruction: %w", err) - } - return buf.Bytes(), nil -} + // Allocate space in a (possibly new) account without funding + Instruction_Allocate -func (i *Instruction) TextEncode(encoder *text.Encoder, option *text.Option) error { - return encoder.Encode(i.Impl, option) -} + // Allocate space for and assign an account at an address derived from a base public key and a seed + Instruction_AllocateWithSeed -var InstructionImplDef = bin.NewVariantDefinition(bin.Uint32TypeIDEncoding, []bin.VariantType{ - {"create_account", (*CreateAccount)(nil)}, - {"assign", (*Assign)(nil)}, - {"transfer", (*Transfer)(nil)}, -}) + // Assign account to a program based on a seed + Instruction_AssignWithSeed -func (i *Instruction) UnmarshalBinary(decoder *bin.Decoder) error { - return i.BaseVariant.UnmarshalBinaryVariant(decoder, InstructionImplDef) -} + // Transfer lamports from a derived address + Instruction_TransferWithSeed +) -func (i *Instruction) MarshalBinary(encoder *bin.Encoder) error { - err := encoder.WriteUint32(i.TypeID, binary.LittleEndian) - if err != nil { - return fmt.Errorf("unable to write variant type: %w", err) +// InstructionIDToName returns the name of the instruction given its ID. +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 "" } - 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:"-"` +type Instruction struct { + ag_binary.BaseVariant } -func (i *CreateAccount) SetAccounts(accounts []*solana.AccountMeta) error { - i.Accounts = &CreateAccountAccounts{ - From: accounts[0], - New: accounts[1], +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)) } - return nil } -type Assign struct { - // prefixed with byte 0x01 - Owner solana.PublicKey -} +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), + }, + }, +) -type Transfer struct { - // Prefixed with byte 0x02 - Lamports bin.Uint64 - Accounts *TransferAccounts `bin:"-"` +func (inst *Instruction) ProgramID() ag_solanago.PublicKey { + return ProgramID } -type TransferAccounts struct { - From *solana.AccountMeta `text:"linear,notype"` - To *solana.AccountMeta `text:"linear,notype"` +func (inst *Instruction) Accounts() (out []*ag_solanago.AccountMeta) { + return inst.Impl.(ag_solanago.AccountsGettable).GetAccounts() } -func (i *Transfer) SetAccounts(accounts []*solana.AccountMeta) error { - i.Accounts = &TransferAccounts{ - From: accounts[0], - To: accounts[1], +func (inst *Instruction) Data() ([]byte, error) { + buf := new(bytes.Buffer) + if err := ag_binary.NewBinEncoder(buf).Encode(inst); err != nil { + return nil, fmt.Errorf("unable to encode instruction: %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 -} - -type WithdrawNonceAccount struct { - // Prefix with 0x05 - Lamports bin.Uint64 + return buf.Bytes(), nil } -type InitializeNonceAccount struct { - // Prefix with 0x06 - AuthorizedAccount solana.PublicKey +func (inst *Instruction) TextEncode(encoder *ag_text.Encoder, option *ag_text.Option) error { + return encoder.Encode(inst.Impl, option) } -type AuthorizeNonceAccount struct { - // Prefix with 0x07 - AuthorizeAccount solana.PublicKey +func (inst *Instruction) UnmarshalWithDecoder(decoder *ag_binary.Decoder) error { + return inst.BaseVariant.UnmarshalBinaryVariant(decoder, InstructionImplDef) } -type Allocate struct { - // Prefix with 0x08 - Space bin.Uint64 +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(inst.Impl) } -type AllocateWithSeed struct { - // Prefixed with byte 0x09 - Base solana.PublicKey - SeedSize int `bin:"sizeof=Seed"` - Seed string - Space bin.Uint64 - Owner solana.PublicKey +func registryDecodeInstruction(accounts []*ag_solanago.AccountMeta, data []byte) (interface{}, error) { + inst, err := DecodeInstruction(accounts, data) + if err != nil { + return nil, err + } + return inst, nil } -type AssignWithSeed struct { - // Prefixed with byte 0x0a - Base solana.PublicKey - SeedSize int `bin:"sizeof=Seed"` - Seed string - Owner solana.PublicKey +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.(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 } 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 03fb285b..ae34e5f5 100644 --- a/programs/token/instructions.go +++ b/programs/token/instructions.go @@ -39,11 +39,11 @@ 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) } - 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) @@ -76,12 +76,14 @@ 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 { - err := encoder.WriteUint8(uint8(i.TypeID)) +func (i *Instruction) MarshalWithEncoder(encoder *bin.Encoder) error { + err := encoder.WriteUint8(i.TypeID.Uint8()) 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 5dba509b..bddfb1a3 100644 --- a/programs/tokenregistry/instruction.go +++ b/programs/tokenregistry/instruction.go @@ -25,11 +25,11 @@ 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) } - 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) @@ -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, @@ -62,9 +62,11 @@ type Instruction struct { bin.BaseVariant } +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} } @@ -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,12 +93,12 @@ 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 { - err := encoder.WriteUint32(i.TypeID, binary.LittleEndian) +func (i *Instruction) MarshalWithEncoder(encoder *bin.Encoder) error { + 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/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/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/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 ( 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 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/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/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/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 { diff --git a/sysvar.go b/sysvar.go new file mode 100644 index 00000000..d8706958 --- /dev/null +++ b/sysvar.go @@ -0,0 +1,51 @@ +package solana + +// 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 = 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 = 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 = 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 = 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 = 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 = 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") + + // The SlotHistory sysvar contains a bitvector of slots present over the last epoch. It is updated every slot. + 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 = MustPublicKeyFromBase58("SysvarStakeHistory1111111111111111111111111") +) diff --git a/text/format/format.go b/text/format/format.go new file mode 100644 index 00000000..d92ace1e --- /dev/null +++ b/text/format/format.go @@ -0,0 +1,42 @@ +package format + +import ( + "strings" + + "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(name) + " " + text.ColorizeBG(programID.String()) +} + +func Instruction(name string) string { + return Purple(Bold("Instruction")) + ": " + Bold(name) +} + +func Param(name string, value interface{}) string { + return Sf(CC(Shakespeare(name), ": %s"), Lime(strings.TrimSpace(spew.Sdump(value)))) +} + +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 +} diff --git a/text/tools.go b/text/tools.go new file mode 100644 index 00000000..65ddb33a --- /dev/null +++ b/text/tools.go @@ -0,0 +1,285 @@ +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 { + 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) +} + +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..55393e57 --- /dev/null +++ b/text/tree.go @@ -0,0 +1,27 @@ +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 (enc *TreeEncoder) WriteString(s string) (int, error) { + return enc.output.Write([]byte(s)) +} diff --git a/transaction.go b/transaction.go index 9c3c4d97..b07fe92a 100644 --- a/transaction.go +++ b/transaction.go @@ -5,13 +5,16 @@ 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" "go.uber.org/zap" ) 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 } @@ -33,35 +36,54 @@ func TransactionPayer(payer PublicKey) TransactionOption { return transactionOptionFunc(func(opts *transactionOptions) { opts.payer = payer }) } -type pubkeySlice []PublicKey +var debugNewTransaction = false -// 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 +type TransactionBuilder struct { + instructions []Instruction + recentBlockHash Hash + opts []TransactionOption } -func (slice *pubkeySlice) append(pubkey PublicKey) { - *slice = append(*slice, pubkey) +// NewTransactionBuilder creates a new instruction builder. +func NewTransactionBuilder() *TransactionBuilder { + return &TransactionBuilder{} } -func (slice *pubkeySlice) has(pubkey PublicKey) bool { - for _, key := range *slice { - if key.Equals(pubkey) { - return true - } - } - return false +// AddInstruction adds the provided instruction to the builder. +func (builder *TransactionBuilder) AddInstruction(instruction Instruction) *TransactionBuilder { + builder.instructions = append(builder.instructions, instruction) + return builder } -var debugNewTransaction = false +// 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, blockHash Hash, opts ...TransactionOption) (*Transaction, error) { +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,17 +104,17 @@ 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") } } - 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 @@ -153,8 +175,18 @@ func NewTransaction(instructions []Instruction, blockHash Hash, opts ...Transact 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: blockHash, + RecentBlockhash: recentBlockHash, } accountKeyIndex := map[string]uint16{} for idx, acc := range finalAccounts { @@ -188,7 +220,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 +228,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()], @@ -224,7 +256,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.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 { @@ -235,13 +268,47 @@ 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) 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.DecodeCompactU16LengthFromByteReader(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.UnmarshalWithDecoder(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 { 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 +321,50 @@ 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 tx.Signatures, nil +} + +func (tx *Transaction) EncodeTree(encoder *text.TreeEncoder) (int, error) { + if len(encoder.Docs) == 0 { + encoder.Docs = []string{"Transaction"} } - return t.Signatures, nil + 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) + } else { + message.Child(spew.Sdump(decodedInstruction)) + } + } + }) } diff --git a/transaction_test.go b/transaction_test.go index 1837ba72..6fc5b120 100644 --- a/transaction_test.go +++ b/transaction_test.go @@ -1,9 +1,12 @@ package solana 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" ) @@ -90,3 +93,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 := TransactionFromDecoder(bin.NewBinDecoder(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, + ) + +} diff --git a/types.go b/types.go index 55dfd8c8..7a4a9fd5 100644 --- a/types.go +++ b/types.go @@ -15,10 +15,11 @@ package solana import ( - "encoding/binary" "fmt" bin "github.com/dfuse-io/binary" + "github.com/gagliardetto/solana-go/text" + "github.com/gagliardetto/treeout" ) type Transaction struct { @@ -32,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) } @@ -58,56 +61,147 @@ 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} - } - b := make([]byte, binary.MaxVarintLen64) - binary.PutUvarint(b, l) - return TrimRightZeros(b) -} +var _ bin.EncoderDecoder = &Message{} + +func (mx *Message) EncodeToTree(txTree treeout.Branches) { + txTree.Child(text.Sf("RecentBlockhash: %s", mx.RecentBlockhash)) -// 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 + txTree.Child("AccountKeys[]").ParentFunc(func(accountKeysBranch treeout.Branches) { + for _, key := range mx.AccountKeys { + accountKeysBranch.Child(text.ColorizeBG(key.String())) } - } - return buf[:cutIndex] + }) + + txTree.Child("Header").ParentFunc(func(message treeout.Branches) { + mx.Header.EncodeToTree(message) + }) +} + +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 (m *Message) MarshalBinary() ([]byte, error) { - b := []byte{ - m.Header.NumRequiredSignatures, - m.Header.NumReadonlySignedAccounts, - m.Header.NumReadonlyUnsignedAccounts, +func (mx *Message) MarshalBinary() ([]byte, error) { + buf := []byte{ + mx.Header.NumRequiredSignatures, + mx.Header.NumReadonlySignedAccounts, + mx.Header.NumReadonlyUnsignedAccounts, } - b = append(b, UintToVarLenBytes(uint64(len(m.AccountKeys)))...) - for _, key := range m.AccountKeys { - b = append(b, key[:]...) + bin.EncodeCompactU16Length(&buf, len(mx.AccountKeys)) + for _, key := range mx.AccountKeys { + buf = append(buf, key[:]...) } - b = append(b, m.RecentBlockhash[:]...) + buf = append(buf, mx.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)))...) + bin.EncodeCompactU16Length(&buf, len(mx.Instructions)) + for _, instruction := range mx.Instructions { + buf = append(buf, byte(instruction.ProgramIDIndex)) + bin.EncodeCompactU16Length(&buf, len(instruction.Accounts)) for _, accountIdx := range instruction.Accounts { - b = append(b, byte(accountIdx)) + buf = append(buf, byte(accountIdx)) } - b = append(b, UintToVarLenBytes(uint64(len(instruction.Data)))...) - b = append(b, instruction.Data...) + bin.EncodeCompactU16Length(&buf, len(instruction.Data)) + buf = append(buf, instruction.Data...) + } + return buf, nil +} + +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 { + return err + } + mx.Header.NumReadonlySignedAccounts, err = decoder.ReadUint8() + if err != nil { + return err + } + mx.Header.NumReadonlyUnsignedAccounts, err = decoder.ReadUint8() + if err != nil { + return err + } + } + { + numAccountKeys, err := bin.DecodeCompactU16LengthFromByteReader(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) + } } - return b, nil + { + recentBlockhashBytes, err := decoder.ReadNBytes(32) + if err != nil { + return err + } + var recentBlockhash Hash + copy(recentBlockhash[:], recentBlockhashBytes) + mx.RecentBlockhash = recentBlockhash + } + { + numInstructions, err := bin.DecodeCompactU16LengthFromByteReader(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.DecodeCompactU16LengthFromByteReader(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.DecodeCompactU16LengthFromByteReader(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 +279,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. @@ -205,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())