Skip to content

Commit

Permalink
FeePricer: use different multipliers for quote / relay (#2663)
Browse files Browse the repository at this point in the history
* Feat: split FixedFeeMultiplier into relay and quote variants

* Fix: tests

* Feat: isRelay -> isQuote

* Feat: default relay mult to quote mult

* Fix: config tests

* [goreleaser]
  • Loading branch information
dwasse authored Jun 5, 2024
1 parent edb67ff commit 5de3ede
Show file tree
Hide file tree
Showing 5 changed files with 144 additions and 105 deletions.
46 changes: 26 additions & 20 deletions services/rfq/relayer/pricer/fee_pricer.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down Expand Up @@ -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)
Expand All @@ -98,15 +98,15 @@ 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
}

// 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
}
Expand All @@ -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)
Expand All @@ -133,15 +133,15 @@ 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
}

// 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
}
Expand All @@ -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()),
Expand All @@ -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)),
Expand Down Expand Up @@ -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)
}
}

Expand All @@ -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()),
Expand Down
17 changes: 13 additions & 4 deletions services/rfq/relayer/pricer/fee_pricer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -277,16 +278,24 @@ 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)

// 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)

// 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)
Expand All @@ -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)
Expand Down
6 changes: 4 additions & 2 deletions services/rfq/relayer/relconfig/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
}
Expand Down
134 changes: 67 additions & 67 deletions services/rfq/relayer/relconfig/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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) {
Expand All @@ -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,
Expand Down
Loading

0 comments on commit 5de3ede

Please sign in to comment.