From 5de3ede848c12e64d9e3e9b0174eccd8f0dfada6 Mon Sep 17 00:00:00 2001 From: dwasse Date: Wed, 5 Jun 2024 13:18:23 -0500 Subject: [PATCH] FeePricer: use different multipliers for quote / relay (#2663) * Feat: split FixedFeeMultiplier into relay and quote variants * Fix: tests * Feat: isRelay -> isQuote * Feat: default relay mult to quote mult * Fix: config tests * [goreleaser] --- services/rfq/relayer/pricer/fee_pricer.go | 46 +++--- .../rfq/relayer/pricer/fee_pricer_test.go | 17 ++- services/rfq/relayer/relconfig/config.go | 6 +- services/rfq/relayer/relconfig/config_test.go | 134 +++++++++--------- services/rfq/relayer/relconfig/getters.go | 46 ++++-- 5 files changed, 144 insertions(+), 105 deletions(-) diff --git a/services/rfq/relayer/pricer/fee_pricer.go b/services/rfq/relayer/pricer/fee_pricer.go index 8faf91599f..d8e925aa0d 100644 --- a/services/rfq/relayer/pricer/fee_pricer.go +++ b/services/rfq/relayer/pricer/fee_pricer.go @@ -21,11 +21,11 @@ type FeePricer interface { // Start starts the fee pricer. Start(ctx context.Context) // 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, useMultiplier bool) (*big.Int, error) + 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, useMultiplier bool) (*big.Int, error) + GetDestinationFee(ctx context.Context, origin, destination uint32, denomToken string, isQuote bool) (*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, useMultiplier bool) (*big.Int, error) + GetTotalFee(ctx context.Context, origin, destination uint32, denomToken string, isQuote bool) (*big.Int, error) // GetGasPrice returns the gas price for a given chainID in native units. GetGasPrice(ctx context.Context, chainID uint32) (*big.Int, error) } @@ -81,13 +81,13 @@ func (f *feePricer) Start(ctx context.Context) { var nativeDecimalsFactor = new(big.Int).Exp(big.NewInt(10), big.NewInt(int64(18)), nil) -func (f *feePricer) GetOriginFee(parentCtx context.Context, origin, destination uint32, denomToken string, useMultiplier bool) (*big.Int, error) { +func (f *feePricer) GetOriginFee(parentCtx context.Context, origin, destination uint32, denomToken string, isQuote bool) (*big.Int, error) { var err error ctx, span := f.handler.Tracer().Start(parentCtx, "getOriginFee", trace.WithAttributes( attribute.Int(metrics.Origin, int(origin)), attribute.Int(metrics.Destination, int(destination)), attribute.String("denom_token", denomToken), - attribute.Bool("use_multiplier", useMultiplier), + attribute.Bool("is_quote", isQuote), )) defer func() { metrics.EndSpanWithErr(span, err) @@ -98,7 +98,7 @@ func (f *feePricer) GetOriginFee(parentCtx context.Context, origin, destination if err != nil { return nil, fmt.Errorf("could not get origin gas estimate: %w", err) } - fee, err := f.getFee(ctx, origin, destination, gasEstimate, denomToken, useMultiplier) + fee, err := f.getFee(ctx, origin, destination, gasEstimate, denomToken, isQuote) if err != nil { return nil, err } @@ -106,7 +106,7 @@ func (f *feePricer) GetOriginFee(parentCtx context.Context, origin, destination // If specified, calculate and add the L1 fee l1ChainID, l1GasEstimate, useL1Fee := f.config.GetL1FeeParams(origin, true) if useL1Fee { - l1Fee, err := f.getFee(ctx, l1ChainID, destination, l1GasEstimate, denomToken, useMultiplier) + l1Fee, err := f.getFee(ctx, l1ChainID, destination, l1GasEstimate, denomToken, isQuote) if err != nil { return nil, err } @@ -117,12 +117,12 @@ func (f *feePricer) GetOriginFee(parentCtx context.Context, origin, destination return fee, nil } -func (f *feePricer) GetDestinationFee(parentCtx context.Context, _, destination uint32, denomToken string, useMultiplier bool) (*big.Int, error) { +func (f *feePricer) GetDestinationFee(parentCtx context.Context, _, destination uint32, denomToken string, isQuote bool) (*big.Int, error) { var err error ctx, span := f.handler.Tracer().Start(parentCtx, "getDestinationFee", trace.WithAttributes( attribute.Int(metrics.Destination, int(destination)), attribute.String("denom_token", denomToken), - attribute.Bool("use_multiplier", useMultiplier), + attribute.Bool("is_quote", isQuote), )) defer func() { metrics.EndSpanWithErr(span, err) @@ -133,7 +133,7 @@ func (f *feePricer) GetDestinationFee(parentCtx context.Context, _, destination if err != nil { return nil, fmt.Errorf("could not get dest gas estimate: %w", err) } - fee, err := f.getFee(ctx, destination, destination, gasEstimate, denomToken, useMultiplier) + fee, err := f.getFee(ctx, destination, destination, gasEstimate, denomToken, isQuote) if err != nil { return nil, err } @@ -141,7 +141,7 @@ func (f *feePricer) GetDestinationFee(parentCtx context.Context, _, destination // If specified, calculate and add the L1 fee l1ChainID, l1GasEstimate, useL1Fee := f.config.GetL1FeeParams(destination, false) if useL1Fee { - l1Fee, err := f.getFee(ctx, l1ChainID, destination, l1GasEstimate, denomToken, useMultiplier) + l1Fee, err := f.getFee(ctx, l1ChainID, destination, l1GasEstimate, denomToken, isQuote) if err != nil { return nil, err } @@ -152,26 +152,26 @@ func (f *feePricer) GetDestinationFee(parentCtx context.Context, _, destination return fee, nil } -func (f *feePricer) GetTotalFee(parentCtx context.Context, origin, destination uint32, denomToken string, useMultiplier bool) (_ *big.Int, err error) { +func (f *feePricer) GetTotalFee(parentCtx context.Context, origin, destination uint32, denomToken string, isQuote bool) (_ *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)), attribute.String("denom_token", denomToken), - attribute.Bool("use_multiplier", useMultiplier), + attribute.Bool("is_quote", isQuote), )) defer func() { metrics.EndSpanWithErr(span, err) }() - originFee, err := f.GetOriginFee(ctx, origin, destination, denomToken, useMultiplier) + originFee, err := f.GetOriginFee(ctx, origin, destination, denomToken, isQuote) if err != nil { span.AddEvent("could not get origin fee", trace.WithAttributes( attribute.String("error", err.Error()), )) return nil, err } - destFee, err := f.GetDestinationFee(ctx, origin, destination, denomToken, useMultiplier) + destFee, err := f.GetDestinationFee(ctx, origin, destination, denomToken, isQuote) if err != nil { span.AddEvent("could not get destination fee", trace.WithAttributes( attribute.String("error", err.Error()), @@ -187,7 +187,7 @@ func (f *feePricer) GetTotalFee(parentCtx context.Context, origin, destination u return totalFee, nil } -func (f *feePricer) getFee(parentCtx context.Context, gasChain, denomChain uint32, gasEstimate int, denomToken string, useMultiplier bool) (_ *big.Int, err error) { +func (f *feePricer) getFee(parentCtx context.Context, gasChain, denomChain uint32, gasEstimate int, denomToken string, isQuote bool) (_ *big.Int, err error) { ctx, span := f.handler.Tracer().Start(parentCtx, "getFee", trace.WithAttributes( attribute.Int("gas_chain", int(gasChain)), attribute.Int("denom_chain", int(denomChain)), @@ -241,11 +241,16 @@ func (f *feePricer) getFee(parentCtx context.Context, gasChain, denomChain uint3 ) } - multiplier := 1. - if useMultiplier { - multiplier, err = f.config.GetFixedFeeMultiplier(int(gasChain)) + var multiplier float64 + if isQuote { + multiplier, err = f.config.GetQuoteFixedFeeMultiplier(int(gasChain)) if err != nil { - return nil, fmt.Errorf("could not get fixed fee multiplier: %w", err) + return nil, fmt.Errorf("could not get quote fixed fee multiplier: %w", err) + } + } else { + multiplier, err = f.config.GetRelayFixedFeeMultiplier(int(gasChain)) + if err != nil { + return nil, fmt.Errorf("could not get relay fixed fee multiplier: %w", err) } } @@ -257,6 +262,7 @@ func (f *feePricer) getFee(parentCtx context.Context, gasChain, denomChain uint3 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()), diff --git a/services/rfq/relayer/pricer/fee_pricer_test.go b/services/rfq/relayer/pricer/fee_pricer_test.go index aae9e7614c..f316622a54 100644 --- a/services/rfq/relayer/pricer/fee_pricer_test.go +++ b/services/rfq/relayer/pricer/fee_pricer_test.go @@ -261,7 +261,8 @@ func (s *PricerSuite) TestGetGasPrice() { func (s *PricerSuite) TestGetTotalFeeWithMultiplier() { // Override the fixed fee multiplier to greater than 1. - s.config.BaseChainConfig.FixedFeeMultiplier = 2 + s.config.BaseChainConfig.QuoteFixedFeeMultiplier = 2 + s.config.BaseChainConfig.RelayFixedFeeMultiplier = 4 // Build a new FeePricer with a mocked client for fetching gas price. clientFetcher := new(fetcherMocks.ClientFetcher) @@ -277,7 +278,7 @@ func (s *PricerSuite) TestGetTotalFeeWithMultiplier() { feePricer := pricer.NewFeePricer(s.config, clientFetcher, priceFetcher, metrics.NewNullHandler()) go func() { feePricer.Start(s.GetTestContext()) }() - // Calculate the total fee. + // Calculate the total fee [quote]. fee, err := feePricer.GetTotalFee(s.GetTestContext(), s.origin, s.destination, "USDC", true) s.NoError(err) @@ -285,8 +286,16 @@ func (s *PricerSuite) TestGetTotalFeeWithMultiplier() { expectedFee := big.NewInt(200_500_000) // 200.50 usd s.Equal(expectedFee, fee) + // Calculate the total fee [relay]. + fee, err = feePricer.GetTotalFee(s.GetTestContext(), s.origin, s.destination, "USDC", false) + s.NoError(err) + + // The expected fee should be the sum of the Origin and Destination fees, i.e. 401_000_000. + expectedFee = big.NewInt(401_000_000) // 401 usd + s.Equal(expectedFee, fee) + // Override the fixed fee multiplier to less than 1; should default to 1. - s.config.BaseChainConfig.FixedFeeMultiplier = -1 + s.config.BaseChainConfig.QuoteFixedFeeMultiplier = -1 // Build a new FeePricer with a mocked client for fetching gas price. clientOrigin.On(testsuite.GetFunctionName(clientOrigin.SuggestGasPrice), mock.Anything).Once().Return(headerOrigin, nil) @@ -305,7 +314,7 @@ func (s *PricerSuite) TestGetTotalFeeWithMultiplier() { s.Equal(expectedFee, fee) // Reset the fixed fee multiplier to zero; should default to 1 - s.config.BaseChainConfig.FixedFeeMultiplier = 0 + s.config.BaseChainConfig.QuoteFixedFeeMultiplier = 0 // Build a new FeePricer with a mocked client for fetching gas price. clientOrigin.On(testsuite.GetFunctionName(clientOrigin.SuggestGasPrice), mock.Anything).Once().Return(headerOrigin, nil) diff --git a/services/rfq/relayer/relconfig/config.go b/services/rfq/relayer/relconfig/config.go index 9ca457b928..1c4222fa10 100644 --- a/services/rfq/relayer/relconfig/config.go +++ b/services/rfq/relayer/relconfig/config.go @@ -86,8 +86,10 @@ type ChainConfig struct { // QuoteWidthBps is the number of basis points to deduct from the dest amount. // Note that this parameter is applied on a chain level and must be positive. QuoteWidthBps float64 `yaml:"quote_width_bps"` - // FixedFeeMultiplier is the multiplier for the fixed fee. - FixedFeeMultiplier float64 `yaml:"fixed_fee_multiplier"` + // QuoteFixedFeeMultiplier is the multiplier for the fixed fee, applied when generating quotes. + QuoteFixedFeeMultiplier float64 `yaml:"quote_fixed_fee_multiplier"` + // RelayFixedFeeMultiplier is the multiplier for the fixed fee, applied when relaying. + RelayFixedFeeMultiplier float64 `yaml:"relay_fixed_fee_multiplier"` // CCTP start block is the block at which the chain listener will listen for CCTP events. CCTPStartBlock uint64 `yaml:"cctp_start_block"` } diff --git a/services/rfq/relayer/relconfig/config_test.go b/services/rfq/relayer/relconfig/config_test.go index e133c1c902..c3fb89fb52 100644 --- a/services/rfq/relayer/relconfig/config_test.go +++ b/services/rfq/relayer/relconfig/config_test.go @@ -18,59 +18,59 @@ func TestChainGetters(t *testing.T) { cfgWithBase := relconfig.Config{ Chains: map[int]relconfig.ChainConfig{ chainID: { - RFQAddress: "0x123", - SynapseCCTPAddress: "0x456", - TokenMessengerAddress: "0x789", - Confirmations: 1, - NativeToken: "MATIC", - DeadlineBufferSeconds: 10, - OriginGasEstimate: 10000, - DestGasEstimate: 20000, - L1FeeChainID: 10, - L1FeeOriginGasEstimate: 30000, - L1FeeDestGasEstimate: 40000, - MinGasToken: "1000", - QuotePct: 50, - QuoteWidthBps: 10, - FixedFeeMultiplier: 1.1, + RFQAddress: "0x123", + SynapseCCTPAddress: "0x456", + TokenMessengerAddress: "0x789", + Confirmations: 1, + NativeToken: "MATIC", + DeadlineBufferSeconds: 10, + OriginGasEstimate: 10000, + DestGasEstimate: 20000, + L1FeeChainID: 10, + L1FeeOriginGasEstimate: 30000, + L1FeeDestGasEstimate: 40000, + MinGasToken: "1000", + QuotePct: 50, + QuoteWidthBps: 10, + QuoteFixedFeeMultiplier: 1.1, }, }, BaseChainConfig: relconfig.ChainConfig{ - RFQAddress: "0x1234", - SynapseCCTPAddress: "0x456", - TokenMessengerAddress: "0x789", - Confirmations: 2, - NativeToken: "ARB", - DeadlineBufferSeconds: 11, - OriginGasEstimate: 10001, - DestGasEstimate: 20001, - L1FeeChainID: 11, - L1FeeOriginGasEstimate: 30001, - L1FeeDestGasEstimate: 40001, - MinGasToken: "1001", - QuotePct: 51, - QuoteWidthBps: 11, - FixedFeeMultiplier: 1.2, + RFQAddress: "0x1234", + SynapseCCTPAddress: "0x456", + TokenMessengerAddress: "0x789", + Confirmations: 2, + NativeToken: "ARB", + DeadlineBufferSeconds: 11, + OriginGasEstimate: 10001, + DestGasEstimate: 20001, + L1FeeChainID: 11, + L1FeeOriginGasEstimate: 30001, + L1FeeDestGasEstimate: 40001, + MinGasToken: "1001", + QuotePct: 51, + QuoteWidthBps: 11, + QuoteFixedFeeMultiplier: 1.2, }, } cfg := relconfig.Config{ Chains: map[int]relconfig.ChainConfig{ chainID: { - RFQAddress: "0x123", - SynapseCCTPAddress: "0x456", - TokenMessengerAddress: "0x789", - Confirmations: 1, - NativeToken: "MATIC", - DeadlineBufferSeconds: 10, - OriginGasEstimate: 10000, - DestGasEstimate: 20000, - L1FeeChainID: 10, - L1FeeOriginGasEstimate: 30000, - L1FeeDestGasEstimate: 40000, - MinGasToken: "1000", - QuotePct: 50, - QuoteWidthBps: 10, - FixedFeeMultiplier: 1.1, + RFQAddress: "0x123", + SynapseCCTPAddress: "0x456", + TokenMessengerAddress: "0x789", + Confirmations: 1, + NativeToken: "MATIC", + DeadlineBufferSeconds: 10, + OriginGasEstimate: 10000, + DestGasEstimate: 20000, + L1FeeChainID: 10, + L1FeeOriginGasEstimate: 30000, + L1FeeDestGasEstimate: 40000, + MinGasToken: "1000", + QuotePct: 50, + QuoteWidthBps: 10, + QuoteFixedFeeMultiplier: 1.1, Tokens: map[string]relconfig.TokenConfig{ "USDC": { Address: usdcAddr, @@ -278,18 +278,18 @@ func TestChainGetters(t *testing.T) { assert.Equal(t, chainVal, cfgWithBase.Chains[chainID].QuoteWidthBps) }) - t.Run("GetFixedFeeMultiplier", func(t *testing.T) { - defaultVal, err := cfg.GetFixedFeeMultiplier(badChainID) + t.Run("GetQuoteFixedFeeMultiplier", func(t *testing.T) { + defaultVal, err := cfg.GetQuoteFixedFeeMultiplier(badChainID) assert.NoError(t, err) - assert.Equal(t, defaultVal, relconfig.DefaultChainConfig.FixedFeeMultiplier) + assert.Equal(t, defaultVal, relconfig.DefaultChainConfig.QuoteFixedFeeMultiplier) - baseVal, err := cfgWithBase.GetFixedFeeMultiplier(badChainID) + baseVal, err := cfgWithBase.GetQuoteFixedFeeMultiplier(badChainID) assert.NoError(t, err) - assert.Equal(t, baseVal, cfgWithBase.BaseChainConfig.FixedFeeMultiplier) + assert.Equal(t, baseVal, cfgWithBase.BaseChainConfig.QuoteFixedFeeMultiplier) - chainVal, err := cfgWithBase.GetFixedFeeMultiplier(chainID) + chainVal, err := cfgWithBase.GetQuoteFixedFeeMultiplier(chainID) assert.NoError(t, err) - assert.Equal(t, chainVal, cfgWithBase.Chains[chainID].FixedFeeMultiplier) + assert.Equal(t, chainVal, cfgWithBase.Chains[chainID].QuoteFixedFeeMultiplier) }) t.Run("GetMaxRebalanceAmount", func(t *testing.T) { @@ -307,21 +307,21 @@ func TestGetQuoteOffset(t *testing.T) { cfg := relconfig.Config{ Chains: map[int]relconfig.ChainConfig{ chainID: { - RFQAddress: "0x123", - SynapseCCTPAddress: "0x456", - TokenMessengerAddress: "0x789", - Confirmations: 1, - NativeToken: "MATIC", - DeadlineBufferSeconds: 10, - OriginGasEstimate: 10000, - DestGasEstimate: 20000, - L1FeeChainID: 10, - L1FeeOriginGasEstimate: 30000, - L1FeeDestGasEstimate: 40000, - MinGasToken: "1000", - QuotePct: 50, - QuoteWidthBps: 10, - FixedFeeMultiplier: 1.1, + RFQAddress: "0x123", + SynapseCCTPAddress: "0x456", + TokenMessengerAddress: "0x789", + Confirmations: 1, + NativeToken: "MATIC", + DeadlineBufferSeconds: 10, + OriginGasEstimate: 10000, + DestGasEstimate: 20000, + L1FeeChainID: 10, + L1FeeOriginGasEstimate: 30000, + L1FeeDestGasEstimate: 40000, + MinGasToken: "1000", + QuotePct: 50, + QuoteWidthBps: 10, + QuoteFixedFeeMultiplier: 1.1, Tokens: map[string]relconfig.TokenConfig{ "USDC": { Address: usdcAddr, diff --git a/services/rfq/relayer/relconfig/getters.go b/services/rfq/relayer/relconfig/getters.go index aa9b380471..3bca0f98ce 100644 --- a/services/rfq/relayer/relconfig/getters.go +++ b/services/rfq/relayer/relconfig/getters.go @@ -13,13 +13,14 @@ import ( // DefaultChainConfig is the default chain config. var DefaultChainConfig = ChainConfig{ - DeadlineBufferSeconds: 600, - OriginGasEstimate: 160000, - DestGasEstimate: 100000, - MinGasToken: "100000000000000000", // 1 ETH - QuotePct: 100, - QuoteWidthBps: 0, - FixedFeeMultiplier: 1, + DeadlineBufferSeconds: 600, + OriginGasEstimate: 160000, + DestGasEstimate: 100000, + MinGasToken: "100000000000000000", // 1 ETH + QuotePct: 100, + QuoteWidthBps: 0, + QuoteFixedFeeMultiplier: 1, + RelayFixedFeeMultiplier: 1, } // getChainConfigValue gets the value of a field from ChainConfig. @@ -314,19 +315,40 @@ func (c Config) GetQuoteWidthBps(chainID int) (value float64, err error) { return value, nil } -// GetFixedFeeMultiplier returns the FixedFeeMultiplier for the given chainID. -func (c Config) GetFixedFeeMultiplier(chainID int) (value float64, err error) { - rawValue, err := c.getChainConfigValue(chainID, "FixedFeeMultiplier") +// GetQuoteFixedFeeMultiplier returns the QuoteFixedFeeMultiplier for the given chainID. +func (c Config) GetQuoteFixedFeeMultiplier(chainID int) (value float64, err error) { + rawValue, err := c.getChainConfigValue(chainID, "QuoteFixedFeeMultiplier") if err != nil { return value, err } value, ok := rawValue.(float64) if !ok { - return value, fmt.Errorf("failed to cast FixedFeeMultiplier to int") + return value, fmt.Errorf("failed to cast QuoteFixedFeeMultiplier to int") } if value <= 0 { - value = DefaultChainConfig.FixedFeeMultiplier + value = DefaultChainConfig.QuoteFixedFeeMultiplier + } + return value, nil +} + +// GetRelayFixedFeeMultiplier returns the RelayFixedFeeMultiplier for the given chainID. +func (c Config) GetRelayFixedFeeMultiplier(chainID int) (value float64, err error) { + rawValue, err := c.getChainConfigValue(chainID, "RelayFixedFeeMultiplier") + if err != nil { + return value, err + } + + value, ok := rawValue.(float64) + if !ok { + return value, fmt.Errorf("failed to cast RelayFixedFeeMultiplier to int") + } + if value <= 0 { + // If the value is not set, we default to the quote fixed fee multiplier. + value, err = c.GetQuoteFixedFeeMultiplier(chainID) + if err != nil { + return value, err + } } return value, nil }