Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(rfq-relayer): fee pricer considers v2 CallValue and CallParams [SLT-320] #3299

Merged
merged 36 commits into from
Dec 6, 2024
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
1c7138e
WIP: incorporate call params into fee
dwasse Oct 15, 2024
76c1d6f
WIP: decompose getFee()
dwasse Oct 15, 2024
8aeb626
Feat: account for CallValue in getDestinationFee()
dwasse Oct 15, 2024
a42cef9
Fix: build
dwasse Oct 15, 2024
43da983
Feat: test call value and call param calculation in fee pricer
dwasse Oct 16, 2024
73531fd
Feat: add context on request body in rpc fwd errs
dwasse Oct 17, 2024
9baed17
Fix: zap estimate gas
dwasse Oct 18, 2024
b123b0f
Cleanup: move gas estimate into own func
dwasse Oct 18, 2024
4e448e8
Fix: quoter tests
dwasse Oct 18, 2024
62d16f7
Cleanup: lint
dwasse Oct 21, 2024
1376b7b
Cleanup: lint
dwasse Oct 21, 2024
2620aaf
Fix: tests
dwasse Oct 21, 2024
32df025
Cleanup: decompose func
dwasse Oct 21, 2024
b9cd947
Merge branch 'feat/relayer-arb-call' into feat/arb-call-fee-pricer
dwasse Oct 21, 2024
cfb8d3c
Cleanup: lint
dwasse Oct 21, 2024
cebce1e
Fix: tests
dwasse Oct 21, 2024
3186071
Cleanup: lint
dwasse Oct 21, 2024
af05543
Feat: always use quote fee multiplier
dwasse Oct 24, 2024
ae348be
WIP: abi encode pack relay()
dwasse Oct 24, 2024
842a0f6
Merge branch 'feat/relayer-arb-call' into feat/arb-call-fee-pricer
dwasse Oct 25, 2024
f8d6d4f
Feat: pass full RawRequest for gas estimation
dwasse Oct 25, 2024
9de9cfb
Cleanup: lint
dwasse Oct 25, 2024
a97da60
Fix: pricer tests
dwasse Oct 25, 2024
308e66b
Feat: ignore static l2 fee when incorporating call params
dwasse Oct 25, 2024
d081602
Fix: tests
dwasse Oct 25, 2024
7e6e1fc
Clarifying comment
dwasse Oct 25, 2024
596d105
Feat: add extra check for call param len
dwasse Oct 25, 2024
7a72ad4
Merge branch 'feat/relayer-arb-call' into feat/arb-call-fee-pricer
dwasse Nov 14, 2024
9fde9a8
Attempt to fix flake
dwasse Nov 14, 2024
a9b3a18
Cleanup: lint
dwasse Nov 14, 2024
2a132a8
Merge branch 'feat/relayer-arb-call' into feat/arb-call-fee-pricer
dwasse Nov 14, 2024
9833e0d
Fix: build
dwasse Nov 14, 2024
7d47ab0
Merge branch 'feat/relayer-arb-call' into feat/arb-call-fee-pricer
dwasse Nov 14, 2024
f93b3b3
feat(rfq-relayer): apply zap fee to dest amount for active quotes [SL…
dwasse Nov 14, 2024
2510a2b
fix(rfq-relayer): gas estimation for zaps (#3413)
dwasse Dec 3, 2024
d9db854
Merge branch 'feat/relayer-arb-call' into feat/arb-call-fee-pricer
parodime Dec 6, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions services/omnirpc/proxy/forward.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
"context"
"crypto/sha256"
"fmt"
goHTTP "net/http"
"strings"

"github.com/ImVexed/fasturl"
"github.com/goccy/go-json"
"github.com/jftuga/ellipsis"
Expand All @@ -14,8 +17,6 @@
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace"
"golang.org/x/exp/slices"
goHTTP "net/http"
"strings"
)

type rawResponse struct {
Expand Down Expand Up @@ -56,7 +57,7 @@

standardizedResponse, err = standardizeResponse(ctx, &f.rpcRequest[0], rpcMessage)
if err != nil {
return nil, fmt.Errorf("could not standardize response: %w", err)
return nil, fmt.Errorf("could not standardize response from body %s: %w", ellipsis.Shorten(string(body), 200), err)

Check warning on line 60 in services/omnirpc/proxy/forward.go

View check run for this annotation

Codecov / codecov/patch

services/omnirpc/proxy/forward.go#L60

Added line #L60 was not covered by tests
}
}

Expand Down
143 changes: 124 additions & 19 deletions services/rfq/relayer/pricer/fee_pricer.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,16 @@ import (
"context"
"fmt"
"math/big"
"strings"
"time"

"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/jellydator/ttlcache/v3"
"github.com/synapsecns/sanguine/core/metrics"
"github.com/synapsecns/sanguine/ethergo/submitter"
"github.com/synapsecns/sanguine/services/rfq/contracts/fastbridgev2"
"github.com/synapsecns/sanguine/services/rfq/relayer/relconfig"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace"
Expand All @@ -22,9 +27,9 @@ type FeePricer interface {
// GetOriginFee returns the total fee for a given chainID and gas limit, denominated in a given token.
GetOriginFee(ctx context.Context, origin, destination uint32, denomToken string, isQuote bool) (*big.Int, error)
// GetDestinationFee returns the total fee for a given chainID and gas limit, denominated in a given token.
GetDestinationFee(ctx context.Context, origin, destination uint32, denomToken string, isQuote bool) (*big.Int, error)
GetDestinationFee(ctx context.Context, origin, destination uint32, denomToken string, isQuote bool, tx *fastbridgev2.IFastBridgeV2BridgeTransactionV2) (*big.Int, error)
// GetTotalFee returns the total fee for a given origin and destination chainID, denominated in a given token.
GetTotalFee(ctx context.Context, origin, destination uint32, denomToken string, isQuote bool) (*big.Int, error)
GetTotalFee(ctx context.Context, origin, destination uint32, denomToken string, isQuote bool, tx *fastbridgev2.IFastBridgeV2BridgeTransactionV2) (*big.Int, error)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codebase verification

Potential Breaking Changes Confirmed

The FeePricer interface in services/rfq/relayer/pricer/fee_pricer.go has not been updated to include the new tx *fastbridgev2.IFastBridgeV2BridgeTransactionV2 parameter in the GetDestinationFee and GetTotalFee methods. However, several implementations and callers are already passing this new parameter, which will lead to compilation errors.

Action Items:

  • Update the FeePricer interface to include the tx parameter in both GetDestinationFee and GetTotalFee methods.
  • Ensure all implementations of the FeePricer interface are updated accordingly.
  • Verify all callers of these methods pass the required tx argument to prevent breaking changes.

Category:

🔗 Analysis chain

Potential Breaking Changes Due to Interface Modification

The method signatures for GetDestinationFee and GetTotalFee in the FeePricer interface have been updated to include a new parameter tx *fastbridgev2.IFastBridgeV2BridgeTransactionV2. This change may cause breaking changes in any implementations of the FeePricer interface and any code that calls these methods.

Please ensure that all implementations of the FeePricer interface are updated to accommodate the new parameter. Additionally, verify that all callers of these methods pass the required tx argument.

You can run the following script to identify all implementations and usages of the updated methods:

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Find all implementations of the FeePricer interface and usages of the updated methods.

# Find all Go files in the repository
fd --type f --extension go

# Find implementations of the FeePricer interface
rg -A 5 'type .*FeePricer interface \{'

# Find structs that implement FeePricer
ast-grep --lang go --pattern 'func ($_ *$StructType) GetDestinationFee($_) ($_)' --no-filename

# Find all calls to GetDestinationFee and GetTotalFee
rg '\.(GetDestinationFee|GetTotalFee)\(' -A 2

Length of output: 76698


Script:

#!/bin/bash
# Description: Corrected ast-grep command to find implementations of FeePricer interface methods with the new tx parameter.

ast-grep --lang go --pattern 'func ($_ *$StructType) GetDestinationFee($_, $_) ($_)' -- --no-filename

ast-grep --lang go --pattern 'func ($_ *$StructType) GetTotalFee($_, $_) ($_)' -- --no-filename

Length of output: 323


Script:

#!/bin/bash
# Description: Find all implementations of the FeePricer interface to verify if they have been updated with the new tx parameter.

rg -A 5 'type .*FeePricer interface \{'

ast-grep --lang go --pattern 'func ($_ *$StructType) GetDestinationFee($_, $_) ($_)' -- --no-filename

ast-grep --lang go --pattern 'func ($_ *$StructType) GetTotalFee($_, $_) ($_)' -- --no-filename

Length of output: 1032


Script:

#!/bin/bash
# Description: Find implementations of the FeePricer interface methods with the new tx parameter.

# Search for structs implementing GetDestinationFee with the new tx parameter
ast-grep --lang go --pattern 'func ($_ *$StructType) GetDestinationFee($_, $_, $_) ($_)' 

# Search for structs implementing GetTotalFee with the new tx parameter
ast-grep --lang go --pattern 'func ($_ *$StructType) GetTotalFee($_, $_, $_) ($_)'

Length of output: 175

// GetGasPrice returns the gas price for a given chainID in native units.
GetGasPrice(ctx context.Context, chainID uint32) (*big.Int, error)
// GetTokenPrice returns the price of a token in USD.
Expand All @@ -44,10 +49,12 @@ type feePricer struct {
handler metrics.Handler
// priceFetcher is used to fetch prices from coingecko.
priceFetcher CoingeckoPriceFetcher
// relayerAddress is the address of the relayer.
relayerAddress common.Address
}

// NewFeePricer creates a new fee pricer.
func NewFeePricer(config relconfig.Config, clientFetcher submitter.ClientFetcher, priceFetcher CoingeckoPriceFetcher, handler metrics.Handler) FeePricer {
func NewFeePricer(config relconfig.Config, clientFetcher submitter.ClientFetcher, priceFetcher CoingeckoPriceFetcher, handler metrics.Handler, relayerAddress common.Address) FeePricer {
gasPriceCache := ttlcache.New[uint32, *big.Int](
ttlcache.WithTTL[uint32, *big.Int](time.Second*time.Duration(config.GetFeePricer().GasPriceCacheTTLSeconds)),
ttlcache.WithDisableTouchOnHit[uint32, *big.Int](),
Expand All @@ -63,6 +70,7 @@ func NewFeePricer(config relconfig.Config, clientFetcher submitter.ClientFetcher
clientFetcher: clientFetcher,
handler: handler,
priceFetcher: priceFetcher,
relayerAddress: relayerAddress,
}
}

Expand Down Expand Up @@ -116,7 +124,7 @@ func (f *feePricer) GetOriginFee(parentCtx context.Context, origin, destination
return fee, nil
}

func (f *feePricer) GetDestinationFee(parentCtx context.Context, _, destination uint32, denomToken string, isQuote bool) (*big.Int, error) {
func (f *feePricer) GetDestinationFee(parentCtx context.Context, _, destination uint32, denomToken string, isQuote bool, tx *fastbridgev2.IFastBridgeV2BridgeTransactionV2) (*big.Int, error) {
var err error
ctx, span := f.handler.Tracer().Start(parentCtx, "getDestinationFee", trace.WithAttributes(
attribute.Int(metrics.Destination, int(destination)),
Expand All @@ -136,6 +144,7 @@ func (f *feePricer) GetDestinationFee(parentCtx context.Context, _, destination
if err != nil {
return nil, err
}
span.SetAttributes(attribute.String("raw_fee", fee.String()))

// If specified, calculate and add the L1 fee
l1ChainID, l1GasEstimate, useL1Fee := f.config.GetL1FeeParams(destination, false)
Expand All @@ -147,11 +156,79 @@ func (f *feePricer) GetDestinationFee(parentCtx context.Context, _, destination
fee = new(big.Int).Add(fee, l1Fee)
span.SetAttributes(attribute.String("l1_fee", l1Fee.String()))
}

// If specified, calculate and add the call fee, as well as the call value which will be paid by the relayer
if tx != nil {
if tx.CallParams != nil {
gasEstimate, err := f.getZapGasEstimate(ctx, destination, tx)
if err != nil {
return nil, err
}
callFee, err := f.getFee(ctx, destination, destination, int(gasEstimate), denomToken, isQuote)
if err != nil {
return nil, err
}
fee = new(big.Int).Add(fee, callFee)
span.SetAttributes(attribute.String("call_fee", callFee.String()))
}

if tx.CallValue != nil {
callValueFloat := new(big.Float).SetInt(tx.CallValue)
valueDenom, err := f.getDenomFee(ctx, destination, destination, denomToken, callValueFloat)
if err != nil {
return nil, err
}
valueScaled, err := f.getFeeWithMultiplier(ctx, destination, isQuote, valueDenom)
if err != nil {
return nil, err
}
fee = new(big.Int).Add(fee, valueScaled)
span.SetAttributes(attribute.String("value_scaled", valueScaled.String()))
}
}

span.SetAttributes(attribute.String("destination_fee", fee.String()))
return fee, nil
}

func (f *feePricer) GetTotalFee(parentCtx context.Context, origin, destination uint32, denomToken string, isQuote bool) (_ *big.Int, err error) {
// cache so that we don't have to parse the ABI every time
var recipientABI *abi.ABI

const methodName = "fastBridgeTransferReceived"

func (f *feePricer) getZapGasEstimate(ctx context.Context, destination uint32, tx *fastbridgev2.IFastBridgeV2BridgeTransactionV2) (gasEstimate uint64, err error) {
client, err := f.clientFetcher.GetClient(ctx, big.NewInt(int64(destination)))
if err != nil {
return 0, fmt.Errorf("could not get client: %w", err)
}

if recipientABI == nil {
parsedABI, err := abi.JSON(strings.NewReader(fastbridgev2.IFastBridgeRecipientMetaData.ABI))
if err != nil {
return 0, fmt.Errorf("could not parse ABI: %w", err)
}
recipientABI = &parsedABI
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Prevent potential data race on recipientABI with concurrent access.

The global variable recipientABI is accessed and modified without synchronization in getZapGasEstimate. In a concurrent environment, this can lead to a data race if multiple goroutines initialize recipientABI simultaneously.

Consider initializing recipientABI in a thread-safe manner using sync.Once to ensure that it's only parsed once, even when accessed concurrently. Here's how you can modify the code:

import (
    "sync"
    // other imports...
)

var (
    recipientABI     *abi.ABI
    recipientABIInit sync.Once
)

func (f *feePricer) getZapGasEstimate(ctx context.Context, destination uint32, tx *fastbridgev2.IFastBridgeV2BridgeTransactionV2) (gasEstimate uint64, err error) {
    client, err := f.clientFetcher.GetClient(ctx, big.NewInt(int64(destination)))
    if err != nil {
        return 0, fmt.Errorf("could not get client: %w", err)
    }

    var parseErr error
    recipientABIInit.Do(func() {
        parsedABI, err := abi.JSON(strings.NewReader(fastbridgev2.IFastBridgeRecipientMetaData.ABI))
        if err != nil {
            parseErr = fmt.Errorf("could not parse ABI: %w", err)
            return
        }
        recipientABI = &parsedABI
    })
    if parseErr != nil {
        return 0, parseErr
    }

    encodedData, err := recipientABI.Pack(methodName, tx.DestToken, tx.DestAmount, tx.CallParams)
    if err != nil {
        return 0, fmt.Errorf("could not encode function call: %w", err)
    }

    // rest of the function...
}

This ensures that recipientABI is safely initialized exactly once.

encodedData, err := recipientABI.Pack(methodName, tx.DestToken, tx.DestAmount, tx.CallParams)
if err != nil {
return 0, fmt.Errorf("could not encode function call: %w", err)
}

callMsg := ethereum.CallMsg{
From: f.relayerAddress,
To: &tx.DestRecipient,
Value: tx.CallValue,
Data: encodedData,
}
gasEstimate, err = client.EstimateGas(ctx, callMsg)
ChiTimesChi marked this conversation as resolved.
Show resolved Hide resolved
ChiTimesChi marked this conversation as resolved.
Show resolved Hide resolved
ChiTimesChi marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return 0, fmt.Errorf("could not estimate gas: %w", err)
}

return gasEstimate, nil
}

func (f *feePricer) GetTotalFee(parentCtx context.Context, origin, destination uint32, denomToken string, isQuote bool, tx *fastbridgev2.IFastBridgeV2BridgeTransactionV2) (_ *big.Int, err error) {
ctx, span := f.handler.Tracer().Start(parentCtx, "getTotalFee", trace.WithAttributes(
attribute.Int(metrics.Origin, int(origin)),
attribute.Int(metrics.Destination, int(destination)),
Expand All @@ -170,7 +247,7 @@ func (f *feePricer) GetTotalFee(parentCtx context.Context, origin, destination u
))
return nil, err
}
destFee, err := f.GetDestinationFee(ctx, origin, destination, denomToken, isQuote)
destFee, err := f.GetDestinationFee(ctx, origin, destination, denomToken, isQuote, tx)
if err != nil {
span.AddEvent("could not get destination fee", trace.WithAttributes(
attribute.String("error", err.Error()),
Expand Down Expand Up @@ -202,6 +279,30 @@ func (f *feePricer) getFee(parentCtx context.Context, gasChain, denomChain uint3
if err != nil {
return nil, err
}
feeWei := new(big.Float).Mul(new(big.Float).SetInt(gasPrice), new(big.Float).SetFloat64(float64(gasEstimate)))

ChiTimesChi marked this conversation as resolved.
Show resolved Hide resolved
ChiTimesChi marked this conversation as resolved.
Show resolved Hide resolved
feeDenom, err := f.getDenomFee(ctx, gasChain, denomChain, denomToken, feeWei)
if err != nil {
return nil, err
}

feeScaled, err := f.getFeeWithMultiplier(ctx, gasChain, isQuote, feeDenom)
if err != nil {
return nil, err
}

span.SetAttributes(
attribute.String("gas_price", gasPrice.String()),
attribute.String("fee_wei", feeWei.String()),
attribute.String("fee_denom", feeDenom.String()),
attribute.String("fee_scaled", feeScaled.String()),
)
return feeScaled, nil
Comment on lines +316 to +334
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Use integer arithmetic to prevent precision loss.

The fee calculations use big.Float which can lead to precision loss. Consider using big.Int for all calculations to maintain precision.

Apply these changes:

- feeWei := new(big.Float).Mul(new(big.Float).SetInt(gasPrice), new(big.Float).SetFloat64(float64(gasEstimate)))
+ feeWei := new(big.Int).Mul(gasPrice, big.NewInt(int64(gasEstimate)))

- feeScaled, _ = new(big.Float).Mul(feeDenom, new(big.Float).SetFloat64(multiplier)).Int(nil)
+ scaledMultiplier := new(big.Int).SetInt64(int64(multiplier * 1e18))
+ feeScaled = new(big.Int).Div(new(big.Int).Mul(feeDenom, scaledMultiplier), big.NewInt(1e18))

Also applies to: 393-394

🧰 Tools
🪛 GitHub Check: codecov/patch

[warning] 306-307: services/rfq/relayer/pricer/fee_pricer.go#L306-L307
Added lines #L306 - L307 were not covered by tests


[warning] 311-312: services/rfq/relayer/pricer/fee_pricer.go#L311-L312
Added lines #L311 - L312 were not covered by tests

}

func (f *feePricer) getDenomFee(ctx context.Context, gasChain, denomChain uint32, denomToken string, feeWei *big.Float) (*big.Float, error) {
span := trace.SpanFromContext(ctx)

nativeToken, err := f.config.GetNativeToken(int(gasChain))
if err != nil {
return nil, err
Expand All @@ -222,7 +323,6 @@ func (f *feePricer) getFee(parentCtx context.Context, gasChain, denomChain uint3

// Compute the fee.
var feeDenom *big.Float
feeWei := new(big.Float).Mul(new(big.Float).SetInt(gasPrice), new(big.Float).SetFloat64(float64(gasEstimate)))
if denomToken == nativeToken {
// Denomination token is native token, so no need for unit conversion.
feeDenom = feeWei
Expand All @@ -239,6 +339,17 @@ func (f *feePricer) getFee(parentCtx context.Context, gasChain, denomChain uint3
attribute.String("fee_usdc", feeUSDC.String()),
)
}
span.SetAttributes(
attribute.Float64("native_token_price", nativeTokenPrice),
attribute.Float64("denom_token_price", denomTokenPrice),
attribute.Int("denom_token_decimals", int(denomTokenDecimals)),
)

return feeDenom, nil
}

func (f *feePricer) getFeeWithMultiplier(ctx context.Context, gasChain uint32, isQuote bool, feeDenom *big.Float) (feeScaled *big.Int, err error) {
span := trace.SpanFromContext(ctx)

var multiplier float64
if isQuote {
Expand All @@ -252,22 +363,16 @@ func (f *feePricer) getFee(parentCtx context.Context, gasChain, denomChain uint3
return nil, fmt.Errorf("could not get relay fixed fee multiplier: %w", err)
}
}
span.SetAttributes(
attribute.Float64("multiplier", multiplier),
)

// Apply the fixed fee multiplier.
// Note that this step rounds towards zero- we may need to apply rounding here if
// we want to be conservative and lean towards overestimating fees.
feeUSDCDecimalsScaled, _ := new(big.Float).Mul(feeDenom, new(big.Float).SetFloat64(multiplier)).Int(nil)
span.SetAttributes(
attribute.String("gas_price", gasPrice.String()),
attribute.Float64("native_token_price", nativeTokenPrice),
attribute.Float64("denom_token_price", denomTokenPrice),
attribute.Float64("multplier", multiplier),
attribute.Int("denom_token_decimals", int(denomTokenDecimals)),
attribute.String("fee_wei", feeWei.String()),
attribute.String("fee_denom", feeDenom.String()),
attribute.String("fee_usdc_decimals_scaled", feeUSDCDecimalsScaled.String()),
)
return feeUSDCDecimalsScaled, nil
feeScaled, _ = new(big.Float).Mul(feeDenom, new(big.Float).SetFloat64(multiplier)).Int(nil)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Implement conservative rounding for fee calculations.

The current implementation rounds towards zero when applying fee multipliers, which could lead to fee underestimation. Consider rounding up to ensure fees are not underestimated.

-feeScaled, _ = new(big.Float).Mul(feeDenom, new(big.Float).SetFloat64(multiplier)).Int(nil)
+feeFloat := new(big.Float).Mul(feeDenom, new(big.Float).SetFloat64(multiplier))
+feeFloat.SetMode(big.AwayFromZero)
+feeScaled, _ = feeFloat.Int(nil)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
feeScaled, _ = new(big.Float).Mul(feeDenom, new(big.Float).SetFloat64(multiplier)).Int(nil)
feeFloat := new(big.Float).Mul(feeDenom, new(big.Float).SetFloat64(multiplier))
feeFloat.SetMode(big.AwayFromZero)
feeScaled, _ = feeFloat.Int(nil)


Comment on lines +407 to +408
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Specify Rounding Behavior When Applying Fee Multiplier

In the getFeeWithMultiplier method, the conversion from big.Float to big.Int truncates towards zero:

feeScaled, _ = new(big.Float).Mul(feeDenom, new(big.Float).SetFloat64(multiplier)).Int(nil)

This truncation may lead to underestimating the fee, potentially resulting in a smaller fee than intended.

Consider specifying the rounding mode to ensure that the fee is rounded up, preventing undercharging:

feeFloat := new(big.Float).Mul(feeDenom, new(big.Float).SetFloat64(multiplier))
- feeScaled, _ = feeFloat.Int(nil)
+ feeScaled = new(big.Int)
+ feeFloat.SetMode(big.AwayFromZero)
+ feeFloat.Int(feeScaled)

Alternatively, use the Ceil method to always round up:

feeFloat := new(big.Float).Mul(feeDenom, new(big.Float).SetFloat64(multiplier))
+ feeFloat = feeFloat.SetMode(big.AwayFromZero)
+ feeScaled, _ = feeFloat.Int(nil)

Ensure that the rounding behavior complies with your fee policy to accurately reflect the intended fees.

return feeScaled, nil
}

// getGasPrice returns the gas price for a given chainID in native units.
Expand Down
Loading
Loading