From 7cd05105b4857beb22bf1a770098bc4fa80d6635 Mon Sep 17 00:00:00 2001 From: Jim McDonald Date: Mon, 18 Nov 2024 21:10:56 +0000 Subject: [PATCH] Various updates. --- api/estimatefeeopts.go | 33 +++ api/feeestimate.go | 41 +++ api/submittransactionopts.go | 26 ++ api/submittransactionresponse.go | 36 +++ jsonrpc/estimatefee.go | 55 ++++ jsonrpc/estimatefee_test.go | 109 ++++++++ jsonrpc/helpers.go | 69 +++++ jsonrpc/submittransaction.go | 89 ++++++ jsonrpc/submittransaction_test.go | 114 ++++++++ service.go | 26 +- spec/block_test.go | 5 +- spec/executionstatus_test.go | 4 +- spec/feeunit_test.go | 4 +- spec/finalitystatus_test.go | 4 +- spec/invokev0transaction.go | 4 +- spec/invokev1transaction.go | 31 ++- spec/invokev1transaction_test.go | 64 +++++ spec/invokev3transaction.go | 51 +++- spec/invokev3transaction_test.go | 64 +++++ spec/transaction.go | 434 ++++-------------------------- spec/transactionreceipt_test.go | 4 +- spec/transactiontype_test.go | 4 +- spec/transactionversion.go | 28 +- types/hash.go | 11 +- types/publickey.go | 99 +++++++ types/signature.go | 17 ++ 26 files changed, 1017 insertions(+), 409 deletions(-) create mode 100644 api/estimatefeeopts.go create mode 100644 api/feeestimate.go create mode 100644 api/submittransactionopts.go create mode 100644 api/submittransactionresponse.go create mode 100644 jsonrpc/estimatefee.go create mode 100644 jsonrpc/estimatefee_test.go create mode 100644 jsonrpc/helpers.go create mode 100644 jsonrpc/submittransaction.go create mode 100644 jsonrpc/submittransaction_test.go create mode 100644 spec/invokev1transaction_test.go create mode 100644 spec/invokev3transaction_test.go create mode 100644 types/publickey.go create mode 100644 types/signature.go diff --git a/api/estimatefeeopts.go b/api/estimatefeeopts.go new file mode 100644 index 0000000..5f5497a --- /dev/null +++ b/api/estimatefeeopts.go @@ -0,0 +1,33 @@ +// Copyright © 2024 Attestant Limited. +// 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 api + +import ( + "github.com/attestantio/go-starknet-client/spec" + "github.com/attestantio/go-starknet-client/types" +) + +// EstimateFeeOpts are the options for estimating fees for a transaction. +type EstimateFeeOpts struct { + Common CommonOpts + + // Block is the block at which the fee is estimated. + // It can be a block number, block hash, or one of the special values "latest" or "pending". + Block types.BlockID + + // Transaction is the transaction for which to estimate fees. + Transaction *spec.Transaction + + // Simulation flags? +} diff --git a/api/feeestimate.go b/api/feeestimate.go new file mode 100644 index 0000000..fb8d6ab --- /dev/null +++ b/api/feeestimate.go @@ -0,0 +1,41 @@ +// Copyright © 2024 Attestant Limited. +// 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 api + +import ( + "encoding/json" + "fmt" + + "github.com/attestantio/go-starknet-client/types" +) + +// FeeEstimate contains a fee estimate. +type FeeEstimate struct { + GasConsumed types.Number `json:"gas_consumed"` + GasPrice types.Number `json:"gas_price"` + DataGasConsumed types.Number `json:"data_gas_consumed"` + DataGasPrice types.Number `json:"data_gas_price"` + OverallFee types.Number `json:"overall_fee"` + Unit string `json:"unit"` +} + +// String returns a string version of the structure. +func (f *FeeEstimate) String() string { + data, err := json.Marshal(f) + if err != nil { + return fmt.Sprintf("ERR: %v", err) + } + + return string(data) +} diff --git a/api/submittransactionopts.go b/api/submittransactionopts.go new file mode 100644 index 0000000..8cf6397 --- /dev/null +++ b/api/submittransactionopts.go @@ -0,0 +1,26 @@ +// Copyright © 2024 Attestant Limited. +// 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 api + +import ( + "github.com/attestantio/go-starknet-client/spec" +) + +// SubmitTransactionOpts are the options for transactions. +type SubmitTransactionOpts struct { + Common CommonOpts + + // Transaction is the transaction to send. + Transaction *spec.Transaction +} diff --git a/api/submittransactionresponse.go b/api/submittransactionresponse.go new file mode 100644 index 0000000..b584159 --- /dev/null +++ b/api/submittransactionresponse.go @@ -0,0 +1,36 @@ +// Copyright © 2024 Attestant Limited. +// 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 api + +import ( + "encoding/json" + "fmt" + + "github.com/attestantio/go-starknet-client/types" +) + +// SubmitTransactionResponse is the response from SubmitTransaction. +type SubmitTransactionResponse struct { + TransactionHash types.Hash `json:"transaction_hash"` +} + +// String returns a string version of the structure. +func (t SubmitTransactionResponse) String() string { + data, err := json.Marshal(t) + if err != nil { + return fmt.Sprintf("ERR: %v", err) + } + + return string(data) +} diff --git a/jsonrpc/estimatefee.go b/jsonrpc/estimatefee.go new file mode 100644 index 0000000..e974244 --- /dev/null +++ b/jsonrpc/estimatefee.go @@ -0,0 +1,55 @@ +// Copyright © 2024 Attestant Limited. +// 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 jsonrpc + +import ( + "context" + "errors" + + client "github.com/attestantio/go-starknet-client" + "github.com/attestantio/go-starknet-client/api" +) + +// EstimateFee estimates the fee for a transaction. +func (s *Service) EstimateFee(ctx context.Context, + opts *api.EstimateFeeOpts, +) ( + *api.Response[[]api.FeeEstimate], + error, +) { + if err := s.assertIsSynced(ctx); err != nil { + return nil, err + } + + if opts == nil { + return nil, client.ErrNoOptions + } + if opts.Transaction == nil { + return nil, errors.Join(errors.New("no transaction specified"), client.ErrInvalidOptions) + } + + tx := preFlightTransaction(ctx, opts.Transaction) + tx.SetQueryBit() + + var data []api.FeeEstimate + err := s.client.CallFor(&data, "starknet_estimateFee", []any{tx}, []any{"SKIP_VALIDATE"}, opts.Block) + if err != nil { + return nil, errors.Join(errors.New("starknet_estimateFee failed"), err) + } + + return &api.Response[[]api.FeeEstimate]{ + Data: data, + Metadata: map[string]any{}, + }, nil +} diff --git a/jsonrpc/estimatefee_test.go b/jsonrpc/estimatefee_test.go new file mode 100644 index 0000000..ee306c9 --- /dev/null +++ b/jsonrpc/estimatefee_test.go @@ -0,0 +1,109 @@ +// Copyright © 2024 Attestant Limited. +// 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 jsonrpc_test + +import ( + "context" + "os" + "testing" + + "github.com/attestantio/go-starknet-client/api" + "github.com/attestantio/go-starknet-client/jsonrpc" + "github.com/attestantio/go-starknet-client/spec" + "github.com/attestantio/go-starknet-client/types" + "github.com/rs/zerolog" + "github.com/stretchr/testify/require" +) + +func TestEstimateFee(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + s, err := jsonrpc.New(ctx, + jsonrpc.WithLogLevel(zerolog.Disabled), + jsonrpc.WithAddress(os.Getenv("JSONRPC_ADDRESS")), + jsonrpc.WithTimeout(timeout), + ) + require.NoError(t, err) + + nonceResponse, err := s.Nonce(ctx, &api.NonceOpts{ + Block: "latest", + Contract: strToAddress("0x391d69afc1b49f01ad8d2e0e8a03756b694dd056fb6645781eb00f33dbd8caf"), + }) + require.NoError(t, err) + + tests := []struct { + name string + transaction *spec.Transaction + }{ + { + name: "InvokeV1", + transaction: &spec.Transaction{ + InvokeV1Transaction: &spec.InvokeV1Transaction{ + Type: spec.TransactionTypeInvoke, + SenderAddress: strToAddress("0x391d69afc1b49f01ad8d2e0e8a03756b694dd056fb6645781eb00f33dbd8caf"), + Calldata: []types.FieldElement{ + strToFieldElement("0x1"), + strToFieldElement("0x49d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"), + strToFieldElement("0x83afd3f4caedc6eebf44246fe54e38c95e3179a5ec9ea81740eca5b482d12e"), + strToFieldElement("0x3"), + strToFieldElement("0x714792a41f3651e171c46c93fc53adeb922a414a891cc36d73029d23e99a6ec"), + strToFieldElement("0x2386f26fc10000"), + strToFieldElement("0x0"), + }, + Version: spec.TransactionVersion1, + Signature: types.Signature{}, + Nonce: types.Number(nonceResponse.Data), + MaxFee: 1000000000000, + }, + }, + }, + { + name: "InvokeV3", + transaction: &spec.Transaction{ + InvokeV3Transaction: &spec.InvokeV3Transaction{ + Type: spec.TransactionTypeInvoke, + SenderAddress: strToAddress("0x391d69afc1b49f01ad8d2e0e8a03756b694dd056fb6645781eb00f33dbd8caf"), + Calldata: []types.FieldElement{ + strToFieldElement("0x1"), + strToFieldElement("0x49d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"), + strToFieldElement("0x83afd3f4caedc6eebf44246fe54e38c95e3179a5ec9ea81740eca5b482d12e"), + strToFieldElement("0x3"), + strToFieldElement("0x714792a41f3651e171c46c93fc53adeb922a414a891cc36d73029d23e99a6ec"), + strToFieldElement("0x2386f26fc10000"), + strToFieldElement("0x0"), + }, + Version: spec.TransactionVersion3, + Signature: types.Signature{}, + Nonce: types.Number(nonceResponse.Data), + ResourceBounds: spec.ResourceBounds{}, + Tip: 0, + NonceDataAvailabilityMode: spec.TxDAModeL1, + FeeDataAvailabilityMode: spec.TxDAModeL1, + }, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + response, err := s.EstimateFee(ctx, &api.EstimateFeeOpts{ + Block: "pending", + Transaction: test.transaction, + }) + require.NoError(t, err) + require.NotNil(t, response.Data) + }) + } +} diff --git a/jsonrpc/helpers.go b/jsonrpc/helpers.go new file mode 100644 index 0000000..dadfed7 --- /dev/null +++ b/jsonrpc/helpers.go @@ -0,0 +1,69 @@ +// Copyright © 2024 Attestant Limited. +// 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 jsonrpc + +import ( + "context" + + "github.com/attestantio/go-starknet-client/spec" + "github.com/attestantio/go-starknet-client/types" +) + +// preFlightTransaction tidies up a transaction before sending it to a client. +func preFlightTransaction(ctx context.Context, + tx *spec.Transaction, +) *spec.Transaction { + switch { + case tx.InvokeV1Transaction != nil: + return preFlightInvokeV1Transaction(ctx, tx) + case tx.InvokeV3Transaction != nil: + return preFlightInvokeV3Transaction(ctx, tx) + } + + return tx +} + +func preFlightInvokeV1Transaction(_ context.Context, + tx *spec.Transaction, +) *spec.Transaction { + cpTx := &spec.Transaction{ + InvokeV1Transaction: tx.InvokeV1Transaction.Copy(), + } + + if cpTx.InvokeV1Transaction.Signature == nil { + cpTx.InvokeV1Transaction.Signature = types.Signature{} + } + + return cpTx +} + +func preFlightInvokeV3Transaction(_ context.Context, + tx *spec.Transaction, +) *spec.Transaction { + cpTx := &spec.Transaction{ + InvokeV3Transaction: tx.InvokeV3Transaction.Copy(), + } + + if cpTx.InvokeV3Transaction.Signature == nil { + cpTx.InvokeV3Transaction.Signature = types.Signature{} + } + if cpTx.InvokeV3Transaction.PaymasterData == nil { + cpTx.InvokeV3Transaction.PaymasterData = []types.FieldElement{} + } + if cpTx.InvokeV3Transaction.AccountDeploymentData == nil { + cpTx.InvokeV3Transaction.AccountDeploymentData = []types.FieldElement{} + } + + return cpTx +} diff --git a/jsonrpc/submittransaction.go b/jsonrpc/submittransaction.go new file mode 100644 index 0000000..1475fff --- /dev/null +++ b/jsonrpc/submittransaction.go @@ -0,0 +1,89 @@ +// Copyright © 2024 Attestant Limited. +// 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 jsonrpc + +import ( + "context" + "errors" + + client "github.com/attestantio/go-starknet-client" + "github.com/attestantio/go-starknet-client/api" + "github.com/attestantio/go-starknet-client/spec" +) + +// SubmitTransaction submits a transaction to the client. +func (s *Service) SubmitTransaction(ctx context.Context, + opts *api.SubmitTransactionOpts, +) ( + *api.Response[*api.SubmitTransactionResponse], + error, +) { + if err := s.assertIsSynced(ctx); err != nil { + return nil, err + } + + if opts == nil { + return nil, client.ErrNoOptions + } + if opts.Transaction == nil { + return nil, errors.Join(errors.New("no transaction specified"), client.ErrInvalidOptions) + } + + opts.Transaction = preFlightTransaction(ctx, opts.Transaction) + + switch { + case opts.Transaction.InvokeV1Transaction != nil: + return s.invokeV1Transaction(ctx, opts) + case opts.Transaction.InvokeV3Transaction != nil: + return s.invokeV3Transaction(ctx, opts) + default: + return nil, errors.New("unhandled transaction type") + } +} + +func (s *Service) invokeV1Transaction(_ context.Context, + opts *api.SubmitTransactionOpts, +) ( + *api.Response[*api.SubmitTransactionResponse], + error, +) { + var data api.SubmitTransactionResponse + err := s.client.CallFor(&data, "starknet_addInvokeTransaction", []*spec.Transaction{opts.Transaction}) + if err != nil { + return nil, errors.Join(errors.New("starknet_call failed"), err) + } + + return &api.Response[*api.SubmitTransactionResponse]{ + Data: &data, + Metadata: map[string]any{}, + }, nil +} + +func (s *Service) invokeV3Transaction(_ context.Context, + opts *api.SubmitTransactionOpts, +) ( + *api.Response[*api.SubmitTransactionResponse], + error, +) { + var data api.SubmitTransactionResponse + err := s.client.CallFor(&data, "starknet_addInvokeTransaction", []*spec.Transaction{opts.Transaction}) + if err != nil { + return nil, errors.Join(errors.New("starknet_call failed"), err) + } + + return &api.Response[*api.SubmitTransactionResponse]{ + Data: &data, + Metadata: map[string]any{}, + }, nil +} diff --git a/jsonrpc/submittransaction_test.go b/jsonrpc/submittransaction_test.go new file mode 100644 index 0000000..382b6f2 --- /dev/null +++ b/jsonrpc/submittransaction_test.go @@ -0,0 +1,114 @@ +// Copyright © 2024 Attestant Limited. +// 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 jsonrpc_test + +import ( + "context" + "os" + "testing" + + "github.com/attestantio/go-starknet-client/api" + "github.com/attestantio/go-starknet-client/jsonrpc" + "github.com/attestantio/go-starknet-client/spec" + "github.com/attestantio/go-starknet-client/types" + "github.com/rs/zerolog" + "github.com/stretchr/testify/require" +) + +func TestSubmitTransaction(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + s, err := jsonrpc.New(ctx, + jsonrpc.WithLogLevel(zerolog.Disabled), + jsonrpc.WithAddress(os.Getenv("JSONRPC_ADDRESS")), + jsonrpc.WithTimeout(timeout), + ) + require.NoError(t, err) + + tests := []struct { + name string + transaction *spec.Transaction + }{ + { + name: "InvokeV1", + transaction: &spec.Transaction{ + InvokeV1Transaction: &spec.InvokeV1Transaction{ + Type: spec.TransactionTypeInvoke, + SenderAddress: strToAddress("0x391d69afc1b49f01ad8d2e0e8a03756b694dd056fb6645781eb00f33dbd8caf"), + Calldata: []types.FieldElement{ + strToFieldElement("0x1"), + strToFieldElement("0x49d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"), + strToFieldElement("0x83afd3f4caedc6eebf44246fe54e38c95e3179a5ec9ea81740eca5b482d12e"), + strToFieldElement("0x3"), + strToFieldElement("0x714792a41f3651e171c46c93fc53adeb922a414a891cc36d73029d23e99a6ec"), + strToFieldElement("0x2386f26fc10000"), + strToFieldElement("0x0"), + }, + Version: spec.TransactionVersion1, + Signature: types.Signature{ + strToFieldElement("0x5cc5059ca03b3270080de3d87e9c05a99a140c47875cbb1c43d283a0098be13"), + strToFieldElement("0x7bc7316ebada49485c49c7a8b5cf0bc756486888f1fc2cb60acdadf0f54afc0"), + }, + Nonce: 3, + MaxFee: 1000000000000000000, + }, + }, + }, + { + name: "InvokeV3", + transaction: &spec.Transaction{ + InvokeV3Transaction: &spec.InvokeV3Transaction{ + Type: spec.TransactionTypeInvoke, + SenderAddress: strToAddress("0x391d69afc1b49f01ad8d2e0e8a03756b694dd056fb6645781eb00f33dbd8caf"), + Calldata: []types.FieldElement{ + strToFieldElement("0x1"), + strToFieldElement("0x49d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"), + strToFieldElement("0x83afd3f4caedc6eebf44246fe54e38c95e3179a5ec9ea81740eca5b482d12e"), + strToFieldElement("0x3"), + strToFieldElement("0x714792a41f3651e171c46c93fc53adeb922a414a891cc36d73029d23e99a6ec"), + strToFieldElement("0x2386f26fc10000"), + strToFieldElement("0x0"), + }, + Version: spec.TransactionVersion3, + Signature: types.Signature{ + strToFieldElement("0x5cc5059ca03b3270080de3d87e9c05a99a140c47875cbb1c43d283a0098be13"), + strToFieldElement("0x7bc7316ebada49485c49c7a8b5cf0bc756486888f1fc2cb60acdadf0f54afc0"), + }, + Nonce: 3, + ResourceBounds: spec.ResourceBounds{ + L1Gas: spec.ResourceBound{ + MaxAmount: 1000, + MaxPricePerUnit: 1000000000000000, + }, + }, + Tip: 0, + NonceDataAvailabilityMode: spec.TxDAModeL1, + FeeDataAvailabilityMode: spec.TxDAModeL1, + }, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + response, err := s.SubmitTransaction(ctx, &api.SubmitTransactionOpts{ + Transaction: test.transaction, + }) + require.NoError(t, err) + require.NotNil(t, response.Data) + require.False(t, response.Data.TransactionHash.IsZero()) + }) + } +} diff --git a/service.go b/service.go index 8ef9c47..87ca786 100644 --- a/service.go +++ b/service.go @@ -63,9 +63,9 @@ type BlockProvider interface { ) } -// CallProvider is the interface for making calls to the execution client. +// CallProvider is the interface for making calls to the client. type CallProvider interface { - // Call makes a call to the execution client. + // Call makes a call to the client. Call(ctx context.Context, opts *api.CallOpts, ) ( @@ -80,6 +80,17 @@ type ChainIDProvider interface { ChainID(ctx context.Context, opts *api.ChainIDOpts) (*api.Response[types.Data], error) } +// EstimateFeeProvider is the interface for estimating transaction fees. +type EstimateFeeProvider interface { + // EstimateFee estimates the fee for a transaction. + EstimateFee(ctx context.Context, + opts *api.EstimateFeeOpts, + ) ( + *api.Response[[]api.FeeEstimate], + error, + ) +} + // EventsProvider is the interface for providing events. type EventsProvider interface { // Events returns the events matching the filter. @@ -129,3 +140,14 @@ type SyncingProvider interface { error, ) } + +// TransactionSubmitter is the interface for submitting transactions to the client. +type TransactionSubmitter interface { + // SubmitTransaction submits a transaction to the client. + SubmitTransaction(ctx context.Context, + opts *api.SubmitTransactionOpts, + ) ( + *api.Response[*api.SubmitTransactionResponse], + error, + ) +} diff --git a/spec/block_test.go b/spec/block_test.go index ac3d746..c7f5bfc 100644 --- a/spec/block_test.go +++ b/spec/block_test.go @@ -41,7 +41,6 @@ func TestBlock(t *testing.T) { name: "Full", input: []byte(`{"status":"ACCEPTED_ON_L1","block_hash":"0x5c627d4aeb51280058bed93c7889bce78114d63baad1be0f0aeb32496d5f19c","parent_hash":"0x0","block_number":0,"new_root":"0xe005205a1327f3dff98074e528f7b96f30e0624a1dfcf571bdc81948d150a0","timestamp":1700406220,"sequencer_address":"0x1176a1bd84444c89232ec27754698e5d2e7e1a7f1539f12027f28b23ec9f3d8","l1_gas_price":{"price_in_fri":"0x0","price_in_wei":"0x3e617dc5"},"l1_data_gas_price":{"price_in_fri":"0x1","price_in_wei":"0x1"},"l1_da_mode":"CALLDATA","starknet_version":"0.12.3","transactions":[{"transaction":{"transaction_hash":"0x656e113cb27707d2147c271a79c51d1069b0273ae447b965e15154a17b3ec01","type":"DECLARE","version":"0x0","max_fee":"0x0","class_hash":"0x5c478ee27f2112411f86f207605b2e2c58cdb647bac0df27f660ef2252359c6","sender_address":"0x1","signature":[]},"receipt":{"type":"DECLARE","transaction_hash":"0x656e113cb27707d2147c271a79c51d1069b0273ae447b965e15154a17b3ec01","actual_fee":{"amount":"0x0","unit":"WEI"},"execution_status":"SUCCEEDED","finality_status":"ACCEPTED_ON_L1","messages_sent":[],"events":[],"execution_resources":{"steps":2711,"pedersen_builtin_applications":15,"range_check_builtin_applications":63,"data_availability":{"l1_gas":0,"l1_data_gas":0}}}},{"transaction":{"transaction_hash":"0x32538718071ad83ccd09fca03fe3a17add776ec12002d1c4e16ad4b92ddf752","type":"DECLARE","version":"0x0","max_fee":"0x0","class_hash":"0xd0e183745e9dae3e4e78a8ffedcce0903fc4900beace4e0abf192d4c202da3","sender_address":"0x1","signature":[]},"receipt":{"type":"DECLARE","transaction_hash":"0x32538718071ad83ccd09fca03fe3a17add776ec12002d1c4e16ad4b92ddf752","actual_fee":{"amount":"0x0","unit":"WEI"},"execution_status":"SUCCEEDED","finality_status":"ACCEPTED_ON_L1","messages_sent":[],"events":[],"execution_resources":{"steps":2711,"pedersen_builtin_applications":15,"range_check_builtin_applications":63,"data_availability":{"l1_gas":0,"l1_data_gas":0}}}},{"transaction":{"transaction_hash":"0x144f41e654d0916810a83df0fe8984043671200f28df1206f58566144e302dd","type":"DEPLOY_ACCOUNT","version":"0x1","nonce":"0x0","max_fee":"0x0","contract_address_salt":"0x0","class_hash":"0x5c478ee27f2112411f86f207605b2e2c58cdb647bac0df27f660ef2252359c6","constructor_calldata":["0x12c4df40394d06f157edec8d0e64db61fe0c271149ea860c8fe98def29ecf02"],"signature":["0x13f82fd9238dfc8d01543f89be2b5d5589b3eb93d9c3b888f1f94b089768771","0x2c279ec310c4dd58a296fab66b2624640780e79a1c5c87388e6150fb5384a9d"]},"receipt":{"type":"DEPLOY_ACCOUNT","transaction_hash":"0x144f41e654d0916810a83df0fe8984043671200f28df1206f58566144e302dd","actual_fee":{"amount":"0x0","unit":"WEI"},"execution_status":"SUCCEEDED","finality_status":"ACCEPTED_ON_L1","messages_sent":[],"events":[],"contract_address":"0x43abaa073c768ebf039c0c4f46db9acc39e9ec165690418060a652aab39e7d8","execution_resources":{"steps":3863,"pedersen_builtin_applications":23,"range_check_builtin_applications":83,"ecdsa_builtin_applications":1,"data_availability":{"l1_gas":0,"l1_data_gas":0}}}},{"transaction":{"transaction_hash":"0x6a5a493cf33919e58aa4c75777bffdef97c0e39cac968896d7bee8cc67905a1","type":"INVOKE","version":"0x1","nonce":"0x1","max_fee":"0x0","sender_address":"0x43abaa073c768ebf039c0c4f46db9acc39e9ec165690418060a652aab39e7d8","signature":["0x357dbb6c509a7d4b58f8ee7151236278b7959b39f7d05b8f7e2ef20593bdf7e","0x64d5f748eef19ca7f1c8cc533e5c9c85f80ef4f040c75da67bda82f5c58328d"],"calldata":["0x1","0x43abaa073c768ebf039c0c4f46db9acc39e9ec165690418060a652aab39e7d8","0x2730079d734ee55315f4f141eaed376bddd8c2133523d223a344c5604e0f7f8","0x0","0x5","0x5","0xd0e183745e9dae3e4e78a8ffedcce0903fc4900beace4e0abf192d4c202da3","0x322c2610264639f6b2cee681ac53fa65c37e187ea24292d1b21d859c55e1a78","0x1","0x0","0x1"]},"receipt":{"type":"INVOKE","transaction_hash":"0x6a5a493cf33919e58aa4c75777bffdef97c0e39cac968896d7bee8cc67905a1","actual_fee":{"amount":"0x0","unit":"WEI"},"execution_status":"SUCCEEDED","finality_status":"ACCEPTED_ON_L1","messages_sent":[],"events":[],"execution_resources":{"steps":5617,"pedersen_builtin_applications":23,"range_check_builtin_applications":122,"ecdsa_builtin_applications":1,"data_availability":{"l1_gas":0,"l1_data_gas":0}}}},{"transaction":{"transaction_hash":"0x4a5a3a951244f37c97b258465db17ba235dc68fd369c539d763bae99b2cae1","type":"INVOKE","version":"0x1","nonce":"0x2","max_fee":"0x0","sender_address":"0x43abaa073c768ebf039c0c4f46db9acc39e9ec165690418060a652aab39e7d8","signature":["0x6b5baf01bf27386f1544efcf2e91aa69a42a41a9781e24b3cd984a91e815de7","0x2de46894d2f386d25f2258e80f464e05f08daab78df764d87c4381a7eae6c97"],"calldata":["0x1","0x43abaa073c768ebf039c0c4f46db9acc39e9ec165690418060a652aab39e7d8","0x2730079d734ee55315f4f141eaed376bddd8c2133523d223a344c5604e0f7f8","0x0","0x5","0x5","0xd0e183745e9dae3e4e78a8ffedcce0903fc4900beace4e0abf192d4c202da3","0x322c2610264639f6b2cee681ac53fa65c37e187ea24292d1b21d859c55e1a78","0x1","0x0","0x0"]},"receipt":{"type":"INVOKE","transaction_hash":"0x4a5a3a951244f37c97b258465db17ba235dc68fd369c539d763bae99b2cae1","actual_fee":{"amount":"0x0","unit":"WEI"},"execution_status":"SUCCEEDED","finality_status":"ACCEPTED_ON_L1","messages_sent":[],"events":[],"execution_resources":{"steps":5617,"pedersen_builtin_applications":23,"range_check_builtin_applications":122,"ecdsa_builtin_applications":1,"data_availability":{"l1_gas":0,"l1_data_gas":0}}}},{"transaction":{"transaction_hash":"0x1bec64a9f5ff52154b560fd489ae2aabbfcb31062f7ea70c3c674ddf14b0add","type":"INVOKE","version":"0x1","nonce":"0x3","max_fee":"0x0","sender_address":"0x43abaa073c768ebf039c0c4f46db9acc39e9ec165690418060a652aab39e7d8","signature":["0x44580ba3cd68e5d9509d2fcb8bd09933ae4a7b7dfe6744eaa2329f9a79d7408","0x68404a4da22c31d8367f873c043318750a24c669702c72de8518a3d52284b94"],"calldata":["0x1","0x49d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7","0x195d4289b867c3d98c335ea31402667f3592e227faf3d2991308563ed102aab","0x0","0x0","0x0"]},"receipt":{"type":"INVOKE","transaction_hash":"0x1bec64a9f5ff52154b560fd489ae2aabbfcb31062f7ea70c3c674ddf14b0add","actual_fee":{"amount":"0x0","unit":"WEI"},"execution_status":"SUCCEEDED","finality_status":"ACCEPTED_ON_L1","messages_sent":[],"events":[{"from_address":"0x49d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7","keys":["0x3774b0545aabb37c45c1eddc6a7dae57de498aae6d5e3589e362d4b4323a533"],"data":["0x43abaa073c768ebf039c0c4f46db9acc39e9ec165690418060a652aab39e7d8","0x43abaa073c768ebf039c0c4f46db9acc39e9ec165690418060a652aab39e7d8"]},{"from_address":"0x49d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7","keys":["0x4595132f9b33b7077ebf2e7f3eb746a8e0a6d5c337c71cd8f9bf46cac3cfd7"],"data":["0x43abaa073c768ebf039c0c4f46db9acc39e9ec165690418060a652aab39e7d8"]}],"execution_resources":{"steps":4854,"pedersen_builtin_applications":17,"range_check_builtin_applications":106,"ecdsa_builtin_applications":1,"data_availability":{"l1_gas":0,"l1_data_gas":0}}}},{"transaction":{"transaction_hash":"0x72b2d3b79b3da51efe860da954a2f50d23da95f028a456c5356a08e61642754","type":"INVOKE","version":"0x1","nonce":"0x4","max_fee":"0x0","sender_address":"0x43abaa073c768ebf039c0c4f46db9acc39e9ec165690418060a652aab39e7d8","signature":["0x12ef79a31ed2ff357b290f40c8c1bbe53cbb253866f0dc52b25aabbfb443c2b","0x376b2ddfd1ba9c6a0d72e3e4dbc5bbb1f0d2601089999cbf477aed944ae5c03"],"calldata":["0x1","0x4c5772d1914fe6ce891b64eb35bf3522aeae1315647314aac58b01137607f3f","0x195d4289b867c3d98c335ea31402667f3592e227faf3d2991308563ed102aab","0x0","0x0","0x0"]},"receipt":{"type":"INVOKE","transaction_hash":"0x72b2d3b79b3da51efe860da954a2f50d23da95f028a456c5356a08e61642754","actual_fee":{"amount":"0x0","unit":"WEI"},"execution_status":"SUCCEEDED","finality_status":"ACCEPTED_ON_L1","messages_sent":[],"events":[{"from_address":"0x4c5772d1914fe6ce891b64eb35bf3522aeae1315647314aac58b01137607f3f","keys":["0x3774b0545aabb37c45c1eddc6a7dae57de498aae6d5e3589e362d4b4323a533"],"data":["0x43abaa073c768ebf039c0c4f46db9acc39e9ec165690418060a652aab39e7d8","0x43abaa073c768ebf039c0c4f46db9acc39e9ec165690418060a652aab39e7d8"]},{"from_address":"0x4c5772d1914fe6ce891b64eb35bf3522aeae1315647314aac58b01137607f3f","keys":["0x4595132f9b33b7077ebf2e7f3eb746a8e0a6d5c337c71cd8f9bf46cac3cfd7"],"data":["0x43abaa073c768ebf039c0c4f46db9acc39e9ec165690418060a652aab39e7d8"]}],"execution_resources":{"steps":4854,"pedersen_builtin_applications":17,"range_check_builtin_applications":106,"ecdsa_builtin_applications":1,"data_availability":{"l1_gas":0,"l1_data_gas":0}}}}]}`), // Ordering is different. - expected: []byte(`{"status":"ACCEPTED_ON_L1","block_hash":"0x5c627d4aeb51280058bed93c7889bce78114d63baad1be0f0aeb32496d5f19c","parent_hash":"0x0","block_number":0,"new_root":"0xe005205a1327f3dff98074e528f7b96f30e0624a1dfcf571bdc81948d150a0","timestamp":1700406220,"sequencer_address":"0x1176a1bd84444c89232ec27754698e5d2e7e1a7f1539f12027f28b23ec9f3d8","l1_gas_price":{"price_in_fri":"0x0","price_in_wei":"0x3e617dc5"},"l1_data_gas_price":{"price_in_fri":"0x1","price_in_wei":"0x1"},"l1_da_mode":"CALLDATA","starknet_version":"0.12.3","transactions":[{"transaction":{"transaction_hash":"0x656e113cb27707d2147c271a79c51d1069b0273ae447b965e15154a17b3ec01","type":"DECLARE","sender_address":"0x1","max_fee":"0x0","version":"0x0","signature":[],"class_hash":"0x5c478ee27f2112411f86f207605b2e2c58cdb647bac0df27f660ef2252359c6"},"receipt":{"type":"DECLARE","transaction_hash":"0x656e113cb27707d2147c271a79c51d1069b0273ae447b965e15154a17b3ec01","actual_fee":{"amount":"0x0","unit":"WEI"},"execution_status":"SUCCEEDED","finality_status":"ACCEPTED_ON_L1","messages_sent":[],"events":[],"execution_resources":{"steps":2711,"pedersen_builtin_applications":15,"range_check_builtin_applications":63,"data_availability":{"l1_gas":0,"l1_data_gas":0}}}},{"transaction":{"transaction_hash":"0x32538718071ad83ccd09fca03fe3a17add776ec12002d1c4e16ad4b92ddf752","type":"DECLARE","sender_address":"0x1","max_fee":"0x0","version":"0x0","signature":[],"class_hash":"0xd0e183745e9dae3e4e78a8ffedcce0903fc4900beace4e0abf192d4c202da3"},"receipt":{"type":"DECLARE","transaction_hash":"0x32538718071ad83ccd09fca03fe3a17add776ec12002d1c4e16ad4b92ddf752","actual_fee":{"amount":"0x0","unit":"WEI"},"execution_status":"SUCCEEDED","finality_status":"ACCEPTED_ON_L1","messages_sent":[],"events":[],"execution_resources":{"steps":2711,"pedersen_builtin_applications":15,"range_check_builtin_applications":63,"data_availability":{"l1_gas":0,"l1_data_gas":0}}}},{"transaction":{"transaction_hash":"0x144f41e654d0916810a83df0fe8984043671200f28df1206f58566144e302dd","type":"DEPLOY_ACCOUNT","max_fee":"0x0","version":"0x1","signature":["0x13f82fd9238dfc8d01543f89be2b5d5589b3eb93d9c3b888f1f94b089768771","0x2c279ec310c4dd58a296fab66b2624640780e79a1c5c87388e6150fb5384a9d"],"nonce":"0x0","contract_address_salt":"0x0","constructor_calldata":["0x12c4df40394d06f157edec8d0e64db61fe0c271149ea860c8fe98def29ecf02"],"class_hash":"0x5c478ee27f2112411f86f207605b2e2c58cdb647bac0df27f660ef2252359c6"},"receipt":{"type":"DEPLOY_ACCOUNT","transaction_hash":"0x144f41e654d0916810a83df0fe8984043671200f28df1206f58566144e302dd","actual_fee":{"amount":"0x0","unit":"WEI"},"execution_status":"SUCCEEDED","finality_status":"ACCEPTED_ON_L1","messages_sent":[],"events":[],"contract_address":"0x43abaa073c768ebf039c0c4f46db9acc39e9ec165690418060a652aab39e7d8","execution_resources":{"steps":3863,"pedersen_builtin_applications":23,"range_check_builtin_applications":83,"ecdsa_builtin_applications":1,"data_availability":{"l1_gas":0,"l1_data_gas":0}}}},{"transaction":{"transaction_hash":"0x6a5a493cf33919e58aa4c75777bffdef97c0e39cac968896d7bee8cc67905a1","type":"INVOKE","sender_address":"0x43abaa073c768ebf039c0c4f46db9acc39e9ec165690418060a652aab39e7d8","calldata":["0x1","0x43abaa073c768ebf039c0c4f46db9acc39e9ec165690418060a652aab39e7d8","0x2730079d734ee55315f4f141eaed376bddd8c2133523d223a344c5604e0f7f8","0x0","0x5","0x5","0xd0e183745e9dae3e4e78a8ffedcce0903fc4900beace4e0abf192d4c202da3","0x322c2610264639f6b2cee681ac53fa65c37e187ea24292d1b21d859c55e1a78","0x1","0x0","0x1"],"max_fee":"0x0","version":"0x1","signature":["0x357dbb6c509a7d4b58f8ee7151236278b7959b39f7d05b8f7e2ef20593bdf7e","0x64d5f748eef19ca7f1c8cc533e5c9c85f80ef4f040c75da67bda82f5c58328d"],"nonce":"0x1"},"receipt":{"type":"INVOKE","transaction_hash":"0x6a5a493cf33919e58aa4c75777bffdef97c0e39cac968896d7bee8cc67905a1","actual_fee":{"amount":"0x0","unit":"WEI"},"execution_status":"SUCCEEDED","finality_status":"ACCEPTED_ON_L1","messages_sent":[],"events":[],"execution_resources":{"steps":5617,"pedersen_builtin_applications":23,"range_check_builtin_applications":122,"ecdsa_builtin_applications":1,"data_availability":{"l1_gas":0,"l1_data_gas":0}}}},{"transaction":{"transaction_hash":"0x4a5a3a951244f37c97b258465db17ba235dc68fd369c539d763bae99b2cae1","type":"INVOKE","sender_address":"0x43abaa073c768ebf039c0c4f46db9acc39e9ec165690418060a652aab39e7d8","calldata":["0x1","0x43abaa073c768ebf039c0c4f46db9acc39e9ec165690418060a652aab39e7d8","0x2730079d734ee55315f4f141eaed376bddd8c2133523d223a344c5604e0f7f8","0x0","0x5","0x5","0xd0e183745e9dae3e4e78a8ffedcce0903fc4900beace4e0abf192d4c202da3","0x322c2610264639f6b2cee681ac53fa65c37e187ea24292d1b21d859c55e1a78","0x1","0x0","0x0"],"max_fee":"0x0","version":"0x1","signature":["0x6b5baf01bf27386f1544efcf2e91aa69a42a41a9781e24b3cd984a91e815de7","0x2de46894d2f386d25f2258e80f464e05f08daab78df764d87c4381a7eae6c97"],"nonce":"0x2"},"receipt":{"type":"INVOKE","transaction_hash":"0x4a5a3a951244f37c97b258465db17ba235dc68fd369c539d763bae99b2cae1","actual_fee":{"amount":"0x0","unit":"WEI"},"execution_status":"SUCCEEDED","finality_status":"ACCEPTED_ON_L1","messages_sent":[],"events":[],"execution_resources":{"steps":5617,"pedersen_builtin_applications":23,"range_check_builtin_applications":122,"ecdsa_builtin_applications":1,"data_availability":{"l1_gas":0,"l1_data_gas":0}}}},{"transaction":{"transaction_hash":"0x1bec64a9f5ff52154b560fd489ae2aabbfcb31062f7ea70c3c674ddf14b0add","type":"INVOKE","sender_address":"0x43abaa073c768ebf039c0c4f46db9acc39e9ec165690418060a652aab39e7d8","calldata":["0x1","0x49d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7","0x195d4289b867c3d98c335ea31402667f3592e227faf3d2991308563ed102aab","0x0","0x0","0x0"],"max_fee":"0x0","version":"0x1","signature":["0x44580ba3cd68e5d9509d2fcb8bd09933ae4a7b7dfe6744eaa2329f9a79d7408","0x68404a4da22c31d8367f873c043318750a24c669702c72de8518a3d52284b94"],"nonce":"0x3"},"receipt":{"type":"INVOKE","transaction_hash":"0x1bec64a9f5ff52154b560fd489ae2aabbfcb31062f7ea70c3c674ddf14b0add","actual_fee":{"amount":"0x0","unit":"WEI"},"execution_status":"SUCCEEDED","finality_status":"ACCEPTED_ON_L1","messages_sent":[],"events":[{"from_address":"0x49d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7","keys":["0x3774b0545aabb37c45c1eddc6a7dae57de498aae6d5e3589e362d4b4323a533"],"data":["0x43abaa073c768ebf039c0c4f46db9acc39e9ec165690418060a652aab39e7d8","0x43abaa073c768ebf039c0c4f46db9acc39e9ec165690418060a652aab39e7d8"]},{"from_address":"0x49d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7","keys":["0x4595132f9b33b7077ebf2e7f3eb746a8e0a6d5c337c71cd8f9bf46cac3cfd7"],"data":["0x43abaa073c768ebf039c0c4f46db9acc39e9ec165690418060a652aab39e7d8"]}],"execution_resources":{"steps":4854,"pedersen_builtin_applications":17,"range_check_builtin_applications":106,"ecdsa_builtin_applications":1,"data_availability":{"l1_gas":0,"l1_data_gas":0}}}},{"transaction":{"transaction_hash":"0x72b2d3b79b3da51efe860da954a2f50d23da95f028a456c5356a08e61642754","type":"INVOKE","sender_address":"0x43abaa073c768ebf039c0c4f46db9acc39e9ec165690418060a652aab39e7d8","calldata":["0x1","0x4c5772d1914fe6ce891b64eb35bf3522aeae1315647314aac58b01137607f3f","0x195d4289b867c3d98c335ea31402667f3592e227faf3d2991308563ed102aab","0x0","0x0","0x0"],"max_fee":"0x0","version":"0x1","signature":["0x12ef79a31ed2ff357b290f40c8c1bbe53cbb253866f0dc52b25aabbfb443c2b","0x376b2ddfd1ba9c6a0d72e3e4dbc5bbb1f0d2601089999cbf477aed944ae5c03"],"nonce":"0x4"},"receipt":{"type":"INVOKE","transaction_hash":"0x72b2d3b79b3da51efe860da954a2f50d23da95f028a456c5356a08e61642754","actual_fee":{"amount":"0x0","unit":"WEI"},"execution_status":"SUCCEEDED","finality_status":"ACCEPTED_ON_L1","messages_sent":[],"events":[{"from_address":"0x4c5772d1914fe6ce891b64eb35bf3522aeae1315647314aac58b01137607f3f","keys":["0x3774b0545aabb37c45c1eddc6a7dae57de498aae6d5e3589e362d4b4323a533"],"data":["0x43abaa073c768ebf039c0c4f46db9acc39e9ec165690418060a652aab39e7d8","0x43abaa073c768ebf039c0c4f46db9acc39e9ec165690418060a652aab39e7d8"]},{"from_address":"0x4c5772d1914fe6ce891b64eb35bf3522aeae1315647314aac58b01137607f3f","keys":["0x4595132f9b33b7077ebf2e7f3eb746a8e0a6d5c337c71cd8f9bf46cac3cfd7"],"data":["0x43abaa073c768ebf039c0c4f46db9acc39e9ec165690418060a652aab39e7d8"]}],"execution_resources":{"steps":4854,"pedersen_builtin_applications":17,"range_check_builtin_applications":106,"ecdsa_builtin_applications":1,"data_availability":{"l1_gas":0,"l1_data_gas":0}}}}]}`), }, { name: "Data", @@ -61,9 +60,9 @@ func TestBlock(t *testing.T) { rt, err := json.Marshal(&res) require.NoError(t, err) if len(test.expected) == 0 { - require.Equal(t, string(test.input), string(rt)) + require.JSONEq(t, string(test.input), string(rt)) } else { - require.Equal(t, string(test.expected), string(rt)) + require.JSONEq(t, string(test.expected), string(rt)) } } }) diff --git a/spec/executionstatus_test.go b/spec/executionstatus_test.go index 610c5b6..46b410e 100644 --- a/spec/executionstatus_test.go +++ b/spec/executionstatus_test.go @@ -68,9 +68,9 @@ func TestExecutionStatus(t *testing.T) { rt, err := json.Marshal(&res) require.NoError(t, err) if len(test.expected) == 0 { - require.Equal(t, string(test.input), string(rt)) + require.JSONEq(t, string(test.input), string(rt)) } else { - require.Equal(t, string(test.expected), string(rt)) + require.JSONEq(t, string(test.expected), string(rt)) } } }) diff --git a/spec/feeunit_test.go b/spec/feeunit_test.go index a11a224..2bbb122 100644 --- a/spec/feeunit_test.go +++ b/spec/feeunit_test.go @@ -73,9 +73,9 @@ func TestFeeUnit(t *testing.T) { rt, err := json.Marshal(&res) require.NoError(t, err) if len(test.expected) == 0 { - require.Equal(t, string(test.input), string(rt)) + require.JSONEq(t, string(test.input), string(rt)) } else { - require.Equal(t, string(test.expected), string(rt)) + require.JSONEq(t, string(test.expected), string(rt)) } } }) diff --git a/spec/finalitystatus_test.go b/spec/finalitystatus_test.go index 884840c..034d7a7 100644 --- a/spec/finalitystatus_test.go +++ b/spec/finalitystatus_test.go @@ -68,9 +68,9 @@ func TestFinalityStatus(t *testing.T) { rt, err := json.Marshal(&res) require.NoError(t, err) if len(test.expected) == 0 { - require.Equal(t, string(test.input), string(rt)) + require.JSONEq(t, string(test.input), string(rt)) } else { - require.Equal(t, string(test.expected), string(rt)) + require.JSONEq(t, string(test.expected), string(rt)) } } }) diff --git a/spec/invokev0transaction.go b/spec/invokev0transaction.go index 570c352..965a08b 100644 --- a/spec/invokev0transaction.go +++ b/spec/invokev0transaction.go @@ -22,11 +22,11 @@ import ( // InvokeV0Transaction is version 0 of the invoke transaction. type InvokeV0Transaction struct { - TransactionHash types.Hash `json:"transaction_hash"` + TransactionHash *types.Hash `json:"transaction_hash,omitempty"` Type TransactionType `json:"type"` MaxFee types.Number `json:"max_fee"` Version TransactionVersion `json:"version"` - Signature []types.FieldElement `json:"signature"` + Signature types.Signature `json:"signature"` ContractAddress types.Address `json:"contract_address"` EntryPointSelector types.FieldElement `json:"entry_point_selector"` Calldata []types.FieldElement `json:"calldata"` diff --git a/spec/invokev1transaction.go b/spec/invokev1transaction.go index 459fd42..4b96fbd 100644 --- a/spec/invokev1transaction.go +++ b/spec/invokev1transaction.go @@ -22,18 +22,43 @@ import ( // InvokeV1Transaction is version 1 of the invoke transaction. type InvokeV1Transaction struct { - TransactionHash types.Hash `json:"transaction_hash"` + TransactionHash *types.Hash `json:"transaction_hash,omitempty"` Type TransactionType `json:"type"` SenderAddress types.Address `json:"sender_address"` Calldata []types.FieldElement `json:"calldata"` MaxFee types.Number `json:"max_fee"` Version TransactionVersion `json:"version"` - Signature []types.FieldElement `json:"signature"` + Signature types.Signature `json:"signature"` Nonce types.Number `json:"nonce"` } +// Copy provides a deep copy of the transaction. +func (t InvokeV1Transaction) Copy() *InvokeV1Transaction { + tx := &InvokeV1Transaction{ + Type: t.Type, + Version: t.Version, + MaxFee: t.MaxFee, + Nonce: t.Nonce, + } + if t.TransactionHash != nil { + tx.TransactionHash = &types.Hash{} + copy(tx.TransactionHash[:], t.TransactionHash[:]) + } + copy(tx.SenderAddress[:], t.SenderAddress[:]) + tx.Calldata = make([]types.FieldElement, len(t.Calldata)) + for i := range t.Calldata { + copy(tx.Calldata[i][:], t.Calldata[i][:]) + } + tx.Signature = make([]types.FieldElement, len(t.Signature)) + for i := range t.Signature { + copy(tx.Signature[i][:], t.Signature[i][:]) + } + + return tx +} + // String returns a string version of the structure. -func (t *InvokeV1Transaction) String() string { +func (t InvokeV1Transaction) String() string { data, err := json.Marshal(t) if err != nil { return fmt.Sprintf("ERR: %v", err) diff --git a/spec/invokev1transaction_test.go b/spec/invokev1transaction_test.go new file mode 100644 index 0000000..ee42224 --- /dev/null +++ b/spec/invokev1transaction_test.go @@ -0,0 +1,64 @@ +// Copyright © 2024 Attestant Limited. +// 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 spec_test + +import ( + "encoding/json" + "testing" + + "github.com/attestantio/go-starknet-client/spec" + "github.com/stretchr/testify/require" +) + +func TestInvokeV1Transaction(t *testing.T) { + tests := []struct { + name string + input []byte + expected []byte + err string + }{ + { + name: "Empty", + err: "unexpected end of JSON input", + }, + { + name: "JSONBad", + input: []byte("[]"), + err: "json: cannot unmarshal array into Go value of type spec.InvokeV1Transaction", + }, + { + name: "Good", + input: []byte(`{"type":"INVOKE","sender_address":"0x391d69afc1b49f01ad8d2e0e8a03756b694dd056fb6645781eb00f33dbd8caf","calldata":["0x1","0x49d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7","0x83afd3f4caedc6eebf44246fe54e38c95e3179a5ec9ea81740eca5b482d12e","0x3","0x714792a41f3651e171c46c93fc53adeb922a414a891cc36d73029d23e99a6ec","0x2386f26fc10000","0x0"],"max_fee":"0x75e126529","version":"0x1","signature":["0x41cb23266d64d2ba4bbbb0a70355e249022153524fb2aeea3c41a2d0d9b785b","0x5f7bab89dcb1eef4e6a09a6da87494759c1816928012d05941c720fa7687920"],"nonce":"0x1"}`), + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + var res spec.InvokeV1Transaction + err := json.Unmarshal(test.input, &res) + if test.err != "" { + require.EqualError(t, err, test.err) + } else { + require.NoError(t, err) + rt, err := json.Marshal(&res) + require.NoError(t, err) + if len(test.expected) == 0 { + require.JSONEq(t, string(test.input), string(rt)) + } else { + require.JSONEq(t, string(test.expected), string(rt)) + } + } + }) + } +} diff --git a/spec/invokev3transaction.go b/spec/invokev3transaction.go index 01347e3..6a2aa86 100644 --- a/spec/invokev3transaction.go +++ b/spec/invokev3transaction.go @@ -22,12 +22,12 @@ import ( // InvokeV3Transaction is version 3 of the invoke transaction. type InvokeV3Transaction struct { - TransactionHash types.Hash `json:"transaction_hash"` + TransactionHash *types.Hash `json:"transaction_hash,omitempty"` Type TransactionType `json:"type"` SenderAddress types.Address `json:"sender_address"` Calldata []types.FieldElement `json:"calldata"` Version TransactionVersion `json:"version"` - Signature []types.FieldElement `json:"signature"` + Signature types.Signature `json:"signature"` Nonce types.Number `json:"nonce"` ResourceBounds ResourceBounds `json:"resource_bounds"` Tip types.Number `json:"tip"` @@ -37,8 +37,53 @@ type InvokeV3Transaction struct { FeeDataAvailabilityMode TxDAMode `json:"fee_data_availability_mode"` } +// Copy provides a deep copy of the transaction. +func (t InvokeV3Transaction) Copy() *InvokeV3Transaction { + tx := &InvokeV3Transaction{ + Type: t.Type, + Version: t.Version, + Nonce: t.Nonce, + ResourceBounds: ResourceBounds{ + L1Gas: ResourceBound{ + MaxAmount: t.ResourceBounds.L1Gas.MaxAmount, + MaxPricePerUnit: t.ResourceBounds.L1Gas.MaxPricePerUnit, + }, + L2Gas: ResourceBound{ + MaxAmount: t.ResourceBounds.L2Gas.MaxAmount, + MaxPricePerUnit: t.ResourceBounds.L2Gas.MaxPricePerUnit, + }, + }, + Tip: t.Tip, + NonceDataAvailabilityMode: t.NonceDataAvailabilityMode, + FeeDataAvailabilityMode: t.FeeDataAvailabilityMode, + } + if t.TransactionHash != nil { + tx.TransactionHash = &types.Hash{} + copy(tx.TransactionHash[:], t.TransactionHash[:]) + } + copy(tx.SenderAddress[:], t.SenderAddress[:]) + tx.Calldata = make([]types.FieldElement, len(t.Calldata)) + for i := range t.Calldata { + copy(tx.Calldata[i][:], t.Calldata[i][:]) + } + tx.Signature = make([]types.FieldElement, len(t.Signature)) + for i := range t.Signature { + copy(tx.Signature[i][:], t.Signature[i][:]) + } + tx.PaymasterData = make([]types.FieldElement, len(t.PaymasterData)) + for i := range t.PaymasterData { + copy(tx.PaymasterData[i][:], t.PaymasterData[i][:]) + } + tx.AccountDeploymentData = make([]types.FieldElement, len(t.AccountDeploymentData)) + for i := range t.AccountDeploymentData { + copy(tx.AccountDeploymentData[i][:], t.AccountDeploymentData[i][:]) + } + + return tx +} + // String returns a string version of the structure. -func (t *InvokeV3Transaction) String() string { +func (t InvokeV3Transaction) String() string { data, err := json.Marshal(t) if err != nil { return fmt.Sprintf("ERR: %v", err) diff --git a/spec/invokev3transaction_test.go b/spec/invokev3transaction_test.go new file mode 100644 index 0000000..b7c784b --- /dev/null +++ b/spec/invokev3transaction_test.go @@ -0,0 +1,64 @@ +// Copyright © 2024 Attestant Limited. +// 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 spec_test + +import ( + "encoding/json" + "testing" + + "github.com/attestantio/go-starknet-client/spec" + "github.com/stretchr/testify/require" +) + +func TestInvokeV3Transaction(t *testing.T) { + tests := []struct { + name string + input []byte + expected []byte + err string + }{ + { + name: "Empty", + err: "unexpected end of JSON input", + }, + { + name: "JSONBad", + input: []byte("[]"), + err: "json: cannot unmarshal array into Go value of type spec.InvokeV3Transaction", + }, + { + name: "Good", + input: []byte(`{"type":"INVOKE","sender_address":"0x391d69afc1b49f01ad8d2e0e8a03756b694dd056fb6645781eb00f33dbd8caf","calldata":["0x1","0x49d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7","0x83afd3f4caedc6eebf44246fe54e38c95e3179a5ec9ea81740eca5b482d12e","0x3","0x714792a41f3651e171c46c93fc53adeb922a414a891cc36d73029d23e99a6ec","0x2386f26fc10000","0x0"],"version":"0x3","signature":["0x30793b441461b3627061238a8370037cfe4ed310d1df5c74b6302ab2f1ee1ce","0x2b08553d33515ba29e8235e163718731a2c4ae55a10b036a848485c9601d236"],"nonce":"0x2","resource_bounds":{"l1_gas":{"max_amount":"0x22","max_price_per_unit":"0x9819642cfe1"},"l2_gas":{"max_amount":"0x0","max_price_per_unit":"0x0"}},"tip":"0x0","paymaster_data":[],"account_deployment_data":[],"nonce_data_availability_mode":"L1","fee_data_availability_mode":"L1"}`), + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + var res spec.InvokeV3Transaction + err := json.Unmarshal(test.input, &res) + if test.err != "" { + require.EqualError(t, err, test.err) + } else { + require.NoError(t, err) + rt, err := json.Marshal(&res) + require.NoError(t, err) + if len(test.expected) == 0 { + require.JSONEq(t, string(test.input), string(rt)) + } else { + require.JSONEq(t, string(test.expected), string(rt)) + } + } + }) + } +} diff --git a/spec/transaction.go b/spec/transaction.go index 94cebd0..c029eb9 100644 --- a/spec/transaction.go +++ b/spec/transaction.go @@ -22,8 +22,6 @@ import ( // Transaction is a struct that covers all transaction types. type Transaction struct { - Type TransactionType - Version TransactionVersion DeployV0Transaction *DeployV0Transaction InvokeV0Transaction *InvokeV0Transaction InvokeV1Transaction *InvokeV1Transaction @@ -38,63 +36,46 @@ type Transaction struct { } // transactionTypeAndVersionJSON is a simple struct to fetch the transaction type and version. -type transactionTypeJSON struct { +type transactionTypeAndVersionJSON struct { Type TransactionType `json:"type"` Version TransactionVersion `json:"version"` } // MarshalJSON marshals a typed transaction. -// -//nolint:gocritic func (t *Transaction) MarshalJSON() ([]byte, error) { - switch t.Type { - case TransactionTypeDeploy: - switch t.Version { - case TransactionVersion0: - return json.Marshal(t.DeployV0Transaction) - } - case TransactionTypeInvoke: - switch t.Version { - case TransactionVersion0: - return json.Marshal(t.InvokeV0Transaction) - case TransactionVersion1: - return json.Marshal(t.InvokeV1Transaction) - case TransactionVersion3: - return json.Marshal(t.InvokeV3Transaction) - } - case TransactionTypeDeclare: - switch t.Version { - case TransactionVersion0: - return json.Marshal(t.DeclareV0Transaction) - case TransactionVersion1: - return json.Marshal(t.DeclareV1Transaction) - case TransactionVersion2: - return json.Marshal(t.DeclareV2Transaction) - case TransactionVersion3: - return json.Marshal(t.DeclareV3Transaction) - } - case TransactionTypeDeployAccount: - switch t.Version { - case TransactionVersion1: - return json.Marshal(t.DeployAccountV1Transaction) - case TransactionVersion3: - return json.Marshal(t.DeployAccountV3Transaction) - } - case TransactionTypeL1Handler: - switch t.Version { - case TransactionVersion0: - return json.Marshal(t.L1HandlerV0Transaction) - } + switch { + case t.DeployV0Transaction != nil: + return json.Marshal(t.DeployV0Transaction) + case t.InvokeV0Transaction != nil: + return json.Marshal(t.InvokeV0Transaction) + case t.InvokeV1Transaction != nil: + return json.Marshal(t.InvokeV1Transaction) + case t.InvokeV3Transaction != nil: + return json.Marshal(t.InvokeV3Transaction) + case t.DeclareV0Transaction != nil: + return json.Marshal(t.DeclareV0Transaction) + case t.DeclareV1Transaction != nil: + return json.Marshal(t.DeclareV1Transaction) + case t.DeclareV2Transaction != nil: + return json.Marshal(t.DeclareV2Transaction) + case t.DeclareV3Transaction != nil: + return json.Marshal(t.DeclareV3Transaction) + case t.DeployAccountV1Transaction != nil: + return json.Marshal(t.DeployAccountV1Transaction) + case t.DeployAccountV3Transaction != nil: + return json.Marshal(t.DeployAccountV3Transaction) + case t.L1HandlerV0Transaction != nil: + return json.Marshal(t.L1HandlerV0Transaction) + default: + return nil, errors.New("unhandled transaction") } - - return nil, fmt.Errorf("unhandled transaction %v %v", t.Type, t.Version) } // UnmarshalJSON implements json.Unmarshaler. // //nolint:gocritic func (t *Transaction) UnmarshalJSON(input []byte) error { - var data transactionTypeJSON + var data transactionTypeAndVersionJSON err := json.Unmarshal(input, &data) if err != nil { return errors.Join(errors.New("invalid JSON"), err) @@ -153,343 +134,36 @@ func (t *Transaction) UnmarshalJSON(input []byte) error { err = fmt.Errorf("unhandled transaction %v %v", data.Type, data.Version) } - t.Type = data.Type - t.Version = data.Version - return err } -// // AccessList returns the access list of the transaction. -// // This value can be nil, if the transaction does not support access lists. -// func (t *Transaction) AccessList() []*AccessListEntry { -// switch t.Type { -// case TransactionType0: -// return nil -// case TransactionType1: -// return t.Type1Transaction.AccessList -// case TransactionType2: -// return t.Type2Transaction.AccessList -// case TransactionType3: -// return t.Type3Transaction.AccessList -// default: -// panic(fmt.Errorf("unhandled transaction type %s", t.Type)) -// } -// } -// -// // BlobGasUsed returns the blob gas used by the transaction. -// // This value can be nil, if the transaction does not support this (e.g. type 0 transactions). -// func (t *Transaction) BlobGasUsed() *uint32 { -// switch t.Type { -// case TransactionType0: -// return nil -// case TransactionType1: -// return nil -// case TransactionType2: -// return nil -// case TransactionType3: -// return t.Type3Transaction.BlobGasUsed -// default: -// panic(fmt.Errorf("unhandled transaction type %s", t.Type)) -// } -// } -// -// // BlobVersionedHashes returns the blob versioned hashes of the transaction. -// // This value can be nil, if the transaction is not a blob transaction. -// func (t *Transaction) BlobVersionedHashes() []types.VersionedHash { -// switch t.Type { -// case TransactionType0: -// return nil -// case TransactionType1: -// return nil -// case TransactionType2: -// return nil -// case TransactionType3: -// return t.Type3Transaction.BlobVersionedHashes -// default: -// panic(fmt.Errorf("unhandled transaction type %s", t.Type)) -// } -// } -// -// // BlockHash returns the block hash of the transaction. -// // This value can be nil, if the transaction is not included in a block. -// func (t *Transaction) BlockHash() *types.Hash { -// switch t.Type { -// case TransactionType0: -// return t.Type0Transaction.BlockHash -// case TransactionType1: -// return t.Type1Transaction.BlockHash -// case TransactionType2: -// return t.Type2Transaction.BlockHash -// case TransactionType3: -// return t.Type3Transaction.BlockHash -// default: -// panic(fmt.Errorf("unhandled transaction type %s", t.Type)) -// } -// } -// -// // BlockNumber returns the block number of the transaction. -// // This value can be nil, if the transaction is not included in a block. -// func (t *Transaction) BlockNumber() *uint32 { -// switch t.Type { -// case TransactionType0: -// return t.Type0Transaction.BlockNumber -// case TransactionType1: -// return t.Type1Transaction.BlockNumber -// case TransactionType2: -// return t.Type2Transaction.BlockNumber -// case TransactionType3: -// return t.Type3Transaction.BlockNumber -// default: -// panic(fmt.Errorf("unhandled transaction type %s", t.Type)) -// } -// } -// -// // From returns the sender of the transaction. -// func (t *Transaction) From() types.Address { -// switch t.Type { -// case TransactionType0: -// return t.Type0Transaction.From -// case TransactionType1: -// return t.Type1Transaction.From -// case TransactionType2: -// return t.Type2Transaction.From -// case TransactionType3: -// return t.Type3Transaction.From -// default: -// panic(fmt.Errorf("unhandled transaction type %s", t.Type)) -// } -// } -// -// // Gas returns the gas limit of the transaction. -// func (t *Transaction) Gas() uint32 { -// switch t.Type { -// case TransactionType0: -// return t.Type0Transaction.Gas -// case TransactionType1: -// return t.Type1Transaction.Gas -// case TransactionType2: -// return t.Type2Transaction.Gas -// case TransactionType3: -// return t.Type3Transaction.Gas -// default: -// panic(fmt.Errorf("unhandled transaction type %s", t.Type)) -// } -// } -// -// // GasPrice returns the gas price of the transaction. -// // This will be 0 for transactions that do not have an individual -// // gas price, for example type 2 transactions. -// func (t *Transaction) GasPrice() uint64 { -// switch t.Type { -// case TransactionType0: -// return t.Type0Transaction.GasPrice -// case TransactionType1: -// return t.Type1Transaction.GasPrice -// case TransactionType2: -// return 0 -// case TransactionType3: -// return 0 -// default: -// panic(fmt.Errorf("unhandled transaction type %s", t.Type)) -// } -// } -// -// // Hash returns the hash of the transaction. -// func (t *Transaction) Hash() types.Hash { -// switch t.Type { -// case TransactionType0: -// return t.Type0Transaction.Hash -// case TransactionType1: -// return t.Type1Transaction.Hash -// case TransactionType2: -// return t.Type2Transaction.Hash -// case TransactionType3: -// return t.Type3Transaction.Hash -// default: -// panic(fmt.Errorf("unhandled transaction type %s", t.Type)) -// } -// } -// -// // Input returns the input data of the transaction. -// func (t *Transaction) Input() []byte { -// switch t.Type { -// case TransactionType0: -// return t.Type0Transaction.Input -// case TransactionType1: -// return t.Type1Transaction.Input -// case TransactionType2: -// return t.Type2Transaction.Input -// case TransactionType3: -// return t.Type3Transaction.Input -// default: -// panic(fmt.Errorf("unhandled transaction type %s", t.Type)) -// } -// } -// -// // MaxFeePerGas returns the maximum fee per gas paid by the transaction. -// // This value can be 0, if the transaction does not support this (e.g. type 0 transactions). -// func (t *Transaction) MaxFeePerGas() uint64 { -// switch t.Type { -// case TransactionType0: -// return 0 -// case TransactionType1: -// return 0 -// case TransactionType2: -// return t.Type2Transaction.MaxFeePerGas -// case TransactionType3: -// return t.Type3Transaction.MaxFeePerGas -// default: -// panic(fmt.Errorf("unhandled transaction type %s", t.Type)) -// } -// } -// -// // MaxFeePerBlobGas returns the maximum fee per blob gas paid by the transaction. -// // This value can be 0, if the transaction does not support this (e.g. type 0 transactions). -// func (t *Transaction) MaxFeePerBlobGas() uint64 { -// switch t.Type { -// case TransactionType0: -// return 0 -// case TransactionType1: -// return 0 -// case TransactionType2: -// return 0 -// case TransactionType3: -// return t.Type3Transaction.MaxFeePerBlobGas -// default: -// panic(fmt.Errorf("unhandled transaction type %s", t.Type)) -// } -// } -// -// // MaxPriorityFeePerGas returns the maximum priority fee per gas paid by the transaction. -// // This value can be 0, if the transaction does not support this (e.g. type 0 transactions). -// func (t *Transaction) MaxPriorityFeePerGas() uint64 { -// switch t.Type { -// case TransactionType0: -// return 0 -// case TransactionType1: -// return 0 -// case TransactionType2: -// return t.Type2Transaction.MaxPriorityFeePerGas -// case TransactionType3: -// return t.Type3Transaction.MaxPriorityFeePerGas -// default: -// panic(fmt.Errorf("unhandled transaction type %s", t.Type)) -// } -// } -// -// // Nonce returns the nonce of the transaction. -// func (t *Transaction) Nonce() uint64 { -// switch t.Type { -// case TransactionType0: -// return t.Type0Transaction.Nonce -// case TransactionType1: -// return t.Type1Transaction.Nonce -// case TransactionType2: -// return t.Type2Transaction.Nonce -// case TransactionType3: -// return t.Type3Transaction.Nonce -// default: -// panic(fmt.Errorf("unhandled transaction type %s", t.Type)) -// } -// } -// -// // R returns the R portion of the signature of the transaction. -// func (t *Transaction) R() *big.Int { -// switch t.Type { -// case TransactionType0: -// return t.Type0Transaction.R -// case TransactionType1: -// return t.Type1Transaction.R -// case TransactionType2: -// return t.Type2Transaction.R -// case TransactionType3: -// return t.Type3Transaction.R -// default: -// panic(fmt.Errorf("unhandled transaction type %s", t.Type)) -// } -// } -// -// // S returns the S portion of the signature of the transaction. -// func (t *Transaction) S() *big.Int { -// switch t.Type { -// case TransactionType0: -// return t.Type0Transaction.S -// case TransactionType1: -// return t.Type1Transaction.S -// case TransactionType2: -// return t.Type2Transaction.S -// case TransactionType3: -// return t.Type3Transaction.S -// default: -// panic(fmt.Errorf("unhandled transaction type %s", t.Type)) -// } -// } -// -// // To returns the recipient of the transaction. -// // This can be nil, for example on contract creation. -// func (t *Transaction) To() *types.Address { -// switch t.Type { -// case TransactionType0: -// return t.Type0Transaction.To -// case TransactionType1: -// return t.Type1Transaction.To -// case TransactionType2: -// return t.Type2Transaction.To -// case TransactionType3: -// return t.Type3Transaction.To -// default: -// panic(fmt.Errorf("unhandled transaction type %s", t.Type)) -// } -// } -// -// // TransactionIndex returns the index of the transaction in its block. -// // This value can be nil, if the transaction is not included in a block. -// func (t *Transaction) TransactionIndex() *uint32 { -// switch t.Type { -// case TransactionType0: -// return t.Type0Transaction.TransactionIndex -// case TransactionType1: -// return t.Type1Transaction.TransactionIndex -// case TransactionType2: -// return t.Type2Transaction.TransactionIndex -// case TransactionType3: -// return t.Type3Transaction.TransactionIndex -// default: -// panic(fmt.Errorf("unhandled transaction type %s", t.Type)) -// } -// } -// -// // V returns the V portion of the signature of the transaction. -// func (t *Transaction) V() *big.Int { -// switch t.Type { -// case TransactionType0: -// return t.Type0Transaction.V -// case TransactionType1: -// return t.Type1Transaction.V -// case TransactionType2: -// return t.Type2Transaction.V -// case TransactionType3: -// return t.Type3Transaction.V -// default: -// panic(fmt.Errorf("unhandled transaction type %s", t.Type)) -// } -// } -// -// // Value returns the value of the transaction. -// func (t *Transaction) Value() *big.Int { -// switch t.Type { -// case TransactionType0: -// return t.Type0Transaction.Value -// case TransactionType1: -// return t.Type1Transaction.Value -// case TransactionType2: -// return t.Type2Transaction.Value -// case TransactionType3: -// return t.Type3Transaction.Value -// default: -// panic(fmt.Errorf("unhandled transaction type %s", t.Type)) -// } -// } +// SetQueryBit sets the query bit for the transaction. +func (t *Transaction) SetQueryBit() { + switch { + case t.DeployV0Transaction != nil: + t.DeployV0Transaction.Version = TransactionVersion0Query + case t.InvokeV0Transaction != nil: + t.InvokeV0Transaction.Version = TransactionVersion0Query + case t.InvokeV1Transaction != nil: + t.InvokeV1Transaction.Version = TransactionVersion1Query + case t.InvokeV3Transaction != nil: + t.InvokeV3Transaction.Version = TransactionVersion3Query + case t.DeclareV0Transaction != nil: + t.DeclareV0Transaction.Version = TransactionVersion0Query + case t.DeclareV1Transaction != nil: + t.DeclareV1Transaction.Version = TransactionVersion1Query + case t.DeclareV2Transaction != nil: + t.DeclareV2Transaction.Version = TransactionVersion2Query + case t.DeclareV3Transaction != nil: + t.DeclareV3Transaction.Version = TransactionVersion3Query + case t.DeployAccountV1Transaction != nil: + t.DeployAccountV1Transaction.Version = TransactionVersion1Query + case t.DeployAccountV3Transaction != nil: + t.DeployAccountV3Transaction.Version = TransactionVersion3Query + case t.L1HandlerV0Transaction != nil: + t.L1HandlerV0Transaction.Version = TransactionVersion1Query + } +} // String returns a string version of the structure. func (t *Transaction) String() string { diff --git a/spec/transactionreceipt_test.go b/spec/transactionreceipt_test.go index d3f7d2d..5de4e8e 100644 --- a/spec/transactionreceipt_test.go +++ b/spec/transactionreceipt_test.go @@ -54,9 +54,9 @@ func TestTransactionReceipt(t *testing.T) { rt, err := json.Marshal(&res) require.NoError(t, err) if len(test.expected) == 0 { - require.Equal(t, string(test.input), string(rt)) + require.JSONEq(t, string(test.input), string(rt)) } else { - require.Equal(t, string(test.expected), string(rt)) + require.JSONEq(t, string(test.expected), string(rt)) } } }) diff --git a/spec/transactiontype_test.go b/spec/transactiontype_test.go index f619367..8591089 100644 --- a/spec/transactiontype_test.go +++ b/spec/transactiontype_test.go @@ -76,9 +76,9 @@ func TestTransactionType(t *testing.T) { rt, err := json.Marshal(&res) require.NoError(t, err) if len(test.expected) == 0 { - require.Equal(t, string(test.input), string(rt)) + require.JSONEq(t, string(test.input), string(rt)) } else { - require.Equal(t, string(test.expected), string(rt)) + require.JSONEq(t, string(test.expected), string(rt)) } } }) diff --git a/spec/transactionversion.go b/spec/transactionversion.go index a904980..f28a61f 100644 --- a/spec/transactionversion.go +++ b/spec/transactionversion.go @@ -29,20 +29,32 @@ const ( TransactionVersionUnknown TransactionVersion = iota // TransactionVersion0 is a version 0 transaction. TransactionVersion0 + // TransactionVersion0Query is a query-only version 0 transaction. + TransactionVersion0Query // TransactionVersion1 is a version 1 transaction. TransactionVersion1 + // TransactionVersion1Query is a query-only version 1 transaction. + TransactionVersion1Query // TransactionVersion2 is a version 2 transaction. TransactionVersion2 + // TransactionVersion2Query is a query-only version 2 transaction. + TransactionVersion2Query // TransactionVersion3 is a version 3 transaction. TransactionVersion3 + // TransactionVersion3Query is a query-only version 3 transaction. + TransactionVersion3Query ) var transactionVersionStrings = [...]string{ "UNKNOWN", "0x0", + "0x100000000000000000000000000000000", "0x1", + "0x100000000000000000000000000000001", "0x2", + "0x100000000000000000000000000000002", "0x3", + "0x100000000000000000000000000000003", } // MarshalJSON implements json.Marshaler. @@ -60,14 +72,22 @@ func (v *TransactionVersion) UnmarshalJSON(input []byte) error { switch strings.ToLower(strings.Trim(string(input), `"`)) { case "unknown": *v = TransactionVersionUnknown - case "0x0", "0x100000000000000000000000000000000": + case "0x0": *v = TransactionVersion0 - case "0x1", "0x100000000000000000000000000000001": + case "0x100000000000000000000000000000000": + *v = TransactionVersion0Query + case "0x1": *v = TransactionVersion1 - case "0x2", "0x100000000000000000000000000000002": + case "0x100000000000000000000000000000001": + *v = TransactionVersion1Query + case "0x2": *v = TransactionVersion2 - case "0x3", "0x100000000000000000000000000000003": + case "0x100000000000000000000000000000002": + *v = TransactionVersion2Query + case "0x3": *v = TransactionVersion3 + case "0x100000000000000000000000000000003": + *v = TransactionVersion3Query default: err = fmt.Errorf("unrecognised transaction version %s", string(input)) } diff --git a/types/hash.go b/types/hash.go index b61569c..31efd0d 100644 --- a/types/hash.go +++ b/types/hash.go @@ -29,8 +29,15 @@ const HashLength = 32 //nolint:recvcheck type Hash [HashLength]byte +var zeroHash = Hash{} + +// IsZero returns true if the hash is zero. +func (h Hash) IsZero() bool { + return bytes.Equal(h[:], zeroHash[:]) +} + // String returns the string representation of the hash. -func (h *Hash) String() string { +func (h Hash) String() string { res := hex.EncodeToString(h[:]) // Leading 0s not allowed... res = strings.TrimLeft(res, "0") @@ -43,7 +50,7 @@ func (h *Hash) String() string { } // Format formats the hash. -func (h *Hash) Format(state fmt.State, v rune) { +func (h Hash) Format(state fmt.State, v rune) { format := string(v) switch v { case 's': diff --git a/types/publickey.go b/types/publickey.go new file mode 100644 index 0000000..9fb2472 --- /dev/null +++ b/types/publickey.go @@ -0,0 +1,99 @@ +// Copyright © 2024 Attestant Limited. +// 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 types + +import ( + "bytes" + "encoding/hex" + "errors" + "fmt" + "strings" +) + +// PublicKeyLength is the length of a startknet public key. +const PublicKeyLength = 32 + +// PublicKey is a starknet public key +// +//nolint:recvcheck +type PublicKey [PublicKeyLength]byte + +var zeroPublicKey = PublicKey{} + +// IsZero returns true if the public key is zero. +func (a *PublicKey) IsZero() bool { + return bytes.Equal(a[:], zeroPublicKey[:]) +} + +// String returns the string representation of the public key. +func (a *PublicKey) String() string { + res := hex.EncodeToString(a[:]) + // Leading 0s not allowed... + res = strings.TrimLeft(res, "0") + // ...unless that's all there was. + if len(res) == 0 { + res = "0" + } + + return "0x" + res +} + +// Format formats the address. +func (a *PublicKey) Format(state fmt.State, v rune) { + format := string(v) + switch v { + case 's': + fmt.Fprint(state, a.String()) + case 'x', 'X': + if state.Flag('#') { + format = "#" + format + } + fmt.Fprintf(state, "%"+format, a[:]) + default: + fmt.Fprintf(state, "%"+format, a[:]) + } +} + +// UnmarshalJSON implements json.Unmarshaler. +func (a *PublicKey) UnmarshalJSON(input []byte) error { + if len(input) == 0 { + return errors.New("address missing") + } + + if !bytes.HasPrefix(input, []byte{'"', '0', 'x'}) { + return errors.New("invalid address prefix") + } + if !bytes.HasSuffix(input, []byte{'"'}) { + return errors.New("invalid address suffix") + } + + // Ensure that there are an even number of characters. + bytesStr := string(input[3 : len(input)-1]) + if len(bytesStr)%2 == 1 { + bytesStr = "0" + bytesStr + } + + val, err := hex.DecodeString(bytesStr) + if err != nil { + return errors.New("invalid address") + } + copy(a[len(a)-len(val):], val) + + return nil +} + +// MarshalJSON implements json.Marshaler. +func (a PublicKey) MarshalJSON() ([]byte, error) { + return []byte(fmt.Sprintf("%q", a.String())), nil +} diff --git a/types/signature.go b/types/signature.go new file mode 100644 index 0000000..ef1d46e --- /dev/null +++ b/types/signature.go @@ -0,0 +1,17 @@ +// Copyright © 2024 Attestant Limited. +// 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 types + +// Signature is a Starknet signature. +type Signature []FieldElement