Skip to content

Commit

Permalink
Make block ID its own type.
Browse files Browse the repository at this point in the history
  • Loading branch information
mcdee committed Oct 30, 2024
1 parent 9a01dcf commit e8d07d1
Show file tree
Hide file tree
Showing 11 changed files with 120 additions and 95 deletions.
42 changes: 2 additions & 40 deletions api/blockopts.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,51 +14,13 @@
package api

import (
"encoding/json"
"errors"
"fmt"
"strconv"
"strings"
"github.com/attestantio/go-starknet-client/types"
)

// BlockOpts are the options for blocks.
type BlockOpts struct {
Common CommonOpts

// Block is the ID of the block.
// It can be a block number, block hash, or one of the special values "latest" or "pending".
Block string
}

// MarshalJSON marshals to a JSON representation.
func (o *BlockOpts) MarshalJSON() ([]byte, error) {
opts := map[string]any{}
switch {
case o.Block == "latest" || o.Block == "pending":
opts["block_id"] = o.Block
case strings.HasPrefix(o.Block, "0x"):
opts["block_id"] = map[string]any{
"block_hash": o.Block,
}
default:
block, err := strconv.ParseUint(o.Block, 10, 64)
if err != nil {
return nil, errors.New("invalid from block")
}
opts["block_id"] = map[string]any{
"block_number": block,
}
}

return json.Marshal(opts)
}

// String returns a string version of the structure.
func (o *BlockOpts) String() string {
data, err := o.MarshalJSON()
if err != nil {
return fmt.Sprintf("ERR: %v", err)
}

return string(data)
Block types.BlockID
}
6 changes: 3 additions & 3 deletions api/callopts.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,14 @@ type CallOpts struct {

// Block is the block for which the data is obtained.
// It can be a block number, block hash, or one of the special values "latest" or "pending".
Block string
Block types.BlockID

// Contact is the contract for which the data is obtained.
Contract types.Address

// EntryPointSelector defines the entry point for the call.
EntryPointSelector types.FieldElement

// CallData is the data passed to the call.
CallData types.FieldElement
// Calldata is the data passed to the call.
Calldata []types.FieldElement
}
43 changes: 4 additions & 39 deletions api/eventsopts.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,7 @@ package api

import (
"encoding/json"
"errors"
"fmt"
"strconv"
"strings"

"github.com/attestantio/go-starknet-client/types"
)
Expand All @@ -28,12 +25,11 @@ type EventsOpts struct {
Common CommonOpts

// FromBlock is the earliest block from which the events are obtained.
// It can be a block number, block hash, or one of the special values "latest" or "pending".
FromBlock string
FromBlock types.BlockID

// ToBlock is the latest block from which the events are obtained.
// It can be a block number, block hash, or one of the special values "latest" or "pending".
ToBlock string
ToBlock types.BlockID

// Address is the contract address from which the events are obtained.
// If empty then events there is no address filter on returned events.
Expand All @@ -51,41 +47,10 @@ type EventsOpts struct {
// MarshalJSON marshals to a JSON representation.
func (o *EventsOpts) MarshalJSON() ([]byte, error) {
filter := map[string]any{
"to_block": o.FromBlock,
"from_block": o.FromBlock,
"to_block": o.ToBlock,
"chunk_size": o.Limit,
}
switch {
case o.FromBlock == "latest" || o.FromBlock == "pending":
filter["from_block"] = o.FromBlock
case strings.HasPrefix(o.FromBlock, "0x"):
filter["from_block"] = map[string]any{
"block_hash": o.FromBlock,
}
default:
fromBlock, err := strconv.ParseUint(o.FromBlock, 10, 64)
if err != nil {
return nil, errors.New("invalid from block")
}
filter["from_block"] = map[string]any{
"block_number": fromBlock,
}
}
switch {
case o.ToBlock == "latest" || o.ToBlock == "pending":
filter["to_block"] = o.ToBlock
case strings.HasPrefix(o.ToBlock, "0x"):
filter["to_block"] = map[string]any{
"block_hash": o.ToBlock,
}
default:
toBlock, err := strconv.ParseUint(o.ToBlock, 10, 64)
if err != nil {
return nil, errors.New("invalid to block")
}
filter["to_block"] = map[string]any{
"block_number": toBlock,
}
}
if o.Address != nil {
filter["address"] = o.Address.String()
}
Expand Down
3 changes: 1 addition & 2 deletions api/nonceopts.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,7 @@ type NonceOpts struct {
Common CommonOpts

// Block is the block for which the data is obtained.
// It can be a block number, block hash, or one of the special values "latest" or "pending".
Block string
Block types.BlockID

// Contact is the contract for which the data is obtained.
Contract types.Address
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/attestantio/go-starknet-client

go 1.22
go 1.23

require (
github.com/prometheus/client_golang v1.20.4
Expand Down
6 changes: 5 additions & 1 deletion jsonrpc/block.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,12 @@ func (s *Service) Block(ctx context.Context,
return nil, errors.Join(errors.New("no block specified"), client.ErrInvalidOptions)
}

blockOpts := map[string]any{
"block_id": opts.Block,
}

var res spec.Block
err := s.client.CallFor(&res, "starknet_getBlockWithReceipts", opts)
err := s.client.CallFor(&res, "starknet_getBlockWithReceipts", blockOpts)
if err != nil {
return nil, errors.Join(errors.New("starknet_getBlockWithReceipts failed"), err)
}
Expand Down
10 changes: 6 additions & 4 deletions jsonrpc/call.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ package jsonrpc
import (
"context"
"errors"
"fmt"

client "github.com/attestantio/go-starknet-client"
"github.com/attestantio/go-starknet-client/api"
Expand All @@ -42,12 +41,15 @@ func (s *Service) Call(ctx context.Context,
}

callOpts := make(map[string]any)
request := make(map[string]string)
request := make(map[string]any)
request["contract_address"] = opts.Contract.String()
request["entry_point_selector"] = opts.EntryPointSelector.String()
if len(opts.CallData) > 0 {
request["call_data"] = fmt.Sprintf("%#x", opts.CallData)

calldata := make([]string, 0, len(opts.Calldata))
for i := range opts.Calldata {
calldata = append(calldata, opts.Calldata[i].String())
}
request["calldata"] = calldata
callOpts["request"] = request
callOpts["block_id"] = opts.Block

Expand Down
30 changes: 27 additions & 3 deletions jsonrpc/call_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,19 +50,43 @@ func TestCall(t *testing.T) {
{
name: "MultipleReturnElements",
opts: &api.CallOpts{
Block: "latest",
Block: "273028",
Contract: strToAddress("0x0590e76a2e65435b7288bf3526cfa5c3ec7748d2f3433a934c931cce62460fc5"),
EntryPointSelector: strToFieldElement("0x36735aa694184cd8116c479c296d9431cc04a470e0467c07067e4586f647ece"),
},
expected: []types.FieldElement{
strToFieldElement("0xde0b6b3a7640000"),
strToFieldElement("0x4718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d"),
strToFieldElement("0x1f777aef22"),
strToFieldElement("0x2264e064ae"),
strToFieldElement("0x3660fc6334c9485065394f6432933c2f04b4716c67511ba174384c65faebc19"),
strToFieldElement("0x52bf6ec001452dbace0ca4c7db1b232b8031b5a0ccb117bb47a05569df435de"),
strToFieldElement("0x12c"),
},
},
{
name: "CallData",
opts: &api.CallOpts{
Block: "272995",
Contract: strToAddress("0x1d3ec552f7875a008783a1a2706c387ef96067469534458dbfb41003ebecb4f"),
EntryPointSelector: strToFieldElement("0x1af63b1cdc061704af767c81df4fd9eb5a3d989868ff970dd79d5431a89db44"),
Calldata: []types.FieldElement{
strToFieldElement("0x714792a41f3651e171c46c93fc53adeb922a414a891cc36d73029d23e99a6ec"),
},
},
expected: []types.FieldElement{
strToFieldElement("0x391d69afc1b49f01ad8d2e0e8a03756b694dd056fb6645781eb00f33dbd8caf"),
strToFieldElement("0x4a7876e03402cf253efdb3b17c760ee7349c7ec2876059b83ec2c92ca451e16"),
strToFieldElement("0x1"),
strToFieldElement("0xde0b6b3a7640000"),
strToFieldElement("0x2264e064ae"),
strToFieldElement("0x2f4d1ee7e060"),
strToFieldElement("0x0"),
strToFieldElement("0xca4ef197ec48699c2d4c9f9802770fd08743256759f6f0e500c92e2d397cd7"),
strToFieldElement("0x4563918244f40000"),
strToFieldElement("0x0"),
strToFieldElement("0x9c4"),
},
},
}

s, err := jsonrpc.New(ctx,
Expand All @@ -86,7 +110,7 @@ func TestCall(t *testing.T) {
default:
require.NoError(t, err)
require.NotNil(t, response)
require.NotNil(t, test.expected, response.Data)
require.Equal(t, test.expected, response.Data)
}
})
}
Expand Down
2 changes: 1 addition & 1 deletion jsonrpc/events_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ func TestEvents(t *testing.T) {

response, err := s.Events(ctx, &api.EventsOpts{
FromBlock: "1",
ToBlock: "6",
ToBlock: "latest",
Limit: 10,
})
require.NoError(t, err)
Expand Down
6 changes: 5 additions & 1 deletion jsonrpc/nonce.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,12 @@ func (s *Service) Nonce(ctx context.Context,
return nil, errors.Join(errors.New("no block specified"), client.ErrInvalidOptions)
}

nonceOpts := map[string]any{
"block_id": opts.Block,
"contract_address": opts.Contract,
}
var res types.Number
err := s.client.CallFor(&res, "starknet_getNonce", opts.Block, opts.Contract.String())
err := s.client.CallFor(&res, "starknet_getNonce", nonceOpts)
if err != nil {
return nil, errors.Join(err, client.ErrRPCCallFailed)
}
Expand Down
65 changes: 65 additions & 0 deletions types/blockid.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// 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 (
"errors"
"fmt"
"strconv"
"strings"
)

// BlockID is a block identifier.
// It can be a block number, block hash, or one of the special values "latest" or "pending".
type BlockID string

// String returns the string representation of the block ID.
func (b BlockID) String() string {
return string(b)
}

// Format formats the address.
func (b BlockID) Format(state fmt.State, v rune) {
format := string(v)
switch v {
case 'q':
fmt.Fprint(state, `"`+string(b)+`"`)
case 's':
fmt.Fprint(state, string(b))
case 'x', 'X':
if state.Flag('#') {
format = "#" + format
}
fmt.Fprintf(state, "%"+format, b[:])
default:
fmt.Fprintf(state, "%"+format, b[:])
}
}

// MarshalJSON implements json.Marshaler.
func (b BlockID) MarshalJSON() ([]byte, error) {
switch {
case b == "latest" || b == "pending":
return []byte(fmt.Sprintf("%q", b)), nil
case strings.HasPrefix(string(b), "0x"):
return []byte(fmt.Sprintf(`{"block_hash":"%s"}`, b)), nil
default:
block, err := strconv.ParseUint(string(b), 10, 64)
if err != nil {
return nil, errors.New("invalid from block")
}

return []byte(fmt.Sprintf(`{"block_number":%d}`, block)), nil
}
}

0 comments on commit e8d07d1

Please sign in to comment.