Skip to content

Commit a281822

Browse files
committed
Add base fee and estimate gas.
1 parent 2d72751 commit a281822

File tree

7 files changed

+456
-0
lines changed

7 files changed

+456
-0
lines changed

jsonrpc/basefee.go

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
// Copyright © 2023 Attestant Limited.
2+
// Licensed under the Apache License, Version 2.0 (the "License");
3+
// you may not use this file except in compliance with the License.
4+
// You may obtain a copy of the License at
5+
//
6+
// http://www.apache.org/licenses/LICENSE-2.0
7+
//
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
14+
package jsonrpc
15+
16+
import (
17+
"context"
18+
"math/big"
19+
"strconv"
20+
"strings"
21+
22+
"github.com/attestantio/go-execution-client/util"
23+
"github.com/pkg/errors"
24+
)
25+
26+
type feeHistory struct {
27+
BaseFeePerGas []string `json:"baseFeePerGas"`
28+
}
29+
30+
// BaseFee provides the base fee of the chain at the given block ID.
31+
func (s *Service) BaseFee(_ context.Context,
32+
blockID string,
33+
) (
34+
*big.Int,
35+
error,
36+
) {
37+
if blockID == "" {
38+
return nil, errors.New("block ID not specified")
39+
}
40+
41+
pending := false
42+
if blockID == "pending" {
43+
pending = true
44+
blockID = "latest"
45+
}
46+
47+
// Convert decimal heights to hex.
48+
_, isIdentifier := blockIdentifiers[blockID]
49+
if !strings.HasPrefix(blockID, "0x") && !isIdentifier {
50+
tmp, err := strconv.ParseInt(blockID, 10, 64)
51+
if err != nil {
52+
return nil, errors.Wrap(err, "unhandled block ID")
53+
}
54+
blockID = util.MarshalInt64(tmp)
55+
}
56+
57+
res := feeHistory{}
58+
if err := s.client.CallFor(&res, "eth_feeHistory", "0x1", blockID, []float64{0}); err != nil {
59+
return nil, errors.Wrap(err, "call to eth_feeHistory failed")
60+
}
61+
if len(res.BaseFeePerGas) == 0 {
62+
return nil, errors.New("no data returned")
63+
}
64+
65+
var baseFeePerGasStr string
66+
if pending {
67+
if len(res.BaseFeePerGas) < 2 {
68+
return nil, errors.New("no pending data returned")
69+
}
70+
baseFeePerGasStr = res.BaseFeePerGas[1]
71+
} else {
72+
baseFeePerGasStr = res.BaseFeePerGas[0]
73+
}
74+
75+
baseFeePerGas, err := util.StrToBigInt("baseFeePerGas", baseFeePerGasStr)
76+
if err != nil {
77+
return nil, err
78+
}
79+
80+
return baseFeePerGas, nil
81+
}

jsonrpc/basefee_test.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
// Copyright © 2023 Attestant Limited.
2+
// Licensed under the Apache License, Version 2.0 (the "License");
3+
// you may not use this file except in compliance with the License.
4+
// You may obtain a copy of the License at
5+
//
6+
// http://www.apache.org/licenses/LICENSE-2.0
7+
//
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
14+
package jsonrpc_test
15+
16+
import (
17+
"context"
18+
"os"
19+
"testing"
20+
21+
execclient "github.com/attestantio/go-execution-client"
22+
"github.com/attestantio/go-execution-client/jsonrpc"
23+
"github.com/rs/zerolog"
24+
"github.com/stretchr/testify/require"
25+
)
26+
27+
func TestBaseFee(t *testing.T) {
28+
ctx := context.Background()
29+
s, err := jsonrpc.New(ctx,
30+
jsonrpc.WithLogLevel(zerolog.Disabled),
31+
jsonrpc.WithAddress(os.Getenv("JSONRPC_ADDRESS")),
32+
jsonrpc.WithTimeout(timeout),
33+
)
34+
require.NoError(t, err)
35+
36+
tests := []struct {
37+
name string
38+
blockID string
39+
err string
40+
}{
41+
{
42+
name: "Implicit",
43+
},
44+
{
45+
name: "Latest",
46+
},
47+
{
48+
name: "15100",
49+
blockID: "15100",
50+
},
51+
{
52+
name: "0x3afc",
53+
blockID: "0x3afc",
54+
},
55+
}
56+
57+
for _, test := range tests {
58+
t.Run(test.name, func(t *testing.T) {
59+
_, err := s.(execclient.BaseFeeProvider).BaseFee(ctx, test.blockID)
60+
if test.err != "" {
61+
require.EqualError(t, err, test.err)
62+
} else {
63+
require.NoError(t, err)
64+
}
65+
})
66+
}
67+
}

jsonrpc/estimategas.go

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
// Copyright © 2023 Attestant Limited.
2+
// Licensed under the Apache License, Version 2.0 (the "License");
3+
// you may not use this file except in compliance with the License.
4+
// You may obtain a copy of the License at
5+
//
6+
// http://www.apache.org/licenses/LICENSE-2.0
7+
//
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
14+
package jsonrpc
15+
16+
import (
17+
"context"
18+
"encoding/json"
19+
"fmt"
20+
"math/big"
21+
22+
"github.com/attestantio/go-execution-client/spec"
23+
"github.com/attestantio/go-execution-client/util"
24+
"github.com/pkg/errors"
25+
)
26+
27+
// EstimateGas estimates the gas required for a transaction.
28+
func (s *Service) EstimateGas(_ context.Context,
29+
tx *spec.TransactionSubmission,
30+
) (
31+
*big.Int,
32+
error,
33+
) {
34+
if tx == nil {
35+
return nil, errors.New("no transaction specified")
36+
}
37+
38+
opts := make(map[string]any)
39+
opts["type"] = tx.Type.String()
40+
if tx.ChainID != nil {
41+
opts["chainId"] = util.MarshalBigInt(tx.ChainID)
42+
}
43+
opts["from"] = tx.From.String()
44+
if tx.Nonce != nil {
45+
opts["nonce"] = util.MarshalUint64(*tx.Nonce)
46+
}
47+
if tx.To != nil {
48+
opts["to"] = tx.To.String()
49+
}
50+
if tx.Input != nil {
51+
opts["input"] = fmt.Sprintf("%#x", tx.Input)
52+
}
53+
if tx.Gas != nil {
54+
opts["gas"] = util.MarshalBigInt(tx.Gas)
55+
}
56+
if tx.GasPrice != nil {
57+
opts["gasPrice"] = util.MarshalBigInt(tx.GasPrice)
58+
}
59+
if tx.MaxPriorityFeePerGas != nil {
60+
opts["maxPriorityFeePerGas"] = util.MarshalBigInt(tx.MaxPriorityFeePerGas)
61+
}
62+
if tx.MaxFeePerGas != nil {
63+
opts["maxFeePerGas"] = util.MarshalBigInt(tx.MaxFeePerGas)
64+
}
65+
if tx.MaxFeePerBlobGas != nil {
66+
opts["maxFeePerBlobGas"] = util.MarshalBigInt(tx.MaxFeePerBlobGas)
67+
}
68+
if tx.Value != nil {
69+
opts["value"] = util.MarshalBigInt(tx.Value)
70+
}
71+
if tx.AccessList != nil {
72+
accessList, err := json.Marshal(tx.AccessList)
73+
if err != nil {
74+
return nil, err
75+
}
76+
opts["accessList"] = string(accessList)
77+
}
78+
if tx.BlobVersionedHashes != nil {
79+
blobVersionedHashes, err := json.Marshal(tx.BlobVersionedHashes)
80+
if err != nil {
81+
return nil, err
82+
}
83+
opts["blobVersionedHashes"] = string(blobVersionedHashes)
84+
}
85+
if tx.Blobs != nil {
86+
blobs, err := json.Marshal(tx.Blobs)
87+
if err != nil {
88+
return nil, err
89+
}
90+
opts["blobs"] = string(blobs)
91+
}
92+
93+
var gasStr string
94+
if err := s.client.CallFor(&gasStr, "eth_estimateGas", opts, "latest"); err != nil {
95+
return nil, errors.Wrap(err, "call to eth_estimateGas failed")
96+
}
97+
98+
gas, err := util.StrToBigInt("gas", gasStr)
99+
if err != nil {
100+
return nil, err
101+
}
102+
103+
return gas, nil
104+
}

jsonrpc/estimategas_test.go

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
// Copyright © 2023 Attestant Limited.
2+
// Licensed under the Apache License, Version 2.0 (the "License");
3+
// you may not use this file except in compliance with the License.
4+
// You may obtain a copy of the License at
5+
//
6+
// http://www.apache.org/licenses/LICENSE-2.0
7+
//
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
14+
package jsonrpc_test
15+
16+
import (
17+
"context"
18+
"math/big"
19+
"os"
20+
"testing"
21+
22+
execclient "github.com/attestantio/go-execution-client"
23+
"github.com/attestantio/go-execution-client/jsonrpc"
24+
"github.com/attestantio/go-execution-client/spec"
25+
"github.com/attestantio/go-execution-client/types"
26+
"github.com/rs/zerolog"
27+
"github.com/stretchr/testify/require"
28+
)
29+
30+
func TestEstimateGas(t *testing.T) {
31+
ctx := context.Background()
32+
s, err := jsonrpc.New(ctx,
33+
jsonrpc.WithLogLevel(zerolog.Disabled),
34+
jsonrpc.WithAddress(os.Getenv("JSONRPC_ADDRESS")),
35+
jsonrpc.WithTimeout(timeout),
36+
)
37+
require.NoError(t, err)
38+
39+
tests := []struct {
40+
name string
41+
tx *spec.TransactionSubmission
42+
err string
43+
}{
44+
{
45+
name: "Type0Transfer",
46+
tx: &spec.TransactionSubmission{
47+
Type: spec.TransactionType0,
48+
From: types.Address{},
49+
To: &types.Address{},
50+
},
51+
},
52+
{
53+
name: "Type0ContractCreation",
54+
tx: &spec.TransactionSubmission{
55+
Type: spec.TransactionType0,
56+
From: types.Address{},
57+
Gas: big.NewInt(100000),
58+
},
59+
},
60+
}
61+
62+
for _, test := range tests {
63+
t.Run(test.name, func(t *testing.T) {
64+
_, err := s.(execclient.GasEstimationProvide).EstimateGas(ctx, test.tx)
65+
if test.err != "" {
66+
require.EqualError(t, err, test.err)
67+
} else {
68+
require.NoError(t, err)
69+
}
70+
})
71+
}
72+
}

service.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,12 @@ type Service interface {
3232
Address() string
3333
}
3434

35+
// BaseFeeProvider is the interface for providing the base fee.
36+
type BaseFeeProvider interface {
37+
// BaseFee provides the base fee of the chain at the given block ID.
38+
BaseFee(ctx context.Context, blockID string) (*big.Int, error)
39+
}
40+
3541
// BalancesProvider is the interface for providing balances.
3642
type BalancesProvider interface {
3743
// Balance obtains the balance for the given address at the given block ID.
@@ -68,6 +74,12 @@ type EventsProvider interface {
6874
Events(ctx context.Context, filter *api.EventsFilter) ([]*spec.BerlinTransactionEvent, error)
6975
}
7076

77+
// GasEstimationProvide is the interface for providing gas estimations.
78+
type GasEstimationProvide interface {
79+
// EstimateGas estimates the gas required for a transaction.
80+
EstimateGas(ctx context.Context, tx *spec.TransactionSubmission) (*big.Int, error)
81+
}
82+
7183
// IssuanceProvider is the interface for providing issuance.
7284
type IssuanceProvider interface {
7385
// Issuance returns the issuance of a block.

spec/transactionsubmission.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// Copyright © 2023 Attestant Limited.
2+
// Licensed under the Apache License, Version 2.0 (the "License");
3+
// you may not use this file except in compliance with the License.
4+
// You may obtain a copy of the License at
5+
//
6+
// http://www.apache.org/licenses/LICENSE-2.0
7+
//
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
14+
package spec
15+
16+
import (
17+
"math/big"
18+
19+
"github.com/attestantio/go-execution-client/types"
20+
)
21+
22+
// TransactionSubmission provides the partial details of a transaction to submit to the network.
23+
type TransactionSubmission struct {
24+
Type TransactionType
25+
ChainID *big.Int
26+
From types.Address
27+
Nonce *uint64
28+
To *types.Address
29+
Input []byte
30+
Gas *big.Int
31+
GasPrice *big.Int
32+
MaxFeePerGas *big.Int
33+
MaxPriorityFeePerGas *big.Int
34+
MaxFeePerBlobGas *big.Int
35+
Value *big.Int
36+
AccessList []*AccessListEntry
37+
BlobVersionedHashes []types.VersionedHash
38+
Blobs []types.Blob
39+
}

0 commit comments

Comments
 (0)