Skip to content

Commit

Permalink
Merge branch 'main' into ma/track-profit-in-db
Browse files Browse the repository at this point in the history
  • Loading branch information
mattac21 committed Dec 5, 2024
2 parents a77316f + d1439fd commit 14099d3
Show file tree
Hide file tree
Showing 7 changed files with 110 additions and 51 deletions.
46 changes: 24 additions & 22 deletions hyperlane/ethereum/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,11 @@ import (
"github.com/skip-mev/go-fast-solver/shared/signing/evm"
evmtxexecutor "github.com/skip-mev/go-fast-solver/shared/txexecutor/evm"

ethtypes "github.com/ethereum/go-ethereum/core/types"
interchain_security_module "github.com/skip-mev/go-fast-solver/shared/contracts/hyperlane/InterchainSecurityModule"
mailbox "github.com/skip-mev/go-fast-solver/shared/contracts/hyperlane/Mailbox"
multisig_ism "github.com/skip-mev/go-fast-solver/shared/contracts/hyperlane/MultisigIsm"

ethtypes "github.com/ethereum/go-ethereum/core/types"

"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
Expand Down Expand Up @@ -271,41 +270,44 @@ func (c *HyperlaneClient) Process(ctx context.Context, domain string, message []
}

func (c *HyperlaneClient) QuoteProcessUUSDC(ctx context.Context, domain string, message []byte, metadata []byte) (*big.Int, error) {
destinationMailbox, err := mailbox.NewMailbox(c.mailboxAddress, c.client.Client())
abi, err := mailbox.MailboxMetaData.GetAbi()
if err != nil {
return nil, fmt.Errorf("creating mailbox contract caller for address %s: %w", c.mailboxAddress.String(), err)
return nil, fmt.Errorf("getting mailbox abi: %w", err)
}
destinationChainID, err := config.GetConfigReader(ctx).GetChainIDByHyperlaneDomain(domain)

chainID, err := config.GetConfigReader(ctx).GetChainIDByHyperlaneDomain(domain)
if err != nil {
return nil, fmt.Errorf("getting chainID for hyperlane domain %s: %w", domain, err)
}

signer, err := c.signer(ctx, domain)
addr, err := c.address(ctx, domain)
if err != nil {
return nil, fmt.Errorf("getting signer: %w", err)
return nil, fmt.Errorf("getting address: %w", err)
}

addr, err := c.address(ctx, domain)
data, err := abi.Pack("process", metadata, message)
if err != nil {
return nil, fmt.Errorf("getting address: %w", err)
return nil, fmt.Errorf("packing mailbox process call: %w", err)
}
to := c.mailboxAddress.String()
value := "0"

unsentProcessTx, err := destinationMailbox.Process(&bind.TransactOpts{
From: addr,
Context: ctx,
Signer: evm.EthereumSignerToBindSignerFn(
signer,
destinationChainID,
),
// NoSend dry runs the tx, this will populate the tx with all necessary
// gas estimates from the node needed to get the tx fee in uusdc
NoSend: true,
}, metadata, message)
tx, err := evm.NewTxBuilder(c.client).Build(
ctx,
evm.WithData(data),
evm.WithValue(value),
evm.WithTo(to),
evm.WithChainID(chainID),
evm.WithNonceOfSigner(addr.String()),
evm.WithEstimatedGasLimit(addr.String(), to, value, data),
evm.WithEstimatedGasTipCap(nil),
evm.WithEstimatedGasFeeCap(nil, big.NewFloat(1.5)),
)
if err != nil {
return nil, fmt.Errorf("simulating process tx: %w", err)
return nil, fmt.Errorf("building process tx to simulate: %w", err)
}

txFeeUUSDC, err := c.txPriceOracle.TxFeeUUSDC(ctx, unsentProcessTx)
txFeeUUSDC, err := c.txPriceOracle.TxFeeUUSDC(ctx, tx)
if err != nil {
return nil, fmt.Errorf("getting tx fee in uusdc from gas oracle: %w", err)
}
Expand Down
12 changes: 11 additions & 1 deletion hyperlane/relayer.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ import (
"encoding/hex"
"errors"
"fmt"
"math/big"

dbtypes "github.com/skip-mev/go-fast-solver/db"
"github.com/skip-mev/go-fast-solver/shared/metrics"
"math/big"

"strings"

Expand Down Expand Up @@ -253,6 +254,10 @@ func (r *relayer) checkpointAtIndex(
return multiSigCheckpoint, nil
}

var (
ErrCouldNotDetermineRelayFee = fmt.Errorf("could not determine relay fee")
)

// isRelayFeeLessThanMax simulates a relay of a message and checks that the fee
// to relay the message is less than the users specified max relay fee in uusdc
func (r *relayer) isRelayFeeLessThanMax(
Expand All @@ -264,6 +269,11 @@ func (r *relayer) isRelayFeeLessThanMax(
) (bool, error) {
txFeeUUSDC, err := r.hyperlane.QuoteProcessUUSDC(ctx, domain, message, metadata)
if err != nil {
if strings.Contains(err.Error(), "execution reverted") {
// if the quote process call has reverted, we return a sentinel
// error so that callers can specifically handle this case
return false, ErrCouldNotDetermineRelayFee
}
return false, fmt.Errorf("quoting process call in uusdc: %w", err)
}

Expand Down
43 changes: 29 additions & 14 deletions hyperlane/relayer_runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,15 @@ func (r *RelayerRunner) Run(ctx context.Context) error {
case errors.Is(err, ErrRelayTooExpensive):
lmt.Logger(ctx).Warn(
"relaying transfer is too expensive, waiting for better conditions",
zap.Int64("transferId", transfer.ID),
zap.String("sourceChainID", transfer.SourceChainID),
zap.String("destChainID", transfer.DestinationChainID),
zap.String("txHash", transfer.MessageSentTx),
)
case errors.Is(err, ErrCouldNotDetermineRelayFee):
lmt.Logger(ctx).Warn(
"could not determine relay fee, retrying",
zap.Int64("transferId", transfer.ID),
zap.String("sourceChainID", transfer.SourceChainID),
zap.String("destChainID", transfer.DestinationChainID),
zap.String("txHash", transfer.MessageSentTx),
Expand Down Expand Up @@ -116,7 +125,9 @@ func (r *RelayerRunner) Run(ctx context.Context) error {
lmt.Logger(ctx).Error(
"error relaying pending hyperlane transfer",
zap.Error(err),
zap.Int64("transferId", transfer.ID),
zap.String("sourceChainID", transfer.SourceChainID),
zap.String("destChainID", transfer.DestinationChainID),
zap.String("txHash", transfer.MessageSentTx),
)
}
Expand All @@ -134,7 +145,9 @@ func (r *RelayerRunner) Run(ctx context.Context) error {
lmt.Logger(ctx).Error(
"error inserting submitted tx for hyperlane transfer",
zap.Error(err),
zap.Int64("transferId", transfer.ID),
zap.String("sourceChainID", transfer.SourceChainID),
zap.String("destChainID", transfer.DestinationChainID),
zap.String("txHash", transfer.MessageSentTx),
)
}
Expand Down Expand Up @@ -334,29 +347,31 @@ func (r *RelayerRunner) getRelayCostCap(ctx context.Context, destinationChainID
// if there is a timeout specified, check if the relay is timed out
timeout := createdAt.Add(*destinationChainConfig.Relayer.ProfitableRelayTimeout)
if time.Now().After(timeout) {
// if the relay is timed out, use the relay cost cap only if it is
// higher than the old max tx fee
if maxTxFeeUUSDC.Cmp(relayCostCapUUSDC) > 0 {
lmt.Logger(ctx).Debug(
"relay has timed out and max tx fee derived from profit margin is larger than the relay cost cap. you should consider raising the relay cost cap. the max tx fee from profit margin will still be used.",
zap.String("originialMaxTxFeeUUSDC", maxTxFeeUUSDC.String()),
zap.String("relayCostCapUUSDC", relayCostCapUUSDC.String()),
zap.String("timedOutAt", timeout.UTC().Format(time.RFC3339)),
)
return maxTxFeeUUSDC, nil
}

lmt.Logger(ctx).Debug(
"relay has timed out, setting max tx fee for relay to the relay cost cap",
"relay has timed out setting max tx fee for relay to the relay cost cap",
zap.String("originialMaxTxFeeUUSDC", maxTxFeeUUSDC.String()),
zap.String("relayCostCapUUSDC", relayCostCapUUSDC.String()),
zap.String("timedOutAt", timeout.UTC().Format(time.RFC3339)),
)
// if the relay is timed out, always use the relay cost cap

return relayCostCapUUSDC, nil
}
}

// if the relay is not timed out or no timeout specified, use the min of
// the configured relay cost cap and the max tx fee set by the caller
var maxRelayTxFeeUUSDC *big.Int
if maxTxFeeUUSDC.Cmp(relayCostCapUUSDC) <= 0 {
// use the caller provided max tx fee if it is less than the
// configured relay tx cost cap
maxRelayTxFeeUUSDC = maxTxFeeUUSDC
} else {
maxRelayTxFeeUUSDC = relayCostCapUUSDC
}

return maxRelayTxFeeUUSDC, nil
// if the relay is not timed out, use the max tx fee the user set
return maxTxFeeUUSDC, nil
}

func mostRecentTx(txs []db.SubmittedTx) db.SubmittedTx {
Expand Down
9 changes: 4 additions & 5 deletions shared/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -243,11 +243,10 @@ type RelayerConfig struct {
// profitability. This can be set to -1 for no timeout.
ProfitableRelayTimeout *time.Duration `yaml:"profitable_relay_timeout"`

// RelayCostCapUUSDC is the maximum amount of uusdc to pay to relay a tx,
// regardless of profitability checking, i.e. if the ProfitableRelayTimeout
// expires for a tx and it is going to be sent without ensuring it is
// profitable for the solver to do so, this is a final check to ensure that
// the tx is not relayed in an extremely expensive block.
// RelayCostCapUUSDC can be set in forcing relays through during times of
// high gas usage on chain. If a relay is past its profitable relay timeout
// window, the relay cost cap will be used as the max uusdc value to pay
// for a tx if that value is greater than the profitable max tx fee.
RelayCostCapUUSDC string `yaml:"relay_cost_cap_uusdc"`
}

Expand Down
17 changes: 10 additions & 7 deletions shared/signing/evm/evm_transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ package evm
import (
"context"
"fmt"
"github.com/skip-mev/go-fast-solver/shared/lmt"
"math"
"math/big"

"github.com/skip-mev/go-fast-solver/shared/lmt"

"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
Expand Down Expand Up @@ -150,7 +152,7 @@ func WithEstimatedGasLimit(from, to, value string, data []byte) TxBuildOption {
return fmt.Errorf("estimating gas limit: %w", err)
}

tx.Gas = gasLimit
tx.Gas = uint64(math.Ceil(float64(gasLimit) * 1.2))
return nil
}
}
Expand Down Expand Up @@ -178,7 +180,7 @@ func WithEstimatedGasTipCap(minGasTipCap *big.Int) TxBuildOption {
}
}

func WithEstimatedGasFeeCap(minGasTipCap *big.Int) TxBuildOption {
func WithEstimatedGasFeeCap(minGasTipCap *big.Int, jitter *big.Float) TxBuildOption {
return func(ctx context.Context, b TxBuilder, tx *types.DynamicFeeTx) error {
if tx.GasTipCap == nil {
if err := WithEstimatedGasTipCap(minGasTipCap)(ctx, b, tx); err != nil {
Expand All @@ -191,10 +193,11 @@ func WithEstimatedGasFeeCap(minGasTipCap *big.Int) TxBuildOption {
return fmt.Errorf("getting latest block header: %w", err)
}

tx.GasFeeCap = new(big.Int).Add(
tx.GasTipCap,
new(big.Int).Mul(head.BaseFee, big.NewInt(2)),
)
baseFee := new(big.Float).SetInt(head.BaseFee)
baseFee.Mul(baseFee, jitter)
baseFeeInt, _ := baseFee.Int(nil)

tx.GasFeeCap = new(big.Int).Add(tx.GasTipCap, baseFeeInt)

return nil
}
Expand Down
29 changes: 29 additions & 0 deletions shared/signing/evm/evm_transaction_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package evm_test

import (
"context"
"testing"

"github.com/ethereum/go-ethereum/core/types"
"github.com/skip-mev/go-fast-solver/mocks/shared/evmrpc"
"github.com/skip-mev/go-fast-solver/shared/signing/evm"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)

func TestWithEstimatedGasLimit(t *testing.T) {
t.Run("gas limit is multiplied by 1.2 from what the node returns", func(t *testing.T) {
mockRPC := evmrpc.NewMockEVMChainRPC(t)
mockRPC.EXPECT().EstimateGas(mock.Anything, mock.Anything).Return(100_000, nil)

builder := evm.NewTxBuilder(mockRPC)
opt := evm.WithEstimatedGasLimit("0xfrom", "0xto", "0", []byte("0xdeadbeef"))

tx := &types.DynamicFeeTx{}
err := opt(context.Background(), builder, tx)
assert.NoError(t, err)

gasLimit := types.NewTx(tx).Gas()
assert.Equal(t, uint64(120_000), gasLimit)
})
}
5 changes: 3 additions & 2 deletions shared/txexecutor/evm/evm_tx_executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@ import (
"time"

"fmt"
"math/big"

"github.com/ethereum/go-ethereum/common"
"github.com/skip-mev/go-fast-solver/shared/config"
"github.com/skip-mev/go-fast-solver/shared/evmrpc"
"github.com/skip-mev/go-fast-solver/shared/signing"
"github.com/skip-mev/go-fast-solver/shared/signing/evm"
"golang.org/x/net/context"
"math/big"
)

type EVMTxExecutor interface {
Expand Down Expand Up @@ -87,7 +88,7 @@ func (s *SerializedEVMTxExecutor) ExecuteTx(
evm.WithNonce(nonce),
evm.WithEstimatedGasLimit(signerAddress, to, value, data),
evm.WithEstimatedGasTipCap(minGasTipCap),
evm.WithEstimatedGasFeeCap(minGasTipCap),
evm.WithEstimatedGasFeeCap(minGasTipCap, big.NewFloat(2)),
)
signedTx, err := signer.Sign(ctx, chainID, tx)
if err != nil {
Expand Down

0 comments on commit 14099d3

Please sign in to comment.