ethergo/backends/simulated/chain.go
package simulated
import (
"context"
"fmt"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rpc"
"github.com/lmittmann/w3/w3types"
"github.com/synapsecns/sanguine/ethergo/backends/simulated/multibackend"
"github.com/synapsecns/sanguine/ethergo/chain/client"
"golang.org/x/sync/errgroup"
"math/big"
)
// Client is a simulated client for a simulated backend.
type Client struct {
*multibackend.SimulatedBackend
}
// Web3Version is not implemented on this backend.
func (s Client) Web3Version(_ context.Context) (version string, err error) {
// TODO implement me
panic("cannot implement on this backend")
}
// FeeHistory is not implemented on this backend.
func (s Client) FeeHistory(_ context.Context, _ uint64, _ *big.Int, _ []float64) (*ethereum.FeeHistory, error) {
// TODO implement me
panic("cannot implement on this backend")
}
// PendingBalanceAt calls balance at since simulated backends are monotonic.
func (s Client) PendingBalanceAt(ctx context.Context, account common.Address) (*big.Int, error) {
//nolint: wrapcheck
return s.SimulatedBackend.BalanceAt(ctx, account, nil)
}
// PendingStorageAt gets the storage at since simulated backends cannot have non-final storage.
func (s Client) PendingStorageAt(ctx context.Context, account common.Address, key common.Hash) ([]byte, error) {
//nolint: wrapcheck
return s.SimulatedBackend.StorageAt(ctx, account, key, nil)
}
// PendingTransactionCount always returns 0 since simulated backends cannot have pending transactions.
func (s Client) PendingTransactionCount(ctx context.Context) (uint, error) {
return 0, nil
}
// SyncProgress panics since this state is not accessible on the simulated backend.
func (s Client) SyncProgress(ctx context.Context) (*ethereum.SyncProgress, error) {
panic("not implemented")
}
// NetworkID wraps network id on underlying backend.
func (s Client) NetworkID(ctx context.Context) (*big.Int, error) {
//nolint: errwrap
return s.ChainID(ctx)
}
// ChainConfig gets the chain config for the backend.
func (s Client) ChainConfig() *params.ChainConfig {
return s.Blockchain().Config()
}
// ChainID returns the chain id.
func (s Client) ChainID(_ context.Context) (*big.Int, error) {
return s.ChainConfig().ChainID, nil
}
// Close closes the connection with the chain.
func (s Client) Close() {
// do nothing
}
// CallContext panics here to bypass interface requirements for testing.
func (s Client) CallContext(ctx context.Context, result interface{}, method string, args ...interface{}) error {
panic("CallContext is not supported on the simulated backend")
}
// BatchCallContext panics here to bypass interface requirements for testing.
func (s Client) BatchCallContext(ctx context.Context, b []rpc.BatchElem) error {
panic("BatchCallContext is not supported on the simulated backend")
}
// BatchContext panics here to bypass interface requirements for testing.
func (s Client) BatchContext(ctx context.Context, calls ...w3types.Caller) error {
panic("BatchCallContext is not supported on the simulated backend")
}
// BlockNumber gets the latest block number.
func (s Client) BlockNumber(ctx context.Context) (uint64, error) {
latestBlock, err := s.BlockByNumber(ctx, nil)
if err != nil {
return 0, fmt.Errorf("could not get block: %w", err)
}
return latestBlock.NumberU64(), nil
}
// SuggestGasPrice follows the rpc behavior for SuggestGasPrice. Because we rely
// on the legacy behavior of eth_sendTransaction and don't utilize the base fee (we need to figure
// out a way to do this cross-chain), we emulate the rpc eth_sugestGasPrice behavior here.
// TODO: find out if not emulating the rpc here is intended behavior on geth's end, patch via pr if not.
func (s Client) SuggestGasPrice(ctx context.Context) (gasPrice *big.Int, err error) {
g, ctx := errgroup.WithContext(ctx)
var (
// baseFee is the base fee to add to the gas price estimation
baseFee *big.Int
// estimatedPrice is the gas price estimate
estimatedPrice *big.Int
)
g.Go(func() error {
latestBlock, err := s.BlockByNumber(ctx, nil)
if err != nil {
return fmt.Errorf("could not get latest block to add to base fee: %w", err)
}
baseFee = latestBlock.BaseFee()
return nil
})
g.Go(func() error {
estimatedPrice, err = s.SimulatedBackend.SuggestGasPrice(ctx)
if err != nil {
return fmt.Errorf("could not get suggested gas price")
}
return nil
})
err = g.Wait()
if err != nil {
return nil, fmt.Errorf("could not get gas price: %w", err)
}
if baseFee == nil {
return estimatedPrice, nil
}
return big.NewInt(0).Add(estimatedPrice, baseFee), nil
}
var _ client.EVMClient = &Client{}