From 92a42e5f0acc9e108b01b53d3054523dc3d9508f Mon Sep 17 00:00:00 2001 From: dwasse Date: Mon, 8 Jan 2024 13:06:20 -0600 Subject: [PATCH] Add `FixedFeeMultiplier` to rfq fee pricer (#1782) * Feat: apply fixed fee multiplier * Feat: add TestGetTotalFeeWithMultiplier --- services/rfq/relayer/pricer/fee_pricer.go | 6 +- .../rfq/relayer/pricer/fee_pricer_test.go | 64 +++++++++++++++++++ services/rfq/relayer/relconfig/config.go | 12 ++++ .../relayer/relconfig/iconfig_generated.go | 2 + 4 files changed, 82 insertions(+), 2 deletions(-) diff --git a/services/rfq/relayer/pricer/fee_pricer.go b/services/rfq/relayer/pricer/fee_pricer.go index 9da7776399..5ead5cb37b 100644 --- a/services/rfq/relayer/pricer/fee_pricer.go +++ b/services/rfq/relayer/pricer/fee_pricer.go @@ -118,8 +118,10 @@ func (f *feePricer) getFee(ctx context.Context, gasChain, denomChain uint32, gas feeUSDC := new(big.Float).Mul(feeUSD, new(big.Float).SetFloat64(denomTokenPrice)) // Note that this rounds towards zero- we may need to apply rounding here if // we want to be conservative and lean towards overestimating fees. - feeUSDCDecimals, _ := new(big.Float).Mul(feeUSDC, new(big.Float).SetInt(denomDecimalsFactor)).Int(nil) - return feeUSDCDecimals, nil + feeUSDCDecimals := new(big.Float).Mul(feeUSDC, new(big.Float).SetInt(denomDecimalsFactor)) + // Apply the fixed fee multiplier. + feeUSDCDecimalsScaled, _ := new(big.Float).Mul(feeUSDCDecimals, new(big.Float).SetFloat64(f.config.GetFixedFeeMultiplier())).Int(nil) + return feeUSDCDecimalsScaled, nil } // getGasPrice returns the gas price for a given chainID in native units. diff --git a/services/rfq/relayer/pricer/fee_pricer_test.go b/services/rfq/relayer/pricer/fee_pricer_test.go index a43b58f5fe..a6d7b96ad0 100644 --- a/services/rfq/relayer/pricer/fee_pricer_test.go +++ b/services/rfq/relayer/pricer/fee_pricer_test.go @@ -139,3 +139,67 @@ func (s *PricerSuite) TestGetGasPrice() { return expectedGasPrice.String() == gasPrice.String() }) } + +func (s *PricerSuite) TestGetTotalFeeWithMultiplier() { + // Override the fixed fee multiplier to greater than 1. + s.config.FeePricer.FixedFeeMultiplier = 2 + + // Build a new FeePricer with a mocked client for fetching gas price. + clientFetcher := new(fetcherMocks.ClientFetcher) + clientOrigin := new(clientMocks.EVM) + clientDestination := new(clientMocks.EVM) + headerOrigin := &types.Header{BaseFee: big.NewInt(100_000_000_000)} // 100 gwei + headerDestination := &types.Header{BaseFee: big.NewInt(500_000_000_000)} // 500 gwei + clientOrigin.On(testsuite.GetFunctionName(clientOrigin.HeaderByNumber), mock.Anything, mock.Anything).Once().Return(headerOrigin, nil) + clientDestination.On(testsuite.GetFunctionName(clientDestination.HeaderByNumber), mock.Anything, mock.Anything).Once().Return(headerDestination, nil) + clientFetcher.On(testsuite.GetFunctionName(clientFetcher.GetClient), mock.Anything, big.NewInt(int64(s.origin))).Once().Return(clientOrigin, nil) + clientFetcher.On(testsuite.GetFunctionName(clientFetcher.GetClient), mock.Anything, big.NewInt(int64(s.destination))).Once().Return(clientDestination, nil) + feePricer := pricer.NewFeePricer(s.config, clientFetcher) + go func() { feePricer.Start(s.GetTestContext()) }() + + // Calculate the total fee. + fee, err := feePricer.GetTotalFee(s.GetTestContext(), s.origin, s.destination, "USDC") + s.NoError(err) + + // The expected fee should be the sum of the Origin and Destination fees, i.e. 200_500_000. + expectedFee := big.NewInt(200_500_000) // 200.50 usd + s.Equal(expectedFee, fee) + + // Override the fixed fee multiplier to less than 1; should default to 1. + s.config.FeePricer.FixedFeeMultiplier = -1 + + // Build a new FeePricer with a mocked client for fetching gas price. + clientOrigin.On(testsuite.GetFunctionName(clientOrigin.HeaderByNumber), mock.Anything, mock.Anything).Once().Return(headerOrigin, nil) + clientDestination.On(testsuite.GetFunctionName(clientDestination.HeaderByNumber), mock.Anything, mock.Anything).Once().Return(headerDestination, nil) + clientFetcher.On(testsuite.GetFunctionName(clientFetcher.GetClient), mock.Anything, big.NewInt(int64(s.origin))).Once().Return(clientOrigin, nil) + clientFetcher.On(testsuite.GetFunctionName(clientFetcher.GetClient), mock.Anything, big.NewInt(int64(s.destination))).Once().Return(clientDestination, nil) + feePricer = pricer.NewFeePricer(s.config, clientFetcher) + go func() { feePricer.Start(s.GetTestContext()) }() + + // Calculate the total fee. + fee, err = feePricer.GetTotalFee(s.GetTestContext(), s.origin, s.destination, "USDC") + s.NoError(err) + + // The expected fee should be the sum of the Origin and Destination fees, i.e. 100_250_000. + expectedFee = big.NewInt(100_250_000) // 100.25 usd + s.Equal(expectedFee, fee) + + // Reset the fixed fee multiplier to zero; should default to 1 + s.config.FeePricer.FixedFeeMultiplier = 0 + + // Build a new FeePricer with a mocked client for fetching gas price. + clientOrigin.On(testsuite.GetFunctionName(clientOrigin.HeaderByNumber), mock.Anything, mock.Anything).Once().Return(headerOrigin, nil) + clientDestination.On(testsuite.GetFunctionName(clientDestination.HeaderByNumber), mock.Anything, mock.Anything).Once().Return(headerDestination, nil) + clientFetcher.On(testsuite.GetFunctionName(clientFetcher.GetClient), mock.Anything, big.NewInt(int64(s.origin))).Once().Return(clientOrigin, nil) + clientFetcher.On(testsuite.GetFunctionName(clientFetcher.GetClient), mock.Anything, big.NewInt(int64(s.destination))).Once().Return(clientDestination, nil) + feePricer = pricer.NewFeePricer(s.config, clientFetcher) + go func() { feePricer.Start(s.GetTestContext()) }() + + // Calculate the total fee. + fee, err = feePricer.GetTotalFee(s.GetTestContext(), s.origin, s.destination, "USDC") + s.NoError(err) + + // The expected fee should be the sum of the Origin and Destination fees, i.e. 100_250_000. + expectedFee = big.NewInt(100_250_000) // 100.25 usd + s.Equal(expectedFee, fee) +} diff --git a/services/rfq/relayer/relconfig/config.go b/services/rfq/relayer/relconfig/config.go index 3ab727bf2f..31e3d79ab0 100644 --- a/services/rfq/relayer/relconfig/config.go +++ b/services/rfq/relayer/relconfig/config.go @@ -71,6 +71,8 @@ type FeePricerConfig struct { OriginGasEstimate int `yaml:"origin_gas_estimate"` // DestinationGasEstimate is the gas required to execute relay transaction on destination chain. DestinationGasEstimate int `yaml:"destination_gas_estimate"` + // FixedFeeMultiplier is the multiplier for the fixed fee. + FixedFeeMultiplier float64 `yaml:"fixed_fee_multiplier"` // GasPriceCacheTTLSeconds is the TTL for the gas price cache. GasPriceCacheTTLSeconds int `yaml:"gas_price_cache_ttl"` // TokenPriceCacheTTLSeconds is the TTL for the token price cache. @@ -193,4 +195,14 @@ func (c Config) GetTokenName(chain uint32, addr string) (string, error) { return "", fmt.Errorf("no tokenName found for chain %d and address %s", chain, addr) } +const defaultFixedFeeMultiplier = 1 + +// GetFixedFeeMultiplier returns the fixed fee multiplier. +func (c Config) GetFixedFeeMultiplier() float64 { + if c.FeePricer.FixedFeeMultiplier <= 0 { + return defaultFixedFeeMultiplier + } + return c.FeePricer.FixedFeeMultiplier +} + var _ IConfig = &Config{} diff --git a/services/rfq/relayer/relconfig/iconfig_generated.go b/services/rfq/relayer/relconfig/iconfig_generated.go index 3bf427218b..488a1fe51f 100644 --- a/services/rfq/relayer/relconfig/iconfig_generated.go +++ b/services/rfq/relayer/relconfig/iconfig_generated.go @@ -32,4 +32,6 @@ type IConfig interface { GetTokens(chainID uint32) (map[string]TokenConfig, error) // GetTokenName returns the token name for the given chain and address. GetTokenName(chain uint32, addr string) (string, error) + // GetFixedFeeMultiplier returns the fixed fee multiplier. + GetFixedFeeMultiplier() float64 }