synapsecns/sanguine

View on GitHub
ethergo/backends/simulated/chain.go

Summary

Maintainability
A
45 mins
Test Coverage
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{}