mirror of
https://github.com/status-im/status-go.git
synced 2025-02-21 03:08:29 +00:00
feat(wallet)_: calculating tx estimated time
This commit is contained in:
parent
1bfb0cef02
commit
0bf6048801
@ -97,7 +97,7 @@ func (c *SendTransactionCommand) Execute(ctx context.Context, request RPCRequest
|
||||
if !fetchedFees.EIP1559Enabled {
|
||||
params.GasPrice = (*hexutil.Big)(fetchedFees.GasPrice)
|
||||
} else {
|
||||
maxFees, priorityFee, err := fetchedFees.FeeFor(fees.GasFeeMedium)
|
||||
maxFees, priorityFee, _, err := fetchedFees.FeeFor(fees.GasFeeMedium)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
@ -481,6 +481,11 @@ func (api *API) GetTransactionEstimatedTime(ctx context.Context, chainID uint64,
|
||||
return api.s.router.GetFeesManager().TransactionEstimatedTime(ctx, chainID, gweiToWei(maxFeePerGas)), nil
|
||||
}
|
||||
|
||||
func (api *API) GetTransactionEstimatedTimeV2(ctx context.Context, chainID uint64, maxFeePerGas *hexutil.Big, maxPriorityFeePerGas *hexutil.Big) (uint, error) {
|
||||
logutils.ZapLogger().Debug("call to getTransactionEstimatedTimeV2")
|
||||
return api.s.router.GetFeesManager().TransactionEstimatedTimeV2(ctx, chainID, maxFeePerGas.ToInt(), maxPriorityFeePerGas.ToInt()), nil
|
||||
}
|
||||
|
||||
func gweiToWei(val *big.Float) *big.Int {
|
||||
res, _ := new(big.Float).Mul(val, big.NewFloat(1000000000)).Int(nil)
|
||||
return res
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"math/big"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
gethParams "github.com/ethereum/go-ethereum/params"
|
||||
"github.com/status-im/status-go/params"
|
||||
@ -75,3 +76,11 @@ func WeiToGwei(val *big.Int) *big.Float {
|
||||
|
||||
return result.Quo(result, new(big.Float).SetInt(unit))
|
||||
}
|
||||
|
||||
func GetBlockCreationTimeForChain(chainID uint64) time.Duration {
|
||||
blockDuration, found := AverageBlockDurationForChain[ChainID(chainID)]
|
||||
if !found {
|
||||
blockDuration = AverageBlockDurationForChain[ChainID(UnknownChainID)]
|
||||
}
|
||||
return blockDuration
|
||||
}
|
||||
|
@ -5,10 +5,24 @@ import (
|
||||
"math"
|
||||
"math/big"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/status-im/status-go/services/wallet/common"
|
||||
)
|
||||
|
||||
const inclusionThreshold = 0.95
|
||||
const (
|
||||
inclusionThreshold = 0.95
|
||||
|
||||
priorityFeePercentileHigh = 0.6
|
||||
priorityFeePercentileMedium = 0.5
|
||||
priorityFeePercentileLow = 0.4
|
||||
|
||||
baseFeePercentileFirstBlock = 0.8
|
||||
baseFeePercentileSecondBlock = 0.7
|
||||
baseFeePercentileThirdBlock = 0.6
|
||||
baseFeePercentileFourthBlock = 0.5
|
||||
baseFeePercentileFifthBlock = 0.4
|
||||
baseFeePercentileSixthBlock = 0.3
|
||||
)
|
||||
|
||||
type TransactionEstimation int
|
||||
|
||||
@ -30,8 +44,8 @@ func (f *FeeManager) TransactionEstimatedTime(ctx context.Context, chainID uint6
|
||||
}
|
||||
|
||||
func (f *FeeManager) estimatedTime(feeHistory *FeeHistory, maxFeePerGas *big.Int) TransactionEstimation {
|
||||
fees, err := f.getFeeHistorySorted(feeHistory)
|
||||
if err != nil || len(fees) == 0 {
|
||||
fees := f.convertToBigIntAndSort(feeHistory.BaseFeePerGas)
|
||||
if len(fees) == 0 {
|
||||
return Unknown
|
||||
}
|
||||
|
||||
@ -102,14 +116,110 @@ func (f *FeeManager) estimatedTime(feeHistory *FeeHistory, maxFeePerGas *big.Int
|
||||
return MoreThanFiveMinutes
|
||||
}
|
||||
|
||||
func (f *FeeManager) getFeeHistorySorted(feeHistory *FeeHistory) ([]*big.Int, error) {
|
||||
fees := []*big.Int{}
|
||||
for _, fee := range feeHistory.BaseFeePerGas {
|
||||
i := new(big.Int)
|
||||
i.SetString(strings.Replace(fee, "0x", "", 1), 16)
|
||||
fees = append(fees, i)
|
||||
func (f *FeeManager) convertToBigIntAndSort(hexArray []string) []*big.Int {
|
||||
values := []*big.Int{}
|
||||
for _, sValue := range hexArray {
|
||||
iValue := new(big.Int)
|
||||
_, ok := iValue.SetString(sValue, 0)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
values = append(values, iValue)
|
||||
}
|
||||
|
||||
sort.Slice(fees, func(i, j int) bool { return fees[i].Cmp(fees[j]) < 0 })
|
||||
return fees, nil
|
||||
sort.Slice(values, func(i, j int) bool { return values[i].Cmp(values[j]) < 0 })
|
||||
return values
|
||||
}
|
||||
|
||||
func (f *FeeManager) getFeeHistoryForTimeEstimation(ctx context.Context, chainID uint64) (*FeeHistory, error) {
|
||||
blockCount := uint64(10) // use the last 10 blocks for L1 chains
|
||||
if chainID != common.EthereumMainnet &&
|
||||
chainID != common.EthereumSepolia &&
|
||||
chainID != common.AnvilMainnet {
|
||||
blockCount = 50 // use the last 50 blocks for L2 chains
|
||||
}
|
||||
return f.getFeeHistory(ctx, chainID, blockCount, "latest", []int{RewardPercentiles2})
|
||||
}
|
||||
|
||||
func calculateTimeForInclusion(chainID uint64, expectedInclusionInBlock int) uint {
|
||||
blockCreationTime := common.GetBlockCreationTimeForChain(chainID)
|
||||
blockCreationTimeInSeconds := uint(blockCreationTime.Seconds())
|
||||
|
||||
// the client will decide how to display estimated times, status-go sends it in the steps of 5 (for example the client
|
||||
// should display "more than 1 minute" if the expected inclusion time is 60 seconds or more.
|
||||
expectedInclusionTime := uint(expectedInclusionInBlock) * blockCreationTimeInSeconds
|
||||
return (expectedInclusionTime/5 + 1) * 5
|
||||
}
|
||||
|
||||
func getBaseFeePercentileIndex(sortedBaseFees []*big.Int, percentile float64, networkCongestion float64) int {
|
||||
// calculate the index of the base fee for the given percentile corrected by the network congestion
|
||||
index := int(float64(len(sortedBaseFees)) * percentile * networkCongestion)
|
||||
if index >= len(sortedBaseFees) {
|
||||
return len(sortedBaseFees) - 1
|
||||
}
|
||||
return index
|
||||
}
|
||||
|
||||
// TransactionEstimatedTimeV2 returns the estimated time in seconds for a transaction to be included in a block
|
||||
func (f *FeeManager) TransactionEstimatedTimeV2(ctx context.Context, chainID uint64, maxFeePerGas *big.Int, priorityFee *big.Int) uint {
|
||||
feeHistory, err := f.getFeeHistoryForTimeEstimation(ctx, chainID)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
return f.estimatedTimeV2(feeHistory, maxFeePerGas, priorityFee, chainID)
|
||||
}
|
||||
|
||||
func (f *FeeManager) estimatedTimeV2(feeHistory *FeeHistory, txMaxFeePerGas *big.Int, txPriorityFee *big.Int, chainID uint64) uint {
|
||||
sortedBaseFees := f.convertToBigIntAndSort(feeHistory.BaseFeePerGas)
|
||||
if len(sortedBaseFees) == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
var mediumPriorityFees []string // based on 50th percentile in the last 100 blocks
|
||||
for _, fee := range feeHistory.Reward {
|
||||
mediumPriorityFees = append(mediumPriorityFees, fee[0])
|
||||
}
|
||||
mediumPriorityFeesSorted := f.convertToBigIntAndSort(mediumPriorityFees)
|
||||
if len(mediumPriorityFeesSorted) == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
txBaseFee := new(big.Int).Sub(txMaxFeePerGas, txPriorityFee)
|
||||
|
||||
networkCongestion := calculateNetworkCongestion(feeHistory)
|
||||
|
||||
// Priority fee for the first two blocks has to be higher than 60th percentile of the mediumPriorityFeesSorted
|
||||
priorityFeePercentileIndex := int(float64(len(mediumPriorityFeesSorted)) * priorityFeePercentileHigh)
|
||||
priorityFeeForFirstTwoBlock := mediumPriorityFeesSorted[priorityFeePercentileIndex]
|
||||
// Priority fee for the second two blocks has to be higher than 50th percentile of the mediumPriorityFeesSorted
|
||||
priorityFeePercentileIndex = int(float64(len(mediumPriorityFeesSorted)) * priorityFeePercentileMedium)
|
||||
priorityFeeForSecondTwoBlocks := mediumPriorityFeesSorted[priorityFeePercentileIndex]
|
||||
// Priority fee for the third two blocks has to be higher than 40th percentile of the mediumPriorityFeesSorted
|
||||
priorityFeePercentileIndex = int(float64(len(mediumPriorityFeesSorted)) * priorityFeePercentileLow)
|
||||
priorityFeeForThirdTwoBlocks := mediumPriorityFeesSorted[priorityFeePercentileIndex]
|
||||
|
||||
// To include the transaction in the block `inclusionInBlock` its base fee has to be in a higher than `baseFeePercentile`
|
||||
// and its priority fee has to be higher than the `priorityFee`
|
||||
inclusions := []struct {
|
||||
inclusionInBlock int
|
||||
baseFeePercentile float64
|
||||
priorityFee *big.Int
|
||||
}{
|
||||
{1, baseFeePercentileFirstBlock, priorityFeeForFirstTwoBlock},
|
||||
{2, baseFeePercentileSecondBlock, priorityFeeForFirstTwoBlock},
|
||||
{3, baseFeePercentileThirdBlock, priorityFeeForSecondTwoBlocks},
|
||||
{4, baseFeePercentileFourthBlock, priorityFeeForSecondTwoBlocks},
|
||||
{5, baseFeePercentileFifthBlock, priorityFeeForThirdTwoBlocks},
|
||||
{6, baseFeePercentileSixthBlock, priorityFeeForThirdTwoBlocks},
|
||||
}
|
||||
|
||||
for _, p := range inclusions {
|
||||
baseFeePercentileIndex := getBaseFeePercentileIndex(sortedBaseFees, p.baseFeePercentile, networkCongestion)
|
||||
if txBaseFee.Cmp(sortedBaseFees[baseFeePercentileIndex]) >= 0 && txPriorityFee.Cmp(p.priorityFee) >= 0 {
|
||||
return calculateTimeForInclusion(chainID, p.inclusionInBlock)
|
||||
}
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
@ -32,28 +32,31 @@ var (
|
||||
)
|
||||
|
||||
type MaxFeesLevels struct {
|
||||
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"`
|
||||
Low *hexutil.Big `json:"low"` // Low max fee per gas in WEI
|
||||
LowPriority *hexutil.Big `json:"lowPriority"` // Low priority fee in WEI
|
||||
LowEstimatedTime uint `json:"lowEstimatedTime"` // Estimated time for low fees in seconds
|
||||
Medium *hexutil.Big `json:"medium"` // Medium max fee per gas in WEI
|
||||
MediumPriority *hexutil.Big `json:"mediumPriority"` // Medium priority fee in WEI
|
||||
MediumEstimatedTime uint `json:"mediumEstimatedTime"` // Estimated time for medium fees in seconds
|
||||
High *hexutil.Big `json:"high"` // High max fee per gas in WEI
|
||||
HighPriority *hexutil.Big `json:"highPriority"` // High priority fee in WEI
|
||||
HighEstimatedTime uint `json:"highEstimatedTime"` // Estimated time for high fees in seconds
|
||||
}
|
||||
|
||||
type MaxPriorityFeesSuggestedBounds struct {
|
||||
Lower *big.Int
|
||||
Upper *big.Int
|
||||
Lower *big.Int // Lower bound for priority fee per gas in WEI
|
||||
Upper *big.Int // Upper bound for priority fee per gas in WEI
|
||||
}
|
||||
|
||||
type SuggestedFees struct {
|
||||
GasPrice *big.Int
|
||||
BaseFee *big.Int
|
||||
CurrentBaseFee *big.Int // Current network base fee (in ETH WEI)
|
||||
MaxFeesLevels *MaxFeesLevels
|
||||
MaxPriorityFeePerGas *big.Int // TODO: remove once clients stop using this field
|
||||
MaxPriorityFeeSuggestedBounds *MaxPriorityFeesSuggestedBounds
|
||||
L1GasFee *big.Float
|
||||
EIP1559Enabled bool
|
||||
GasPrice *big.Int // TODO: remove once clients stop using this field, used for EIP-1559 incompatible chains, not in use anymore
|
||||
BaseFee *big.Int // TODO: remove once clients stop using this field, current network base fee (in ETH WEI), kept for backward compatibility
|
||||
CurrentBaseFee *big.Int // Current network base fee (in ETH WEI)
|
||||
MaxFeesLevels *MaxFeesLevels // Max fees levels for low, medium and high fee modes
|
||||
MaxPriorityFeePerGas *big.Int // TODO: remove once clients stop using this field, kept for backward compatibility
|
||||
MaxPriorityFeeSuggestedBounds *MaxPriorityFeesSuggestedBounds // Lower and upper bounds for priority fee per gas in WEI
|
||||
L1GasFee *big.Float // TODO: remove once clients stop using this field, not in use anymore
|
||||
EIP1559Enabled bool // TODO: remove it since all chains we have support EIP-1559
|
||||
}
|
||||
|
||||
// //////////////////////////////////////////////////////////////////////////////
|
||||
@ -71,23 +74,23 @@ type SuggestedFeesGwei struct {
|
||||
EIP1559Enabled bool `json:"eip1559Enabled"`
|
||||
}
|
||||
|
||||
func (m *MaxFeesLevels) FeeFor(mode GasFeeMode) (*big.Int, *big.Int, error) {
|
||||
func (m *MaxFeesLevels) FeeFor(mode GasFeeMode) (*big.Int, *big.Int, uint, error) {
|
||||
if mode == GasFeeCustom {
|
||||
return nil, nil, ErrCustomFeeModeNotAvailableInSuggestedFees
|
||||
return nil, nil, 0, ErrCustomFeeModeNotAvailableInSuggestedFees
|
||||
}
|
||||
|
||||
if mode == GasFeeLow {
|
||||
return m.Low.ToInt(), m.LowPriority.ToInt(), nil
|
||||
return m.Low.ToInt(), m.LowPriority.ToInt(), m.LowEstimatedTime, nil
|
||||
}
|
||||
|
||||
if mode == GasFeeHigh {
|
||||
return m.High.ToInt(), m.HighPriority.ToInt(), nil
|
||||
return m.High.ToInt(), m.HighPriority.ToInt(), m.MediumEstimatedTime, nil
|
||||
}
|
||||
|
||||
return m.Medium.ToInt(), m.MediumPriority.ToInt(), nil
|
||||
return m.Medium.ToInt(), m.MediumPriority.ToInt(), m.HighEstimatedTime, nil
|
||||
}
|
||||
|
||||
func (s *SuggestedFees) FeeFor(mode GasFeeMode) (*big.Int, *big.Int, error) {
|
||||
func (s *SuggestedFees) FeeFor(mode GasFeeMode) (*big.Int, *big.Int, uint, error) {
|
||||
return s.MaxFeesLevels.FeeFor(mode)
|
||||
}
|
||||
|
||||
@ -150,6 +153,14 @@ func (f *FeeManager) SuggestedFees(ctx context.Context, chainID uint64) (*Sugges
|
||||
}
|
||||
}
|
||||
|
||||
feeHistory, err = f.getFeeHistoryForTimeEstimation(ctx, chainID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
suggestedFees.MaxFeesLevels.LowEstimatedTime = f.estimatedTimeV2(feeHistory, suggestedFees.MaxFeesLevels.Low.ToInt(), suggestedFees.MaxFeesLevels.LowPriority.ToInt(), chainID)
|
||||
suggestedFees.MaxFeesLevels.MediumEstimatedTime = f.estimatedTimeV2(feeHistory, suggestedFees.MaxFeesLevels.Medium.ToInt(), suggestedFees.MaxFeesLevels.MediumPriority.ToInt(), chainID)
|
||||
suggestedFees.MaxFeesLevels.HighEstimatedTime = f.estimatedTimeV2(feeHistory, suggestedFees.MaxFeesLevels.High.ToInt(), suggestedFees.MaxFeesLevels.HighPriority.ToInt(), chainID)
|
||||
|
||||
return suggestedFees, nil
|
||||
}
|
||||
|
||||
|
@ -64,32 +64,43 @@ func TestEstimatedTime(t *testing.T) {
|
||||
assert.Equal(t, LessThanOneMinute, estimation)
|
||||
}
|
||||
|
||||
func TestSuggestedFeesForNotEIP1559CompatibleChains(t *testing.T) {
|
||||
func TestEstimatedTimeV2(t *testing.T) {
|
||||
state := setupTest(t)
|
||||
|
||||
chainID := uint64(1)
|
||||
gasPrice := big.NewInt(1)
|
||||
// no fee history
|
||||
feeHistory := &FeeHistory{}
|
||||
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)
|
||||
state.rpcClient.EXPECT().Call(feeHistory, uint64(1), "eth_feeHistory", uint64(10), "latest", []int{RewardPercentiles2}).Times(1).Return(nil)
|
||||
|
||||
suggestedFees, err := state.feeManager.SuggestedFees(context.Background(), chainID)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, suggestedFees)
|
||||
assert.Equal(t, gasPrice, suggestedFees.GasPrice)
|
||||
assert.False(t, suggestedFees.EIP1559Enabled)
|
||||
}
|
||||
maxFeesPerGas := big.NewInt(2e9)
|
||||
priorityFeesPerGas := big.NewInt(2e8)
|
||||
estimation := state.feeManager.TransactionEstimatedTimeV2(context.Background(), uint64(1), maxFeesPerGas, priorityFeesPerGas)
|
||||
|
||||
func TestSuggestedFeesForEIP1559CompatibleChains(t *testing.T) {
|
||||
state := setupTest(t)
|
||||
assert.Equal(t, uint(0), estimation)
|
||||
|
||||
chainID := uint64(1)
|
||||
feeHistory := &FeeHistory{}
|
||||
percentiles := []int{RewardPercentiles1, RewardPercentiles2, RewardPercentiles3}
|
||||
state.rpcClient.EXPECT().Call(feeHistory, chainID, "eth_feeHistory", uint64(300), "latest", percentiles).Times(1).Return(nil).
|
||||
// there is fee history
|
||||
state.rpcClient.EXPECT().Call(feeHistory, uint64(1), "eth_feeHistory", uint64(10), "latest", []int{RewardPercentiles2}).Times(1).Return(nil).
|
||||
Do(func(feeHistory, chainID, method any, args ...any) {
|
||||
feeHistoryResponse := &FeeHistory{
|
||||
BaseFeePerGas: []string{
|
||||
"0x12f0e070b",
|
||||
"0x13f10da8b",
|
||||
"0x126c30d5e",
|
||||
"0x136e4fe51",
|
||||
"0x134180d5a",
|
||||
"0x134e32c33",
|
||||
"0x137da8d22",
|
||||
},
|
||||
}
|
||||
*feeHistory.(*FeeHistory) = *feeHistoryResponse
|
||||
})
|
||||
|
||||
maxFeesPerGas = big.NewInt(100e9)
|
||||
priorityFeesPerGas = big.NewInt(10e9)
|
||||
estimation = state.feeManager.TransactionEstimatedTimeV2(context.Background(), uint64(1), maxFeesPerGas, priorityFeesPerGas)
|
||||
|
||||
assert.Equal(t, uint(0), estimation)
|
||||
|
||||
// there is fee history and rewards
|
||||
state.rpcClient.EXPECT().Call(feeHistory, uint64(1), "eth_feeHistory", uint64(10), "latest", []int{RewardPercentiles2}).Times(1).Return(nil).
|
||||
Do(func(feeHistory, chainID, method any, args ...any) {
|
||||
feeHistoryResponse := &FeeHistory{
|
||||
BaseFeePerGas: []string{
|
||||
@ -152,6 +163,107 @@ func TestSuggestedFeesForEIP1559CompatibleChains(t *testing.T) {
|
||||
*feeHistory.(*FeeHistory) = *feeHistoryResponse
|
||||
})
|
||||
|
||||
estimation = state.feeManager.TransactionEstimatedTimeV2(context.Background(), uint64(1), maxFeesPerGas, priorityFeesPerGas)
|
||||
|
||||
assert.Equal(t, uint(15), estimation)
|
||||
}
|
||||
|
||||
func TestSuggestedFeesForNotEIP1559CompatibleChains(t *testing.T) {
|
||||
state := setupTest(t)
|
||||
|
||||
chainID := uint64(1)
|
||||
gasPrice := big.NewInt(1)
|
||||
feeHistory := &FeeHistory{}
|
||||
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)
|
||||
|
||||
suggestedFees, err := state.feeManager.SuggestedFees(context.Background(), chainID)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, suggestedFees)
|
||||
assert.Equal(t, gasPrice, suggestedFees.GasPrice)
|
||||
assert.False(t, suggestedFees.EIP1559Enabled)
|
||||
}
|
||||
|
||||
func TestSuggestedFeesForEIP1559CompatibleChains(t *testing.T) {
|
||||
state := setupTest(t)
|
||||
|
||||
feeHistoryResponse := &FeeHistory{
|
||||
BaseFeePerGas: []string{
|
||||
"0x12f0e070b",
|
||||
"0x13f10da8b",
|
||||
"0x126c30d5e",
|
||||
"0x136e4fe51",
|
||||
"0x134180d5a",
|
||||
"0x134e32c33",
|
||||
"0x137da8d22",
|
||||
},
|
||||
GasUsedRatio: []float64{
|
||||
0.7113286209349903,
|
||||
0.19531163333333335,
|
||||
0.7189235666666667,
|
||||
0.4639678021079083,
|
||||
0.5103012666666666,
|
||||
0.538413,
|
||||
0.16543626666666666,
|
||||
},
|
||||
OldestBlock: "0x1497d4b",
|
||||
Reward: [][]string{
|
||||
{
|
||||
"0x2faf080",
|
||||
"0x39d10680",
|
||||
"0x722d7ef5",
|
||||
},
|
||||
{
|
||||
"0x5f5e100",
|
||||
"0x3b9aca00",
|
||||
"0x59682f00",
|
||||
},
|
||||
{
|
||||
"0x342e4a2",
|
||||
"0x39d10680",
|
||||
"0x77359400",
|
||||
},
|
||||
{
|
||||
"0x14a22237",
|
||||
"0x40170350",
|
||||
"0x77359400",
|
||||
},
|
||||
{
|
||||
"0x9134860",
|
||||
"0x39d10680",
|
||||
"0x618400ad",
|
||||
},
|
||||
{
|
||||
"0x2faf080",
|
||||
"0x39d10680",
|
||||
"0x77359400",
|
||||
},
|
||||
{
|
||||
"0x1ed69035",
|
||||
"0x39d10680",
|
||||
"0x41d0a8d6",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
chainID := uint64(1)
|
||||
feeHistory := &FeeHistory{}
|
||||
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) {
|
||||
*feeHistory.(*FeeHistory) = *feeHistoryResponse
|
||||
})
|
||||
|
||||
feeHistory = &FeeHistory{}
|
||||
percentiles = []int{RewardPercentiles2}
|
||||
state.rpcClient.EXPECT().Call(feeHistory, chainID, "eth_feeHistory", uint64(10), "latest", percentiles).Times(1).Return(nil).
|
||||
Do(func(feeHistory, chainID, method any, args ...any) {
|
||||
*feeHistory.(*FeeHistory) = *feeHistoryResponse
|
||||
})
|
||||
|
||||
suggestedFees, err := state.feeManager.SuggestedFees(context.Background(), chainID)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, suggestedFees)
|
||||
@ -177,4 +289,7 @@ func TestSuggestedFeesForEIP1559CompatibleChains(t *testing.T) {
|
||||
assert.Equal(t, big.NewInt(100000000), suggestedFees.MaxPriorityFeeSuggestedBounds.Lower)
|
||||
assert.Equal(t, big.NewInt(1915584245), suggestedFees.MaxPriorityFeeSuggestedBounds.Upper)
|
||||
assert.True(t, suggestedFees.EIP1559Enabled)
|
||||
assert.Equal(t, uint(40), suggestedFees.MaxFeesLevels.LowEstimatedTime)
|
||||
assert.Equal(t, uint(15), suggestedFees.MaxFeesLevels.MediumEstimatedTime)
|
||||
assert.Equal(t, uint(15), suggestedFees.MaxFeesLevels.HighEstimatedTime)
|
||||
}
|
||||
|
@ -142,7 +142,7 @@ func (r *Router) SetTestBalanceMap(balanceMap map[string]*big.Int) {
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Router) setCustomTxDetails(pathTxIdentity *requests.PathTxIdentity, pathTxCustomParams *requests.PathTxCustomParams) error {
|
||||
func (r *Router) setCustomTxDetails(ctx context.Context, pathTxIdentity *requests.PathTxIdentity, pathTxCustomParams *requests.PathTxCustomParams) error {
|
||||
if pathTxIdentity == nil {
|
||||
return ErrTxIdentityNotProvided
|
||||
}
|
||||
@ -164,31 +164,17 @@ func (r *Router) setCustomTxDetails(pathTxIdentity *requests.PathTxIdentity, pat
|
||||
return ErrCannotCustomizeIfNoRoute
|
||||
}
|
||||
|
||||
fetchedFees, err := r.feesManager.SuggestedFees(ctx, pathTxIdentity.ChainID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, path := range r.activeRoutes.Best {
|
||||
if path.PathIdentity() != pathTxIdentity.PathIdentity() {
|
||||
continue
|
||||
}
|
||||
|
||||
if pathTxIdentity.IsApprovalTx {
|
||||
path.ApprovalGasFeeMode = pathTxCustomParams.GasFeeMode
|
||||
if pathTxCustomParams.GasFeeMode == fees.GasFeeCustom {
|
||||
path.ApprovalTxNonce = (*hexutil.Uint64)(&pathTxCustomParams.Nonce)
|
||||
path.ApprovalGasAmount = pathTxCustomParams.GasAmount
|
||||
path.ApprovalMaxFeesPerGas = pathTxCustomParams.MaxFeesPerGas
|
||||
path.ApprovalBaseFee = (*hexutil.Big)(new(big.Int).Sub(pathTxCustomParams.MaxFeesPerGas.ToInt(), pathTxCustomParams.PriorityFee.ToInt()))
|
||||
path.ApprovalPriorityFee = pathTxCustomParams.PriorityFee
|
||||
}
|
||||
} else {
|
||||
path.TxGasFeeMode = pathTxCustomParams.GasFeeMode
|
||||
if pathTxCustomParams.GasFeeMode == fees.GasFeeCustom {
|
||||
path.TxNonce = (*hexutil.Uint64)(&pathTxCustomParams.Nonce)
|
||||
path.TxGasAmount = pathTxCustomParams.GasAmount
|
||||
path.TxMaxFeesPerGas = pathTxCustomParams.MaxFeesPerGas
|
||||
path.TxBaseFee = (*hexutil.Big)(new(big.Int).Sub(pathTxCustomParams.MaxFeesPerGas.ToInt(), pathTxCustomParams.PriorityFee.ToInt()))
|
||||
path.TxPriorityFee = pathTxCustomParams.PriorityFee
|
||||
}
|
||||
}
|
||||
|
||||
// update the custom params
|
||||
r.lastInputParamsMutex.Lock()
|
||||
if r.lastInputParams.PathTxCustomParams == nil {
|
||||
r.lastInputParams.PathTxCustomParams = make(map[string]*requests.PathTxCustomParams)
|
||||
@ -196,6 +182,15 @@ func (r *Router) setCustomTxDetails(pathTxIdentity *requests.PathTxIdentity, pat
|
||||
r.lastInputParams.PathTxCustomParams[pathTxIdentity.TxIdentityKey()] = pathTxCustomParams
|
||||
r.lastInputParamsMutex.Unlock()
|
||||
|
||||
// update the path details
|
||||
usedNonces := make(map[uint64]uint64)
|
||||
err = r.evaluateAndUpdatePathDetails(ctx, path, fetchedFees, usedNonces, false, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// inform the client about the changes
|
||||
sendRouterResult(pathTxIdentity.RouterInputParamsUuid, r.activeRoutes, nil)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -207,14 +202,14 @@ func (r *Router) SetFeeMode(ctx context.Context, pathTxIdentity *requests.PathTx
|
||||
return ErrCustomFeeModeCannotBeSetThisWay
|
||||
}
|
||||
|
||||
return r.setCustomTxDetails(pathTxIdentity, &requests.PathTxCustomParams{GasFeeMode: feeMode})
|
||||
return r.setCustomTxDetails(ctx, pathTxIdentity, &requests.PathTxCustomParams{GasFeeMode: feeMode})
|
||||
}
|
||||
|
||||
func (r *Router) SetCustomTxDetails(ctx context.Context, pathTxIdentity *requests.PathTxIdentity, pathTxCustomParams *requests.PathTxCustomParams) error {
|
||||
if pathTxCustomParams != nil && pathTxCustomParams.GasFeeMode != fees.GasFeeCustom {
|
||||
return ErrOnlyCustomFeeModeCanBeSetThisWay
|
||||
}
|
||||
return r.setCustomTxDetails(pathTxIdentity, pathTxCustomParams)
|
||||
return r.setCustomTxDetails(ctx, pathTxIdentity, pathTxCustomParams)
|
||||
}
|
||||
|
||||
func newSuggestedRoutes(
|
||||
|
@ -195,7 +195,7 @@ func (r *Router) applyCustomFields(ctx context.Context, path *routes.Path, fetch
|
||||
|
||||
if r.lastInputParams.PathTxCustomParams == nil || len(r.lastInputParams.PathTxCustomParams) == 0 {
|
||||
// if no custom params are provided, use the initial fee mode
|
||||
maxFeesPerGas, priorityFee, err := fetchedFees.FeeFor(r.lastInputParams.GasFeeMode)
|
||||
maxFeesPerGas, priorityFee, estimatedTime, err := fetchedFees.FeeFor(r.lastInputParams.GasFeeMode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -204,31 +204,35 @@ func (r *Router) applyCustomFields(ctx context.Context, path *routes.Path, fetch
|
||||
path.ApprovalMaxFeesPerGas = (*hexutil.Big)(maxFeesPerGas)
|
||||
path.ApprovalBaseFee = (*hexutil.Big)(fetchedFees.BaseFee)
|
||||
path.ApprovalPriorityFee = (*hexutil.Big)(priorityFee)
|
||||
path.ApprovalEstimatedTime = estimatedTime
|
||||
}
|
||||
|
||||
path.TxGasFeeMode = r.lastInputParams.GasFeeMode
|
||||
path.TxMaxFeesPerGas = (*hexutil.Big)(maxFeesPerGas)
|
||||
path.TxBaseFee = (*hexutil.Big)(fetchedFees.BaseFee)
|
||||
path.TxPriorityFee = (*hexutil.Big)(priorityFee)
|
||||
path.TxEstimatedTime = estimatedTime
|
||||
} else {
|
||||
if path.ApprovalRequired {
|
||||
approvalTxIdentityKey := path.TxIdentityKey(true)
|
||||
if approvalTxCustomParams, ok := r.lastInputParams.PathTxCustomParams[approvalTxIdentityKey]; ok {
|
||||
path.ApprovalGasFeeMode = approvalTxCustomParams.GasFeeMode
|
||||
if approvalTxCustomParams.GasFeeMode != fees.GasFeeCustom {
|
||||
maxFeesPerGas, priorityFee, err := fetchedFees.FeeFor(approvalTxCustomParams.GasFeeMode)
|
||||
maxFeesPerGas, priorityFee, estimatedTime, err := fetchedFees.FeeFor(approvalTxCustomParams.GasFeeMode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
path.ApprovalMaxFeesPerGas = (*hexutil.Big)(maxFeesPerGas)
|
||||
path.ApprovalBaseFee = (*hexutil.Big)(fetchedFees.BaseFee)
|
||||
path.ApprovalPriorityFee = (*hexutil.Big)(priorityFee)
|
||||
path.ApprovalEstimatedTime = estimatedTime
|
||||
} else {
|
||||
path.ApprovalTxNonce = (*hexutil.Uint64)(&approvalTxCustomParams.Nonce)
|
||||
path.ApprovalGasAmount = approvalTxCustomParams.GasAmount
|
||||
path.ApprovalMaxFeesPerGas = approvalTxCustomParams.MaxFeesPerGas
|
||||
path.ApprovalBaseFee = (*hexutil.Big)(new(big.Int).Sub(approvalTxCustomParams.MaxFeesPerGas.ToInt(), approvalTxCustomParams.PriorityFee.ToInt()))
|
||||
path.ApprovalPriorityFee = approvalTxCustomParams.PriorityFee
|
||||
path.ApprovalEstimatedTime = r.feesManager.TransactionEstimatedTimeV2(ctx, path.FromChain.ChainID, path.ApprovalMaxFeesPerGas.ToInt(), path.ApprovalPriorityFee.ToInt())
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -237,19 +241,21 @@ func (r *Router) applyCustomFields(ctx context.Context, path *routes.Path, fetch
|
||||
if txCustomParams, ok := r.lastInputParams.PathTxCustomParams[txIdentityKey]; ok {
|
||||
path.TxGasFeeMode = txCustomParams.GasFeeMode
|
||||
if txCustomParams.GasFeeMode != fees.GasFeeCustom {
|
||||
maxFeesPerGas, priorityFee, err := fetchedFees.FeeFor(txCustomParams.GasFeeMode)
|
||||
maxFeesPerGas, priorityFee, estimatedTime, err := fetchedFees.FeeFor(txCustomParams.GasFeeMode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
path.TxMaxFeesPerGas = (*hexutil.Big)(maxFeesPerGas)
|
||||
path.TxBaseFee = (*hexutil.Big)(fetchedFees.BaseFee)
|
||||
path.TxPriorityFee = (*hexutil.Big)(priorityFee)
|
||||
path.TxEstimatedTime = estimatedTime
|
||||
} else {
|
||||
path.TxNonce = (*hexutil.Uint64)(&txCustomParams.Nonce)
|
||||
path.TxGasAmount = txCustomParams.GasAmount
|
||||
path.TxMaxFeesPerGas = txCustomParams.MaxFeesPerGas
|
||||
path.TxBaseFee = (*hexutil.Big)(new(big.Int).Sub(txCustomParams.MaxFeesPerGas.ToInt(), txCustomParams.PriorityFee.ToInt()))
|
||||
path.TxPriorityFee = txCustomParams.PriorityFee
|
||||
path.TxEstimatedTime = r.feesManager.TransactionEstimatedTimeV2(ctx, path.FromChain.ChainID, path.TxMaxFeesPerGas.ToInt(), path.TxPriorityFee.ToInt())
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -336,15 +342,6 @@ func (r *Router) evaluateAndUpdatePathDetails(ctx context.Context, path *routes.
|
||||
path.RequiredTokenBalance = requiredTokenBalance
|
||||
path.RequiredNativeBalance = requiredNativeBalance
|
||||
|
||||
path.TxEstimatedTime = r.feesManager.TransactionEstimatedTime(ctx, path.FromChain.ChainID, path.TxMaxFeesPerGas.ToInt())
|
||||
if path.ApprovalRequired {
|
||||
if path.TxMaxFeesPerGas.ToInt().Cmp(path.ApprovalMaxFeesPerGas.ToInt()) == 0 {
|
||||
path.ApprovalEstimatedTime = path.TxEstimatedTime
|
||||
} else {
|
||||
path.ApprovalEstimatedTime = r.feesManager.TransactionEstimatedTime(ctx, path.FromChain.ChainID, path.ApprovalMaxFeesPerGas.ToInt())
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -42,7 +42,7 @@ type Path struct {
|
||||
TxGasAmount uint64 // Gas used for the transaction
|
||||
TxBonderFees *hexutil.Big // Bonder fees for the transaction - used for Hop bridge (in selected token)
|
||||
TxTokenFees *hexutil.Big // Token fees for the transaction - used for bridges (represent the difference between the amount in and the amount out, in selected token)
|
||||
TxEstimatedTime fees.TransactionEstimation
|
||||
TxEstimatedTime uint // Estimated time for the transaction in seconds
|
||||
|
||||
TxFee *hexutil.Big // fee for the transaction (includes tx fee only, doesn't include approval fees, l1 fees, l1 approval fees, token fees or bonders fees, in ETH WEI)
|
||||
TxL1Fee *hexutil.Big // L1 fee for the transaction - used for for transactions placed on L2 chains (in ETH WEI)
|
||||
@ -57,7 +57,7 @@ type Path struct {
|
||||
ApprovalBaseFee *hexutil.Big // Base fee for the approval transaction (in ETH WEI)
|
||||
ApprovalPriorityFee *hexutil.Big // Priority fee for the approval transaction (in ETH WEI)
|
||||
ApprovalGasAmount uint64 // Gas used for the approval transaction
|
||||
ApprovalEstimatedTime fees.TransactionEstimation
|
||||
ApprovalEstimatedTime uint // Estimated time for the approval transaction in seconds
|
||||
|
||||
ApprovalFee *hexutil.Big // Total fee for the approval transaction (includes approval tx fees only, doesn't include approval l1 fees, in ETH WEI)
|
||||
ApprovalL1Fee *hexutil.Big // L1 fee for the approval transaction - used for for transactions placed on L2 chains (in ETH WEI)
|
||||
|
@ -36,7 +36,7 @@ func TestCopyPath(t *testing.T) {
|
||||
TxGasAmount: 100,
|
||||
TxBonderFees: (*hexutil.Big)(big.NewInt(100)),
|
||||
TxTokenFees: (*hexutil.Big)(big.NewInt(100)),
|
||||
TxEstimatedTime: fees.TransactionEstimation(100),
|
||||
TxEstimatedTime: 100,
|
||||
TxFee: (*hexutil.Big)(big.NewInt(100)),
|
||||
TxL1Fee: (*hexutil.Big)(big.NewInt(100)),
|
||||
ApprovalRequired: true,
|
||||
@ -46,7 +46,7 @@ func TestCopyPath(t *testing.T) {
|
||||
ApprovalBaseFee: (*hexutil.Big)(big.NewInt(100)),
|
||||
ApprovalPriorityFee: (*hexutil.Big)(big.NewInt(100)),
|
||||
ApprovalGasAmount: 100,
|
||||
ApprovalEstimatedTime: fees.TransactionEstimation(100),
|
||||
ApprovalEstimatedTime: 100,
|
||||
ApprovalFee: (*hexutil.Big)(big.NewInt(100)),
|
||||
ApprovalL1Fee: (*hexutil.Big)(big.NewInt(100)),
|
||||
TxTotalFee: (*hexutil.Big)(big.NewInt(100)),
|
||||
|
Loading…
x
Reference in New Issue
Block a user