diff --git a/pkg/eth/simulator.go b/pkg/eth/simulator.go index 14c86b2..3c2656d 100644 --- a/pkg/eth/simulator.go +++ b/pkg/eth/simulator.go @@ -5,6 +5,7 @@ import ( "fmt" "math/big" + "github.com/KyberNetwork/tradinglib/pkg/mev" "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" @@ -28,33 +29,13 @@ func (s *Simulator) EstimateGasWithOverrides( ) (uint64, error) { var hex hexutil.Uint64 err := s.c.CallContext( - ctx, &hex, "eth_estimateGas", toCallArg(msg), + ctx, &hex, "eth_estimateGas", mev.ToCallArg(msg), toBlockNumArg(blockNumber), overrides, ) return uint64(hex), err } -func toCallArg(msg ethereum.CallMsg) interface{} { - arg := map[string]interface{}{ - "from": msg.From, - "to": msg.To, - } - if len(msg.Data) > 0 { - arg["input"] = hexutil.Bytes(msg.Data) - } - if msg.Value != nil { - arg["value"] = (*hexutil.Big)(msg.Value) - } - if msg.Gas != 0 { - arg["gas"] = hexutil.Uint64(msg.Gas) - } - if msg.GasPrice != nil { - arg["gasPrice"] = (*hexutil.Big)(msg.GasPrice) - } - return arg -} - func toBlockNumArg(number *big.Int) string { if number == nil { return "latest" diff --git a/pkg/mev/blxr_bundle_sender.go b/pkg/mev/blxr_bundle_sender.go index 1e12819..09d7c2e 100644 --- a/pkg/mev/blxr_bundle_sender.go +++ b/pkg/mev/blxr_bundle_sender.go @@ -9,8 +9,10 @@ import ( "net/http" "strconv" + "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethclient/gethclient" "github.com/flashbots/mev-share-node/mevshare" ) @@ -32,6 +34,14 @@ type BloxrouteClient struct { enabledBuilders []BlxrBuilder } +func (s *BloxrouteClient) EstimateBundleGas( + _ context.Context, + _ []ethereum.CallMsg, + _ *map[common.Address]gethclient.OverrideAccount, +) ([]uint64, error) { + return nil, nil +} + func (s *BloxrouteClient) MevSimulateBundle( _ uint64, _ common.Hash, diff --git a/pkg/mev/bundle_sender.go b/pkg/mev/bundle_sender.go index d86f029..0a4ad99 100644 --- a/pkg/mev/bundle_sender.go +++ b/pkg/mev/bundle_sender.go @@ -9,11 +9,13 @@ import ( "net/http" "github.com/duoxehyon/mev-share-go/rpc" + "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethclient/gethclient" "github.com/flashbots/mev-share-node/mevshare" ) @@ -26,7 +28,8 @@ type Client struct { cancelBySendBundle bool senderType BundleSenderType // mevShareClient is the client for mev-share flashbots node - mevShareClient rpc.MevAPIClient + mevShareClient rpc.MevAPIClient + gasBundleEstimator IGasBundleEstimator } // NewClient set the flashbotKey to nil will skip adding the signature header. @@ -36,7 +39,8 @@ func NewClient( flashbotKey *ecdsa.PrivateKey, cancelBySendBundle bool, senderType BundleSenderType, -) *Client { + gasBundleEstimator IGasBundleEstimator, +) (*Client, error) { var mevShareClient rpc.MevAPIClient if flashbotKey != nil { mevShareClient = rpc.NewClient(endpoint, flashbotKey) @@ -49,7 +53,8 @@ func NewClient( cancelBySendBundle: cancelBySendBundle, senderType: senderType, mevShareClient: mevShareClient, - } + gasBundleEstimator: gasBundleEstimator, + }, nil } func (s *Client) GetSenderType() BundleSenderType { @@ -124,6 +129,14 @@ func (s *Client) flashbotBackrunSendBundle( return res, err } +func (s *Client) EstimateBundleGas( + ctx context.Context, + messages []ethereum.CallMsg, + overrides *map[common.Address]gethclient.OverrideAccount, +) ([]uint64, error) { + return s.gasBundleEstimator.EstimateBundleGas(ctx, messages, overrides) +} + func (s *Client) MevSimulateBundle( blockNumber uint64, pendingTxHash common.Hash, diff --git a/pkg/mev/bundle_sender_test.go b/pkg/mev/bundle_sender_test.go index 7897fc8..b63ff26 100644 --- a/pkg/mev/bundle_sender_test.go +++ b/pkg/mev/bundle_sender_test.go @@ -66,7 +66,11 @@ func TestSendBundle(t *testing.T) { t.Log("new tx", signedTx.Hash().String()) uuid := uuid.NewString() - sender := mev.NewClient(client, endpoint, privateKey, false, mev.BundleSenderTypeFlashbot) + ethClient, err = ethclient.Dial(endpoint) + require.NoError(t, err) + gasBundleEstimator := mev.NewGasBundleEstimator(ethClient.Client()) + sender, err := mev.NewClient(client, endpoint, privateKey, false, mev.BundleSenderTypeFlashbot, gasBundleEstimator) + require.NoError(t, err) resp, err := sender.SendBundle(ctx, &uuid, blockNumber+12, signedTx) require.NoError(t, err) // sepolia: code: [-32000], message: [internal server error] @@ -95,7 +99,12 @@ func TestCancelBeaver(t *testing.T) { bundleUUID = uuid.New().String() ) - sender := mev.NewClient(client, endpoint, nil, true, mev.BundleSenderTypeBeaver) + ethClient, err := ethclient.Dial(endpoint) + require.NoError(t, err) + gasBundleEstimator := mev.NewGasBundleEstimator(ethClient.Client()) + + sender, err := mev.NewClient(client, endpoint, nil, true, mev.BundleSenderTypeBeaver, gasBundleEstimator) + require.NoError(t, err) require.NoError(t, sender.CancelBundle(ctx, bundleUUID)) } @@ -129,10 +138,15 @@ func Test_SimulateBundle(t *testing.T) { txs = append(txs, &tx) } - var ( - simulationEndpoint = "http://localhost:8545" - client = mev.NewClient(http.DefaultClient, simulationEndpoint, nil, false, mev.BundleSenderTypeFlashbot) - ) + simulationEndpoint := "http://localhost:8545" + ethClient, err := ethclient.Dial(simulationEndpoint) + require.NoError(t, err) + gasBundleEstimator := mev.NewGasBundleEstimator(ethClient.Client()) + + client, err := mev.NewClient(http.DefaultClient, + simulationEndpoint, nil, false, + mev.BundleSenderTypeFlashbot, gasBundleEstimator) + require.NoError(t, err) simulationResponse, err := client.SimulateBundle(context.Background(), uint64(blockNumber), txs...) require.NoError(t, err) diff --git a/pkg/mev/gas_bundle_estimator.go b/pkg/mev/gas_bundle_estimator.go new file mode 100644 index 0000000..0784399 --- /dev/null +++ b/pkg/mev/gas_bundle_estimator.go @@ -0,0 +1,50 @@ +package mev + +import ( + "context" + + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/ethclient/gethclient" + "github.com/ethereum/go-ethereum/rpc" +) + +type GasBundleEstimator struct { + client *rpc.Client +} + +func NewGasBundleEstimator(client *rpc.Client) GasBundleEstimator { + return GasBundleEstimator{ + client: client, + } +} + +func (g GasBundleEstimator) EstimateBundleGas( + _ context.Context, + messages []ethereum.CallMsg, + overrides *map[common.Address]gethclient.OverrideAccount, +) ([]uint64, error) { + bundles := make([]interface{}, 0, len(messages)) + for _, msg := range messages { + bundles = append(bundles, ToCallArg(msg)) + } + + var gasEstimateCost []hexutil.Uint64 + + err := g.client.Call( + &gasEstimateCost, ETHEstimateGasBundleMethod, + map[string]interface{}{ + "transactions": bundles, + }, "latest", overrides, + ) + if err != nil { + return nil, err + } + result := make([]uint64, 0, len(gasEstimateCost)) + + for _, gasEstimate := range gasEstimateCost { + result = append(result, uint64(gasEstimate)) + } + return result, nil +} diff --git a/pkg/mev/pkg.go b/pkg/mev/pkg.go index 9d39849..0b8cb19 100644 --- a/pkg/mev/pkg.go +++ b/pkg/mev/pkg.go @@ -9,8 +9,11 @@ import ( "io" "net/http" + "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethclient/gethclient" "github.com/flashbots/mev-share-node/mevshare" ) @@ -33,6 +36,7 @@ const ( ETHSendBundleMethod = "eth_sendBundle" EthCallBundleMethod = "eth_callBundle" ETHCancelBundleMethod = "eth_cancelBundle" + ETHEstimateGasBundleMethod = "eth_estimateGasBundle" MevSendBundleMethod = "mev_sendBundle" MaxBlockFromTarget = 3 ) @@ -55,6 +59,11 @@ type IBundleSender interface { ctx context.Context, bundleUUID string, ) error SimulateBundle(ctx context.Context, blockNumber uint64, txs ...*types.Transaction) (SendBundleResponse, error) + EstimateBundleGas( + ctx context.Context, + messages []ethereum.CallMsg, + overrides *map[common.Address]gethclient.OverrideAccount, + ) ([]uint64, error) MevSimulateBundle( blockNumber uint64, pendingTxHash common.Hash, @@ -62,6 +71,14 @@ type IBundleSender interface { GetSenderType() BundleSenderType } +type IGasBundleEstimator interface { + EstimateBundleGas( + ctx context.Context, + messages []ethereum.CallMsg, + overrides *map[common.Address]gethclient.OverrideAccount, + ) ([]uint64, error) +} + var ( _ IBundleSender = &Client{} _ IBundleSender = &BloxrouteClient{} @@ -174,3 +191,28 @@ type TitanCancelBundleResponse struct { Result int `json:"result,omitempty"` Error SendBundleError `json:"error,omitempty"` } + +func ToCallArg(msg ethereum.CallMsg) interface{} { + arg := map[string]interface{}{ + "from": msg.From, + "to": msg.To, + } + if len(msg.Data) > 0 { + arg["input"] = hexutil.Bytes(msg.Data) + } + if msg.Value != nil { + arg["value"] = (*hexutil.Big)(msg.Value) + } + if msg.Gas != 0 { + arg["gas"] = hexutil.Uint64(msg.Gas) + } + if msg.GasPrice != nil { + arg["gasPrice"] = (*hexutil.Big)(msg.GasPrice) + } + return arg +} + +func GetFrom(tx *types.Transaction) (common.Address, error) { + from, err := types.Sender(types.LatestSignerForChainID(tx.ChainId()), tx) + return from, err +}