Skip to content

Commit

Permalink
chore(wallet)_: adjusting fees calculation to get closer to MM values
Browse files Browse the repository at this point in the history
  • Loading branch information
saledjenic committed Jan 31, 2025
1 parent 55268de commit 9957dd3
Show file tree
Hide file tree
Showing 8 changed files with 191 additions and 60 deletions.
4 changes: 2 additions & 2 deletions services/connector/commands/send_transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,12 +97,12 @@ func (c *SendTransactionCommand) Execute(ctx context.Context, request RPCRequest
if !fetchedFees.EIP1559Enabled {
params.GasPrice = (*hexutil.Big)(fetchedFees.GasPrice)
} else {
maxFees, err := fetchedFees.FeeFor(fees.GasFeeMedium)
maxFees, priorityFee, err := fetchedFees.FeeFor(fees.GasFeeMedium)
if err != nil {
return "", err
}
params.MaxFeePerGas = (*hexutil.Big)(maxFees)
params.MaxPriorityFeePerGas = (*hexutil.Big)(fetchedFees.MaxPriorityFeePerGas)
params.MaxPriorityFeePerGas = (*hexutil.Big)(priorityFee)
}
}

Expand Down
9 changes: 6 additions & 3 deletions services/connector/commands/send_transaction_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,8 @@ func TestSendTransactionWithSignalTimout(t *testing.T) {

mockedChainClient := mock_client.NewMockClientInterface(state.mockCtrl)
feeHistory := &fees.FeeHistory{}
state.rpcClient.EXPECT().Call(feeHistory, uint64(1), "eth_feeHistory", uint64(300), "latest", []int{25, 50, 75}).Times(1).Return(nil)
percentiles := []int{fees.RewardPercentiles1, fees.RewardPercentiles2, fees.RewardPercentiles3}
state.rpcClient.EXPECT().Call(feeHistory, uint64(1), "eth_feeHistory", uint64(300), "latest", percentiles).Times(1).Return(nil)
state.rpcClient.EXPECT().EthClient(uint64(1)).Times(1).Return(mockedChainClient, nil)
mockedChainClient.EXPECT().SuggestGasPrice(state.ctx).Times(1).Return(big.NewInt(1), nil)
state.rpcClient.EXPECT().EthClient(uint64(1)).Times(1).Return(mockedChainClient, nil)
Expand Down Expand Up @@ -129,7 +130,8 @@ func TestSendTransactionWithSignalAccepted(t *testing.T) {

mockedChainClient := mock_client.NewMockClientInterface(state.mockCtrl)
feeHistory := &fees.FeeHistory{}
state.rpcClient.EXPECT().Call(feeHistory, uint64(1), "eth_feeHistory", uint64(300), "latest", []int{25, 50, 75}).Times(1).Return(nil)
percentiles := []int{fees.RewardPercentiles1, fees.RewardPercentiles2, fees.RewardPercentiles3}
state.rpcClient.EXPECT().Call(feeHistory, uint64(1), "eth_feeHistory", uint64(300), "latest", percentiles).Times(1).Return(nil)
state.rpcClient.EXPECT().EthClient(uint64(1)).Times(1).Return(mockedChainClient, nil)
mockedChainClient.EXPECT().SuggestGasPrice(state.ctx).Times(1).Return(big.NewInt(1), nil)
state.rpcClient.EXPECT().EthClient(uint64(1)).Times(1).Return(mockedChainClient, nil)
Expand Down Expand Up @@ -172,7 +174,8 @@ func TestSendTransactionWithSignalRejected(t *testing.T) {

mockedChainClient := mock_client.NewMockClientInterface(state.mockCtrl)
feeHistory := &fees.FeeHistory{}
state.rpcClient.EXPECT().Call(feeHistory, uint64(1), "eth_feeHistory", uint64(300), "latest", []int{25, 50, 75}).Times(1).Return(nil)
percentiles := []int{fees.RewardPercentiles1, fees.RewardPercentiles2, fees.RewardPercentiles3}
state.rpcClient.EXPECT().Call(feeHistory, uint64(1), "eth_feeHistory", uint64(300), "latest", percentiles).Times(1).Return(nil)
state.rpcClient.EXPECT().EthClient(uint64(1)).Times(1).Return(mockedChainClient, nil)
mockedChainClient.EXPECT().SuggestGasPrice(state.ctx).Times(1).Return(big.NewInt(1), nil)
state.rpcClient.EXPECT().EthClient(uint64(1)).Times(1).Return(mockedChainClient, nil)
Expand Down
3 changes: 2 additions & 1 deletion services/connector/connector_flows_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,8 @@ func TestRequestAccountsSwitchChainAndSendTransactionFlow(t *testing.T) {
// Send transaction
mockedChainClient := mock_client.NewMockClientInterface(state.mockCtrl)
feeHistory := &fees.FeeHistory{}
state.rpcClient.EXPECT().Call(feeHistory, uint64(1), "eth_feeHistory", uint64(300), "latest", []int{25, 50, 75}).Times(1).Return(nil)
percentiles := []int{fees.RewardPercentiles1, fees.RewardPercentiles2, fees.RewardPercentiles3}
state.rpcClient.EXPECT().Call(feeHistory, uint64(1), "eth_feeHistory", uint64(300), "latest", percentiles).Times(1).Return(nil)
state.rpcClient.EXPECT().EthClient(uint64(1)).Times(1).Return(mockedChainClient, nil)
mockedChainClient.EXPECT().SuggestGasPrice(state.ctx).Times(1).Return(big.NewInt(1), nil)
state.rpcClient.EXPECT().EthClient(uint64(1)).Times(1).Return(mockedChainClient, nil)
Expand Down
80 changes: 59 additions & 21 deletions services/wallet/router/fees/fees.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ import (
"github.com/status-im/status-go/services/wallet/common"
)

const (
RewardPercentiles1 = 10
RewardPercentiles2 = 50
RewardPercentiles3 = 90
)

type GasFeeMode int

const (
Expand All @@ -26,9 +32,12 @@ var (
)

type MaxFeesLevels struct {
Low *hexutil.Big `json:"low"`
Medium *hexutil.Big `json:"medium"`
High *hexutil.Big `json:"high"`
Low *hexutil.Big `json:"low"`
LowPriority *hexutil.Big `json:"lowPriority"`
Medium *hexutil.Big `json:"medium"`
MediumPriority *hexutil.Big `json:"mediumPriority"`
High *hexutil.Big `json:"high"`
HighPriority *hexutil.Big `json:"highPriority"`
}

type MaxPriorityFeesSuggestedBounds struct {
Expand All @@ -41,7 +50,7 @@ type SuggestedFees struct {
BaseFee *big.Int
CurrentBaseFee *big.Int // Current network base fee (in ETH WEI)
MaxFeesLevels *MaxFeesLevels
MaxPriorityFeePerGas *big.Int
MaxPriorityFeePerGas *big.Int // TODO: remove once clients stop using this field
MaxPriorityFeeSuggestedBounds *MaxPriorityFeesSuggestedBounds
L1GasFee *big.Float
EIP1559Enabled bool
Expand All @@ -62,23 +71,23 @@ type SuggestedFeesGwei struct {
EIP1559Enabled bool `json:"eip1559Enabled"`
}

func (m *MaxFeesLevels) FeeFor(mode GasFeeMode) (*big.Int, error) {
func (m *MaxFeesLevels) FeeFor(mode GasFeeMode) (*big.Int, *big.Int, error) {
if mode == GasFeeCustom {
return nil, ErrCustomFeeModeNotAvailableInSuggestedFees
return nil, nil, ErrCustomFeeModeNotAvailableInSuggestedFees
}

if mode == GasFeeLow {
return m.Low.ToInt(), nil
return m.Low.ToInt(), m.LowPriority.ToInt(), nil
}

if mode == GasFeeHigh {
return m.High.ToInt(), nil
return m.High.ToInt(), m.HighPriority.ToInt(), nil
}

return m.Medium.ToInt(), nil
return m.Medium.ToInt(), m.MediumPriority.ToInt(), nil
}

func (s *SuggestedFees) FeeFor(mode GasFeeMode) (*big.Int, error) {
func (s *SuggestedFees) FeeFor(mode GasFeeMode) (*big.Int, *big.Int, error) {
return s.MaxFeesLevels.FeeFor(mode)
}

Expand All @@ -87,32 +96,61 @@ type FeeManager struct {
}

func (f *FeeManager) SuggestedFees(ctx context.Context, chainID uint64) (*SuggestedFees, error) {
feeHistory, err := f.getFeeHistory(ctx, chainID, 300, "latest", []int{25, 50, 75})
feeHistory, err := f.getFeeHistory(ctx, chainID, 300, "latest", []int{RewardPercentiles1, RewardPercentiles2, RewardPercentiles3})
if err != nil {
return f.getNonEIP1559SuggestedFees(ctx, chainID)
}

maxPriorityFeePerGasLowerBound, maxPriorityFeePerGas, maxPriorityFeePerGasUpperBound, baseFee, err := getEIP1559SuggestedFees(feeHistory)
lowPriorityFeePerGasLowerBound, mediumPriorityFeePerGas, maxPriorityFeePerGasUpperBound, baseFee, err := getEIP1559SuggestedFees(feeHistory)
if err != nil {
return f.getNonEIP1559SuggestedFees(ctx, chainID)
}

return &SuggestedFees{
suggestedFees := &SuggestedFees{
GasPrice: big.NewInt(0),
BaseFee: baseFee,
CurrentBaseFee: baseFee,
MaxPriorityFeePerGas: maxPriorityFeePerGas,
MaxPriorityFeePerGas: mediumPriorityFeePerGas,
MaxPriorityFeeSuggestedBounds: &MaxPriorityFeesSuggestedBounds{
Lower: maxPriorityFeePerGasLowerBound,
Lower: lowPriorityFeePerGasLowerBound,
Upper: maxPriorityFeePerGasUpperBound,
},
MaxFeesLevels: &MaxFeesLevels{
Low: (*hexutil.Big)(new(big.Int).Add(baseFee, maxPriorityFeePerGas)),
Medium: (*hexutil.Big)(new(big.Int).Add(new(big.Int).Mul(baseFee, big.NewInt(2)), maxPriorityFeePerGas)),
High: (*hexutil.Big)(new(big.Int).Add(new(big.Int).Mul(baseFee, big.NewInt(3)), maxPriorityFeePerGas)),
},
EIP1559Enabled: true,
}, nil
}

if chainID == common.EthereumMainnet || chainID == common.EthereumSepolia || chainID == common.AnvilMainnet {
networkCongestion := calculateNetworkCongestion(feeHistory)

baseFeeFloat := new(big.Float).SetUint64(baseFee.Uint64())
baseFeeFloat.Mul(baseFeeFloat, big.NewFloat(networkCongestion))
additionBasedOnCongestion := new(big.Int)
baseFeeFloat.Int(additionBasedOnCongestion)

mediumBaseFee := new(big.Int).Add(baseFee, additionBasedOnCongestion)

highBaseFee := new(big.Int).Mul(baseFee, big.NewInt(2))
highBaseFee.Add(highBaseFee, additionBasedOnCongestion)

suggestedFees.MaxFeesLevels = &MaxFeesLevels{
Low: (*hexutil.Big)(new(big.Int).Add(baseFee, lowPriorityFeePerGasLowerBound)),
LowPriority: (*hexutil.Big)(lowPriorityFeePerGasLowerBound),
Medium: (*hexutil.Big)(new(big.Int).Add(mediumBaseFee, mediumPriorityFeePerGas)),
MediumPriority: (*hexutil.Big)(mediumPriorityFeePerGas),
High: (*hexutil.Big)(new(big.Int).Add(highBaseFee, maxPriorityFeePerGasUpperBound)),
HighPriority: (*hexutil.Big)(maxPriorityFeePerGasUpperBound),
}
} else {
suggestedFees.MaxFeesLevels = &MaxFeesLevels{
Low: (*hexutil.Big)(new(big.Int).Add(baseFee, lowPriorityFeePerGasLowerBound)),
LowPriority: (*hexutil.Big)(lowPriorityFeePerGasLowerBound),
Medium: (*hexutil.Big)(new(big.Int).Add(new(big.Int).Mul(baseFee, big.NewInt(4)), mediumPriorityFeePerGas)),
MediumPriority: (*hexutil.Big)(mediumPriorityFeePerGas),
High: (*hexutil.Big)(new(big.Int).Add(new(big.Int).Mul(baseFee, big.NewInt(10)), maxPriorityFeePerGasUpperBound)),
HighPriority: (*hexutil.Big)(maxPriorityFeePerGasUpperBound),
}
}

return suggestedFees, nil
}

// //////////////////////////////////////////////////////////////////////////////
Expand Down
35 changes: 23 additions & 12 deletions services/wallet/router/fees/fees_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,8 @@ func TestSuggestedFeesForNotEIP1559CompatibleChains(t *testing.T) {
chainID := uint64(1)
gasPrice := big.NewInt(1)
feeHistory := &FeeHistory{}

state.rpcClient.EXPECT().Call(feeHistory, chainID, "eth_feeHistory", uint64(300), "latest", []int{25, 50, 75}).Times(1).Return(nil)
percentiles := []int{RewardPercentiles1, RewardPercentiles2, RewardPercentiles3}
state.rpcClient.EXPECT().Call(feeHistory, chainID, "eth_feeHistory", uint64(300), "latest", percentiles).Times(1).Return(nil)
mockedChainClient := mock_client.NewMockClientInterface(state.mockCtrl)
state.rpcClient.EXPECT().EthClient(chainID).Times(1).Return(mockedChainClient, nil)
mockedChainClient.EXPECT().SuggestGasPrice(state.ctx).Times(1).Return(gasPrice, nil)
Expand All @@ -88,8 +88,8 @@ func TestSuggestedFeesForEIP1559CompatibleChains(t *testing.T) {

chainID := uint64(1)
feeHistory := &FeeHistory{}

state.rpcClient.EXPECT().Call(feeHistory, chainID, "eth_feeHistory", uint64(300), "latest", []int{25, 50, 75}).Times(1).Return(nil).
percentiles := []int{RewardPercentiles1, RewardPercentiles2, RewardPercentiles3}
state.rpcClient.EXPECT().Call(feeHistory, chainID, "eth_feeHistory", uint64(300), "latest", percentiles).Times(1).Return(nil).
Do(func(feeHistory, chainID, method any, args ...any) {
feeHistoryResponse := &FeeHistory{
BaseFeePerGas: []string{
Expand Down Expand Up @@ -156,14 +156,25 @@ func TestSuggestedFeesForEIP1559CompatibleChains(t *testing.T) {
assert.NoError(t, err)
assert.NotNil(t, suggestedFees)

variadicFee1 := big.NewInt(0).Sub(suggestedFees.MaxFeesLevels.High.ToInt(), suggestedFees.MaxFeesLevels.HighPriority.ToInt())
variadicFee1.Sub(variadicFee1, big.NewInt(0).Mul(big.NewInt(2), suggestedFees.BaseFee))

variadicFee2 := big.NewInt(0).Sub(suggestedFees.MaxFeesLevels.Medium.ToInt(), suggestedFees.MaxFeesLevels.MediumPriority.ToInt())
variadicFee2.Sub(variadicFee2, suggestedFees.BaseFee)

assert.Equal(t, variadicFee1, variadicFee2)

assert.Equal(t, big.NewInt(0), suggestedFees.GasPrice)
assert.Equal(t, big.NewInt(6958609414), suggestedFees.BaseFee)
assert.Equal(t, big.NewInt(6958609414), suggestedFees.CurrentBaseFee)
assert.Equal(t, big.NewInt(7928609414), suggestedFees.MaxFeesLevels.Low.ToInt())
assert.Equal(t, big.NewInt(14887218828), suggestedFees.MaxFeesLevels.Medium.ToInt())
assert.Equal(t, big.NewInt(21845828242), suggestedFees.MaxFeesLevels.High.ToInt())
assert.Equal(t, big.NewInt(970000000), suggestedFees.MaxPriorityFeePerGas)
assert.Equal(t, big.NewInt(54715554), suggestedFees.MaxPriorityFeeSuggestedBounds.Lower)
assert.Equal(t, big.NewInt(1636040877), suggestedFees.MaxPriorityFeeSuggestedBounds.Upper)
assert.Equal(t, big.NewInt(5362838082), suggestedFees.BaseFee)
assert.Equal(t, big.NewInt(5362838082), suggestedFees.CurrentBaseFee)
assert.Equal(t, big.NewInt(5462838082), suggestedFees.MaxFeesLevels.Low.ToInt())
assert.Equal(t, big.NewInt(100000000), suggestedFees.MaxFeesLevels.LowPriority.ToInt())
assert.Equal(t, big.NewInt(7795989313), suggestedFees.MaxFeesLevels.Medium.ToInt())
assert.Equal(t, big.NewInt(970000000), suggestedFees.MaxFeesLevels.MediumPriority.ToInt())
assert.Equal(t, big.NewInt(14104411640), suggestedFees.MaxFeesLevels.High.ToInt())
assert.Equal(t, big.NewInt(1915584245), suggestedFees.MaxFeesLevels.HighPriority.ToInt())
assert.Equal(t, suggestedFees.MaxFeesLevels.MediumPriority.ToInt(), suggestedFees.MaxPriorityFeePerGas)
assert.Equal(t, big.NewInt(100000000), suggestedFees.MaxPriorityFeeSuggestedBounds.Lower)
assert.Equal(t, big.NewInt(1915584245), suggestedFees.MaxPriorityFeeSuggestedBounds.Upper)
assert.True(t, suggestedFees.EIP1559Enabled)
}
69 changes: 60 additions & 9 deletions services/wallet/router/fees/suggested_priority.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,13 @@ import (
"github.com/ethereum/go-ethereum/common/hexutil"
)

const baseFeeIncreaseFactor = 1.33
const (
// To get closer to MM values, we don't want to increase the base fee
baseFeeIncreaseFactor = 1.025 // 2.5% increase

priorityWeight = 0.7
gasUsedWeight = 0.3
)

func hexStringToBigInt(value string) (*big.Int, error) {
valueWitoutPrefix := strings.TrimPrefix(value, "0x")
Expand All @@ -26,11 +32,14 @@ func scaleBaseFeePerGas(value string) (*big.Int, error) {
if err != nil {
return nil, err
}
valueDouble := new(big.Float).SetInt(val)
valueDouble.Mul(valueDouble, big.NewFloat(baseFeeIncreaseFactor))
scaledValue := new(big.Int)
valueDouble.Int(scaledValue)
return scaledValue, nil
if baseFeeIncreaseFactor > 0 {
valueDouble := new(big.Float).SetInt(val)
valueDouble.Mul(valueDouble, big.NewFloat(baseFeeIncreaseFactor))
scaledValue := new(big.Int)
valueDouble.Int(scaledValue)
return scaledValue, nil
}
return val, nil
}

func (f *FeeManager) getNonEIP1559SuggestedFees(ctx context.Context, chainID uint64) (*SuggestedFees, error) {
Expand Down Expand Up @@ -108,7 +117,7 @@ func getEIP1559SuggestedFees(feeHistory *FeeHistory) (lowPriorityFee, avgPriorit
return currentPriorityFees[a].Cmp(currentPriorityFees[b]) < 0
})

percentileIndex := int(float64(len(currentPriorityFees)) * 0.4)
percentileIndex := int(float64(len(currentPriorityFees)) * 0.5)
if i == 0 {
lowPriorityFee = currentPriorityFees[percentileIndex]
} else if i == 1 {
Expand All @@ -121,18 +130,60 @@ func getEIP1559SuggestedFees(feeHistory *FeeHistory) (lowPriorityFee, avgPriorit
}

// Adjust low priority fee if it's equal to avg
lowIndex := int(float64(len(priorityFees[0])) * 0.4)
lowIndex := int(float64(len(priorityFees[0])) * 0.5)
for lowIndex > 0 && lowPriorityFee == avgPriorityFee {
lowIndex--
lowPriorityFee = priorityFees[0][lowIndex]
}

// Adjust high priority fee if it's equal to avg
highIndex := int(float64(len(priorityFees[2])) * 0.4)
highIndex := int(float64(len(priorityFees[2])) * 0.5)
for highIndex < len(priorityFees[2])-1 && highPriorityFee == avgPriorityFee {
highIndex++
highPriorityFee = priorityFees[2][highIndex]
}

return lowPriorityFee, avgPriorityFee, highPriorityFee, suggestedBaseFee, nil
}

func calculateNetworkCongestion(feeHistory *FeeHistory) float64 {
if len(feeHistory.BaseFeePerGas) == 0 || len(feeHistory.Reward) == 0 || len(feeHistory.GasUsedRatio) == 0 {
return 0.0
}

var totalBaseFee uint64
for _, baseFee := range feeHistory.BaseFeePerGas {
fee, err := hexStringToBigInt(baseFee)
if err != nil {
return 0.0
}
totalBaseFee = totalBaseFee + fee.Uint64()
}
avgBaseFee := float64(totalBaseFee) / float64(len(feeHistory.BaseFeePerGas))

var totalPriorityFee uint64
var countPriorityFees int
for _, rewardSet := range feeHistory.Reward {
for _, reward := range rewardSet {
fee, err := hexStringToBigInt(reward)
if err != nil {
return 0.0
}
totalPriorityFee = totalPriorityFee + fee.Uint64()
countPriorityFees++
}
}
avgPriorityFee := float64(totalPriorityFee) / float64(countPriorityFees)

var totalGasUsedRatio float64
for _, gasUsedRatio := range feeHistory.GasUsedRatio {
totalGasUsedRatio += gasUsedRatio
}
avgGasUsedRatio := totalGasUsedRatio / float64(len(feeHistory.GasUsedRatio))

priorityFeeRatio := avgPriorityFee / avgBaseFee

congestionScore := (priorityFeeRatio * priorityWeight) + (avgGasUsedRatio * gasUsedWeight)

return congestionScore
}
Loading

0 comments on commit 9957dd3

Please sign in to comment.