Added improvements in resolving the best route (#5569)
* fix_: filtering out routes which do not match the amount in * fix_: finding the best route logic updated * fix_: "to" chains being used in sending bridge tx via hop are more explicit Using `ChainIDTo` depicts better an intention which chain should be used. * chore_: checking for required balance improved An error contains now more details, for which token on which chain there is not enough balance for executing a tx. Also check for required balance now calculates in fees for all tx that possibly can be sent from the same chain.
This commit is contained in:
parent
8bb0bb0b3c
commit
2bbdb35f6c
|
@ -21,12 +21,14 @@ var (
|
|||
ErrLockedAmountNotNegative = &errors.ErrorResponse{Code: errors.ErrorCode("WR-013"), Details: "locked amount must not be negative"}
|
||||
ErrLockedAmountExceedsTotalSendAmount = &errors.ErrorResponse{Code: errors.ErrorCode("WR-014"), Details: "locked amount exceeds the total amount to send"}
|
||||
ErrLockedAmountLessThanSendAmountAllNetworks = &errors.ErrorResponse{Code: errors.ErrorCode("WR-015"), Details: "locked amount is less than the total amount to send, but all networks are locked"}
|
||||
ErrNotEnoughTokenBalance = &errors.ErrorResponse{Code: errors.ErrorCode("WR-016"), Details: "not enough token balance"}
|
||||
ErrNotEnoughNativeBalance = &errors.ErrorResponse{Code: errors.ErrorCode("WR-017"), Details: "not enough native balance"}
|
||||
ErrNotEnoughTokenBalance = &errors.ErrorResponse{Code: errors.ErrorCode("WR-016"), Details: "{\"token\": %s, \"chainId\": %d}"}
|
||||
ErrNotEnoughNativeBalance = &errors.ErrorResponse{Code: errors.ErrorCode("WR-017"), Details: "{\"token\": %s, \"chainId\": %d}"}
|
||||
ErrNativeTokenNotFound = &errors.ErrorResponse{Code: errors.ErrorCode("WR-018"), Details: "native token not found"}
|
||||
ErrDisabledChainFoundAmongLockedNetworks = &errors.ErrorResponse{Code: errors.ErrorCode("WR-019"), Details: "disabled chain found among locked networks"}
|
||||
ErrENSSetPubKeyInvalidUsername = &errors.ErrorResponse{Code: errors.ErrorCode("WR-020"), Details: "a valid username, ending in '.eth', is required for ENSSetPubKey"}
|
||||
ErrLockedAmountExcludesAllSupported = &errors.ErrorResponse{Code: errors.ErrorCode("WR-021"), Details: "all supported chains are excluded, routing impossible"}
|
||||
ErrTokenNotFound = &errors.ErrorResponse{Code: errors.ErrorCode("WR-022"), Details: "token not found"}
|
||||
ErrNoBestRouteFound = &errors.ErrorResponse{Code: errors.ErrorCode("WR-023"), Details: "no best route found"}
|
||||
ErrCannotCheckReceiverBalance = &errors.ErrorResponse{Code: errors.ErrorCode("WR-024"), Details: "cannot check receiver balance"}
|
||||
ErrCannotCheckLockedAmounts = &errors.ErrorResponse{Code: errors.ErrorCode("WR-025"), Details: "cannot check locked amounts"}
|
||||
)
|
||||
|
|
|
@ -54,27 +54,23 @@ type SuggestedFeesGwei struct {
|
|||
EIP1559Enabled bool `json:"eip1559Enabled"`
|
||||
}
|
||||
|
||||
func (s *SuggestedFees) feeFor(mode GasFeeMode) *big.Int {
|
||||
if !s.EIP1559Enabled {
|
||||
return s.GasPrice
|
||||
}
|
||||
|
||||
func (m *MaxFeesLevels) feeFor(mode GasFeeMode) *big.Int {
|
||||
if mode == GasFeeLow {
|
||||
return s.MaxFeesLevels.Low.ToInt()
|
||||
return m.Low.ToInt()
|
||||
}
|
||||
|
||||
if mode == GasFeeHigh {
|
||||
return s.MaxFeesLevels.High.ToInt()
|
||||
return m.High.ToInt()
|
||||
}
|
||||
|
||||
return s.MaxFeesLevels.Medium.ToInt()
|
||||
return m.Medium.ToInt()
|
||||
}
|
||||
|
||||
func (s *SuggestedFees) feeFor(mode GasFeeMode) *big.Int {
|
||||
return s.MaxFeesLevels.feeFor(mode)
|
||||
}
|
||||
|
||||
func (s *SuggestedFeesGwei) feeFor(mode GasFeeMode) *big.Float {
|
||||
if !s.EIP1559Enabled {
|
||||
return s.GasPrice
|
||||
}
|
||||
|
||||
if mode == GasFeeLow {
|
||||
return s.MaxFeePerGasLow
|
||||
}
|
||||
|
@ -141,9 +137,9 @@ func (f *FeeManager) SuggestedFees(ctx context.Context, chainID uint64) (*Sugges
|
|||
BaseFee: big.NewInt(0),
|
||||
MaxPriorityFeePerGas: big.NewInt(0),
|
||||
MaxFeesLevels: &MaxFeesLevels{
|
||||
Low: (*hexutil.Big)(big.NewInt(0)),
|
||||
Medium: (*hexutil.Big)(big.NewInt(0)),
|
||||
High: (*hexutil.Big)(big.NewInt(0)),
|
||||
Low: (*hexutil.Big)(gasPrice),
|
||||
Medium: (*hexutil.Big)(gasPrice),
|
||||
High: (*hexutil.Big)(gasPrice),
|
||||
},
|
||||
EIP1559Enabled: false,
|
||||
}, nil
|
||||
|
|
|
@ -2,6 +2,7 @@ package router
|
|||
|
||||
import (
|
||||
"math/big"
|
||||
"reflect"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/status-im/status-go/services/wallet/router/pathprocessor"
|
||||
|
@ -20,6 +21,19 @@ func init() {
|
|||
}
|
||||
|
||||
func filterRoutesV2(routes [][]*PathV2, amountIn *big.Int, fromLockedAmount map[uint64]*hexutil.Big) [][]*PathV2 {
|
||||
for i := len(routes) - 1; i >= 0; i-- {
|
||||
routeAmount := big.NewInt(0)
|
||||
for _, p := range routes[i] {
|
||||
routeAmount.Add(routeAmount, p.AmountIn.ToInt())
|
||||
}
|
||||
|
||||
if routeAmount.Cmp(amountIn) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
routes = append(routes[:i], routes[i+1:]...)
|
||||
}
|
||||
|
||||
if len(fromLockedAmount) == 0 {
|
||||
return routes
|
||||
}
|
||||
|
@ -43,7 +57,7 @@ func filterNetworkComplianceV2(routes [][]*PathV2, fromLockedAmount map[uint64]*
|
|||
}
|
||||
|
||||
// Create fresh copies of the maps for each route check, because they are manipulated
|
||||
if isValidForNetworkComplianceV2(route, copyMap(fromIncluded), copyMap(fromExcluded)) {
|
||||
if isValidForNetworkComplianceV2(route, copyMapGeneric(fromIncluded, nil).(map[uint64]bool), copyMapGeneric(fromExcluded, nil).(map[uint64]bool)) {
|
||||
filteredRoutes = append(filteredRoutes, route)
|
||||
}
|
||||
}
|
||||
|
@ -57,6 +71,10 @@ func isValidForNetworkComplianceV2(route []*PathV2, fromIncluded, fromExcluded m
|
|||
zap.Any("fromExcluded", fromExcluded),
|
||||
)
|
||||
|
||||
if fromIncluded == nil || fromExcluded == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, path := range route {
|
||||
if path == nil || path.FromChain == nil {
|
||||
logger.Debug("Invalid path", zap.Any("path", path))
|
||||
|
@ -114,6 +132,10 @@ func filterCapacityValidationV2(routes [][]*PathV2, amountIn *big.Int, fromLocke
|
|||
func hasSufficientCapacityV2(route []*PathV2, amountIn *big.Int, fromLockedAmount map[uint64]*hexutil.Big) bool {
|
||||
for _, path := range route {
|
||||
if amount, ok := fromLockedAmount[path.FromChain.ChainID]; ok {
|
||||
if path.AmountIn.ToInt().Cmp(amount.ToInt()) != 0 {
|
||||
logger.Debug("Amount in does not match locked amount", zap.Any("path", path))
|
||||
return false
|
||||
}
|
||||
requiredAmountIn := new(big.Int).Sub(amountIn, amount.ToInt())
|
||||
restAmountIn := calculateRestAmountInV2(route, path)
|
||||
|
||||
|
@ -121,11 +143,7 @@ func hasSufficientCapacityV2(route []*PathV2, amountIn *big.Int, fromLockedAmoun
|
|||
logger.Debug("Required amount in", zap.String("requiredAmountIn", requiredAmountIn.String()))
|
||||
logger.Debug("Rest amount in", zap.String("restAmountIn", restAmountIn.String()))
|
||||
|
||||
if restAmountIn.Cmp(requiredAmountIn) >= 0 {
|
||||
path.AmountIn = amount
|
||||
path.AmountInLocked = true
|
||||
logger.Debug("Path has sufficient capacity", zap.Any("path", path))
|
||||
} else {
|
||||
if restAmountIn.Cmp(requiredAmountIn) < 0 {
|
||||
logger.Debug("Path does not have sufficient capacity", zap.Any("path", path))
|
||||
return false
|
||||
}
|
||||
|
@ -145,11 +163,21 @@ func calculateRestAmountInV2(route []*PathV2, excludePath *PathV2) *big.Int {
|
|||
return restAmountIn
|
||||
}
|
||||
|
||||
// copyMap creates a copy of the given map[uint64]bool
|
||||
func copyMap(original map[uint64]bool) map[uint64]bool {
|
||||
c := make(map[uint64]bool)
|
||||
for k, v := range original {
|
||||
c[k] = v
|
||||
// copyMapGeneric creates a copy of any map, if the deepCopyValue function is provided, it will be used to copy values.
|
||||
func copyMapGeneric(original interface{}, deepCopyValueFn func(interface{}) interface{}) interface{} {
|
||||
originalVal := reflect.ValueOf(original)
|
||||
if originalVal.Kind() != reflect.Map {
|
||||
return nil
|
||||
}
|
||||
return c
|
||||
|
||||
newMap := reflect.MakeMap(originalVal.Type())
|
||||
for iter := originalVal.MapRange(); iter.Next(); {
|
||||
if deepCopyValueFn != nil {
|
||||
newMap.SetMapIndex(iter.Key(), reflect.ValueOf(deepCopyValueFn(iter.Value().Interface())))
|
||||
} else {
|
||||
newMap.SetMapIndex(iter.Key(), iter.Value())
|
||||
}
|
||||
}
|
||||
|
||||
return newMap.Interface()
|
||||
}
|
||||
|
|
|
@ -27,11 +27,20 @@ var (
|
|||
amount5 = hexutil.Big(*big.NewInt(500))
|
||||
|
||||
path0 = &PathV2{FromChain: network4, AmountIn: &amount0}
|
||||
path1 = &PathV2{FromChain: network1, AmountIn: &amount1}
|
||||
path2 = &PathV2{FromChain: network2, AmountIn: &amount2}
|
||||
path3 = &PathV2{FromChain: network3, AmountIn: &amount3}
|
||||
path4 = &PathV2{FromChain: network4, AmountIn: &amount4}
|
||||
path5 = &PathV2{FromChain: network5, AmountIn: &amount5}
|
||||
|
||||
pathC1A1 = &PathV2{FromChain: network1, AmountIn: &amount1}
|
||||
|
||||
pathC2A1 = &PathV2{FromChain: network2, AmountIn: &amount1}
|
||||
pathC2A2 = &PathV2{FromChain: network2, AmountIn: &amount2}
|
||||
|
||||
pathC3A1 = &PathV2{FromChain: network3, AmountIn: &amount1}
|
||||
pathC3A2 = &PathV2{FromChain: network3, AmountIn: &amount2}
|
||||
pathC3A3 = &PathV2{FromChain: network3, AmountIn: &amount3}
|
||||
|
||||
pathC4A1 = &PathV2{FromChain: network4, AmountIn: &amount1}
|
||||
pathC4A4 = &PathV2{FromChain: network4, AmountIn: &amount4}
|
||||
|
||||
pathC5A5 = &PathV2{FromChain: network5, AmountIn: &amount5}
|
||||
)
|
||||
|
||||
func routesEqual(t *testing.T, expected, actual [][]*PathV2) bool {
|
||||
|
@ -167,33 +176,33 @@ func TestCalculateRestAmountInV2(t *testing.T) {
|
|||
expected *big.Int
|
||||
}{
|
||||
{
|
||||
name: "Exclude path1",
|
||||
route: []*PathV2{path1, path2, path3},
|
||||
excludePath: path1,
|
||||
name: "Exclude pathC1A1",
|
||||
route: []*PathV2{pathC1A1, pathC2A2, pathC3A3},
|
||||
excludePath: pathC1A1,
|
||||
expected: big.NewInt(500), // 200 + 300
|
||||
},
|
||||
{
|
||||
name: "Exclude path2",
|
||||
route: []*PathV2{path1, path2, path3},
|
||||
excludePath: path2,
|
||||
name: "Exclude pathC2A2",
|
||||
route: []*PathV2{pathC1A1, pathC2A2, pathC3A3},
|
||||
excludePath: pathC2A2,
|
||||
expected: big.NewInt(400), // 100 + 300
|
||||
},
|
||||
{
|
||||
name: "Exclude path3",
|
||||
route: []*PathV2{path1, path2, path3},
|
||||
excludePath: path3,
|
||||
name: "Exclude pathC3A3",
|
||||
route: []*PathV2{pathC1A1, pathC2A2, pathC3A3},
|
||||
excludePath: pathC3A3,
|
||||
expected: big.NewInt(300), // 100 + 200
|
||||
},
|
||||
{
|
||||
name: "Single path, exclude that path",
|
||||
route: []*PathV2{path1},
|
||||
excludePath: path1,
|
||||
route: []*PathV2{pathC1A1},
|
||||
excludePath: pathC1A1,
|
||||
expected: big.NewInt(0), // No other paths
|
||||
},
|
||||
{
|
||||
name: "Empty route",
|
||||
route: []*PathV2{},
|
||||
excludePath: path1,
|
||||
excludePath: pathC1A1,
|
||||
expected: big.NewInt(0), // No paths
|
||||
},
|
||||
{
|
||||
|
@ -222,42 +231,42 @@ func TestIsValidForNetworkComplianceV2(t *testing.T) {
|
|||
}{
|
||||
{
|
||||
name: "Route with all included chain IDs",
|
||||
route: []*PathV2{path1, path2},
|
||||
route: []*PathV2{pathC1A1, pathC2A2},
|
||||
fromIncluded: map[uint64]bool{1: true, 2: true},
|
||||
fromExcluded: map[uint64]bool{},
|
||||
expectedResult: true,
|
||||
},
|
||||
{
|
||||
name: "Route with fromExcluded only",
|
||||
route: []*PathV2{path1, path2},
|
||||
route: []*PathV2{pathC1A1, pathC2A2},
|
||||
fromIncluded: map[uint64]bool{},
|
||||
fromExcluded: map[uint64]bool{3: false, 4: false},
|
||||
expectedResult: true,
|
||||
},
|
||||
{
|
||||
name: "Route without excluded chain IDs",
|
||||
route: []*PathV2{path1, path2},
|
||||
route: []*PathV2{pathC1A1, pathC2A2},
|
||||
fromIncluded: map[uint64]bool{1: false, 2: false},
|
||||
fromExcluded: map[uint64]bool{3: false, 4: false},
|
||||
expectedResult: true,
|
||||
},
|
||||
{
|
||||
name: "Route with an excluded chain ID",
|
||||
route: []*PathV2{path1, path3},
|
||||
route: []*PathV2{pathC1A1, pathC3A3},
|
||||
fromIncluded: map[uint64]bool{1: false, 2: false},
|
||||
fromExcluded: map[uint64]bool{3: false, 4: false},
|
||||
expectedResult: false,
|
||||
},
|
||||
{
|
||||
name: "Route missing one included chain ID",
|
||||
route: []*PathV2{path1},
|
||||
route: []*PathV2{pathC1A1},
|
||||
fromIncluded: map[uint64]bool{1: false, 2: false},
|
||||
fromExcluded: map[uint64]bool{},
|
||||
expectedResult: false,
|
||||
},
|
||||
{
|
||||
name: "Route with no fromIncluded or fromExcluded",
|
||||
route: []*PathV2{path1, path2},
|
||||
route: []*PathV2{pathC1A1, pathC2A2},
|
||||
fromIncluded: map[uint64]bool{},
|
||||
fromExcluded: map[uint64]bool{},
|
||||
expectedResult: true,
|
||||
|
@ -289,7 +298,7 @@ func TestHasSufficientCapacityV2(t *testing.T) {
|
|||
}{
|
||||
{
|
||||
name: "All paths meet required amount",
|
||||
route: []*PathV2{path1, path2, path3},
|
||||
route: []*PathV2{pathC1A1, pathC2A2, pathC3A3},
|
||||
amountIn: big.NewInt(600),
|
||||
fromLockedAmount: map[uint64]*hexutil.Big{1: &amount1, 2: &amount2, 3: &amount3},
|
||||
expected: true,
|
||||
|
@ -299,7 +308,7 @@ func TestHasSufficientCapacityV2(t *testing.T) {
|
|||
/*
|
||||
{
|
||||
name: "A path does not meet required amount",
|
||||
route: []*PathV2{path1, path2, path3},
|
||||
route: []*PathV2{pathC1A1, pathC2A2, pathC3A3},
|
||||
amountIn: big.NewInt(600),
|
||||
fromLockedAmount: map[uint64]*hexutil.Big{1: &amount1, 2: &amount2, 4: &amount4},
|
||||
expected: false,
|
||||
|
@ -307,35 +316,35 @@ func TestHasSufficientCapacityV2(t *testing.T) {
|
|||
*/
|
||||
{
|
||||
name: "No fromLockedAmount",
|
||||
route: []*PathV2{path1, path2, path3},
|
||||
route: []*PathV2{pathC1A1, pathC2A2, pathC3A3},
|
||||
amountIn: big.NewInt(600),
|
||||
fromLockedAmount: map[uint64]*hexutil.Big{},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "Single path meets required amount",
|
||||
route: []*PathV2{path1},
|
||||
route: []*PathV2{pathC1A1},
|
||||
amountIn: big.NewInt(100),
|
||||
fromLockedAmount: map[uint64]*hexutil.Big{1: &amount1},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "Single path does not meet required amount",
|
||||
route: []*PathV2{path1},
|
||||
route: []*PathV2{pathC1A1},
|
||||
amountIn: big.NewInt(200),
|
||||
fromLockedAmount: map[uint64]*hexutil.Big{1: &amount1},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "Path meets required amount with excess",
|
||||
route: []*PathV2{path1, path2},
|
||||
route: []*PathV2{pathC1A1, pathC2A2},
|
||||
amountIn: big.NewInt(250),
|
||||
fromLockedAmount: map[uint64]*hexutil.Big{1: &amount1, 2: &amount2},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "Path does not meet required amount due to insufficient rest",
|
||||
route: []*PathV2{path1, path2, path4},
|
||||
route: []*PathV2{pathC1A1, pathC2A2, pathC4A4},
|
||||
amountIn: big.NewInt(800),
|
||||
fromLockedAmount: map[uint64]*hexutil.Big{1: &amount1, 4: &amount4},
|
||||
expected: false,
|
||||
|
@ -596,40 +605,40 @@ func TestFilterNetworkComplianceV2(t *testing.T) {
|
|||
{
|
||||
name: "Routes without excluded chain IDs, missing included path",
|
||||
routes: [][]*PathV2{
|
||||
{path1, path2},
|
||||
{path2, path3},
|
||||
{pathC1A1, pathC2A2},
|
||||
{pathC2A2, pathC3A3},
|
||||
},
|
||||
fromLockedAmount: map[uint64]*hexutil.Big{1: &amount1, 2: &amount2},
|
||||
expected: [][]*PathV2{
|
||||
{path1, path2},
|
||||
{pathC1A1, pathC2A2},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Routes with an excluded chain ID",
|
||||
routes: [][]*PathV2{
|
||||
{path1, path2},
|
||||
{path2, path3, path0},
|
||||
{pathC1A1, pathC2A2},
|
||||
{pathC2A2, pathC3A3, path0},
|
||||
},
|
||||
fromLockedAmount: map[uint64]*hexutil.Big{1: &amount1, 2: &amount2, 4: &amount0},
|
||||
expected: [][]*PathV2{
|
||||
{path1, path2},
|
||||
{pathC1A1, pathC2A2},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Routes with all included chain IDs",
|
||||
routes: [][]*PathV2{
|
||||
{path1, path2, path3},
|
||||
{pathC1A1, pathC2A2, pathC3A3},
|
||||
},
|
||||
fromLockedAmount: map[uint64]*hexutil.Big{1: &amount1, 2: &amount2, 3: &amount3},
|
||||
expected: [][]*PathV2{
|
||||
{path1, path2, path3},
|
||||
{pathC1A1, pathC2A2, pathC3A3},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Routes missing one included chain ID",
|
||||
routes: [][]*PathV2{
|
||||
{path1, path2},
|
||||
{path1},
|
||||
{pathC1A1, pathC2A2},
|
||||
{pathC1A1},
|
||||
},
|
||||
fromLockedAmount: map[uint64]*hexutil.Big{1: &amount1, 2: &amount2, 3: &amount3},
|
||||
expected: [][]*PathV2{},
|
||||
|
@ -637,32 +646,32 @@ func TestFilterNetworkComplianceV2(t *testing.T) {
|
|||
{
|
||||
name: "Routes with no fromLockedAmount",
|
||||
routes: [][]*PathV2{
|
||||
{path1, path2},
|
||||
{path2, path3},
|
||||
{pathC1A1, pathC2A2},
|
||||
{pathC2A2, pathC3A3},
|
||||
},
|
||||
fromLockedAmount: map[uint64]*hexutil.Big{},
|
||||
expected: [][]*PathV2{
|
||||
{path1, path2},
|
||||
{path2, path3},
|
||||
{pathC1A1, pathC2A2},
|
||||
{pathC2A2, pathC3A3},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Routes with fromExcluded only",
|
||||
routes: [][]*PathV2{
|
||||
{path1, path2},
|
||||
{path2, path3},
|
||||
{pathC1A1, pathC2A2},
|
||||
{pathC2A2, pathC3A3},
|
||||
},
|
||||
fromLockedAmount: map[uint64]*hexutil.Big{4: &amount0},
|
||||
expected: [][]*PathV2{
|
||||
{path1, path2},
|
||||
{path2, path3},
|
||||
{pathC1A1, pathC2A2},
|
||||
{pathC2A2, pathC3A3},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Routes with all excluded chain IDs",
|
||||
routes: [][]*PathV2{
|
||||
{path0, path1},
|
||||
{path0, path2},
|
||||
{path0, pathC1A1},
|
||||
{path0, pathC2A2},
|
||||
},
|
||||
fromLockedAmount: map[uint64]*hexutil.Big{1: &amount1, 2: &amount2, 3: &amount3, 4: &amount0},
|
||||
expected: [][]*PathV2{},
|
||||
|
@ -691,27 +700,32 @@ func TestFilterCapacityValidationV2(t *testing.T) {
|
|||
name: "Sufficient capacity with multiple paths",
|
||||
routes: [][]*PathV2{
|
||||
{
|
||||
{FromChain: network1, AmountIn: (*hexutil.Big)(big.NewInt(100))},
|
||||
{FromChain: network2, AmountIn: (*hexutil.Big)(big.NewInt(200))},
|
||||
{FromChain: network1, AmountIn: (*hexutil.Big)(big.NewInt(50))},
|
||||
{FromChain: network2, AmountIn: (*hexutil.Big)(big.NewInt(100))},
|
||||
{FromChain: network3, AmountIn: (*hexutil.Big)(big.NewInt(100))},
|
||||
},
|
||||
{
|
||||
{FromChain: network1, AmountIn: (*hexutil.Big)(big.NewInt(50))},
|
||||
{FromChain: network2, AmountIn: (*hexutil.Big)(big.NewInt(100))},
|
||||
{FromChain: network2, AmountIn: (*hexutil.Big)(big.NewInt(200))},
|
||||
},
|
||||
{
|
||||
{FromChain: network1, AmountIn: (*hexutil.Big)(big.NewInt(100))},
|
||||
{FromChain: network2, AmountIn: (*hexutil.Big)(big.NewInt(200))},
|
||||
},
|
||||
},
|
||||
amountIn: big.NewInt(150),
|
||||
amountIn: big.NewInt(250),
|
||||
fromLockedAmount: map[uint64]*hexutil.Big{
|
||||
1: (*hexutil.Big)(big.NewInt(50)),
|
||||
2: (*hexutil.Big)(big.NewInt(100)),
|
||||
},
|
||||
expectedRoutes: [][]*PathV2{
|
||||
{
|
||||
{FromChain: network1, AmountIn: (*hexutil.Big)(big.NewInt(50)), AmountInLocked: true},
|
||||
{FromChain: network2, AmountIn: (*hexutil.Big)(big.NewInt(100)), AmountInLocked: true},
|
||||
{FromChain: network1, AmountIn: (*hexutil.Big)(big.NewInt(50))},
|
||||
{FromChain: network2, AmountIn: (*hexutil.Big)(big.NewInt(100))},
|
||||
{FromChain: network3, AmountIn: (*hexutil.Big)(big.NewInt(100))},
|
||||
},
|
||||
{
|
||||
{FromChain: network1, AmountIn: (*hexutil.Big)(big.NewInt(50)), AmountInLocked: true},
|
||||
{FromChain: network2, AmountIn: (*hexutil.Big)(big.NewInt(100)), AmountInLocked: true},
|
||||
{FromChain: network1, AmountIn: (*hexutil.Big)(big.NewInt(50))},
|
||||
{FromChain: network2, AmountIn: (*hexutil.Big)(big.NewInt(200))},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -745,8 +759,8 @@ func TestFilterCapacityValidationV2(t *testing.T) {
|
|||
},
|
||||
expectedRoutes: [][]*PathV2{
|
||||
{
|
||||
{FromChain: network1, AmountIn: (*hexutil.Big)(big.NewInt(100)), AmountInLocked: true},
|
||||
{FromChain: network2, AmountIn: (*hexutil.Big)(big.NewInt(50)), AmountInLocked: true},
|
||||
{FromChain: network1, AmountIn: (*hexutil.Big)(big.NewInt(100))},
|
||||
{FromChain: network2, AmountIn: (*hexutil.Big)(big.NewInt(50))},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -762,17 +776,35 @@ func TestFilterCapacityValidationV2(t *testing.T) {
|
|||
fromLockedAmount: map[uint64]*hexutil.Big{},
|
||||
expectedRoutes: [][]*PathV2{
|
||||
{
|
||||
{FromChain: network1, AmountIn: (*hexutil.Big)(big.NewInt(100)), AmountInLocked: false},
|
||||
{FromChain: network2, AmountIn: (*hexutil.Big)(big.NewInt(50)), AmountInLocked: false},
|
||||
{FromChain: network1, AmountIn: (*hexutil.Big)(big.NewInt(100))},
|
||||
{FromChain: network2, AmountIn: (*hexutil.Big)(big.NewInt(50))},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
// TODO Is the behaviour of this test correct? It looks wrong
|
||||
name: "Single route with sufficient capacity",
|
||||
routes: [][]*PathV2{
|
||||
{
|
||||
{FromChain: network1, AmountIn: (*hexutil.Big)(big.NewInt(200))},
|
||||
{FromChain: network1, AmountIn: (*hexutil.Big)(big.NewInt(50))},
|
||||
{FromChain: network2, AmountIn: (*hexutil.Big)(big.NewInt(100))},
|
||||
},
|
||||
},
|
||||
amountIn: big.NewInt(150),
|
||||
fromLockedAmount: map[uint64]*hexutil.Big{
|
||||
1: (*hexutil.Big)(big.NewInt(50)),
|
||||
},
|
||||
expectedRoutes: [][]*PathV2{
|
||||
{
|
||||
{FromChain: network1, AmountIn: (*hexutil.Big)(big.NewInt(50))},
|
||||
{FromChain: network2, AmountIn: (*hexutil.Big)(big.NewInt(100))},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Single route with inappropriately locked amount",
|
||||
routes: [][]*PathV2{
|
||||
{
|
||||
{FromChain: network1, AmountIn: (*hexutil.Big)(big.NewInt(100))},
|
||||
},
|
||||
},
|
||||
amountIn: big.NewInt(150),
|
||||
|
@ -785,7 +817,7 @@ func TestFilterCapacityValidationV2(t *testing.T) {
|
|||
name: "Single route with insufficient capacity",
|
||||
routes: [][]*PathV2{
|
||||
{
|
||||
{FromChain: network1, AmountIn: (*hexutil.Big)(big.NewInt(100))},
|
||||
{FromChain: network1, AmountIn: (*hexutil.Big)(big.NewInt(50))},
|
||||
},
|
||||
},
|
||||
amountIn: big.NewInt(150),
|
||||
|
@ -803,28 +835,13 @@ func TestFilterCapacityValidationV2(t *testing.T) {
|
|||
},
|
||||
expectedRoutes: [][]*PathV2{},
|
||||
},
|
||||
{
|
||||
// TODO this seems wrong also. Should this test case work?
|
||||
name: "Routes with duplicate chain IDs",
|
||||
routes: [][]*PathV2{
|
||||
{
|
||||
{FromChain: network1, AmountIn: (*hexutil.Big)(big.NewInt(100))},
|
||||
{FromChain: network1, AmountIn: (*hexutil.Big)(big.NewInt(100))},
|
||||
},
|
||||
},
|
||||
amountIn: big.NewInt(150),
|
||||
fromLockedAmount: map[uint64]*hexutil.Big{
|
||||
1: (*hexutil.Big)(big.NewInt(50)),
|
||||
},
|
||||
expectedRoutes: [][]*PathV2{},
|
||||
},
|
||||
{
|
||||
name: "Partial locked amounts",
|
||||
routes: [][]*PathV2{
|
||||
{
|
||||
{FromChain: network1, AmountIn: (*hexutil.Big)(big.NewInt(100))},
|
||||
{FromChain: network2, AmountIn: (*hexutil.Big)(big.NewInt(100))},
|
||||
{FromChain: network3, AmountIn: (*hexutil.Big)(big.NewInt(200))},
|
||||
{FromChain: network1, AmountIn: (*hexutil.Big)(big.NewInt(50))},
|
||||
{FromChain: network3, AmountIn: (*hexutil.Big)(big.NewInt(100))},
|
||||
{FromChain: network4, AmountIn: (*hexutil.Big)(big.NewInt(100))},
|
||||
},
|
||||
},
|
||||
amountIn: big.NewInt(250),
|
||||
|
@ -833,7 +850,13 @@ func TestFilterCapacityValidationV2(t *testing.T) {
|
|||
2: (*hexutil.Big)(big.NewInt(0)), // Excluded path
|
||||
3: (*hexutil.Big)(big.NewInt(100)),
|
||||
},
|
||||
expectedRoutes: [][]*PathV2{},
|
||||
expectedRoutes: [][]*PathV2{
|
||||
{
|
||||
{FromChain: network1, AmountIn: (*hexutil.Big)(big.NewInt(50))},
|
||||
{FromChain: network3, AmountIn: (*hexutil.Big)(big.NewInt(100))},
|
||||
{FromChain: network4, AmountIn: (*hexutil.Big)(big.NewInt(100))},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Mixed networks with sufficient capacity",
|
||||
|
@ -843,15 +866,15 @@ func TestFilterCapacityValidationV2(t *testing.T) {
|
|||
{FromChain: network3, AmountIn: (*hexutil.Big)(big.NewInt(200))},
|
||||
},
|
||||
},
|
||||
amountIn: big.NewInt(250),
|
||||
amountIn: big.NewInt(300),
|
||||
fromLockedAmount: map[uint64]*hexutil.Big{
|
||||
1: (*hexutil.Big)(big.NewInt(100)),
|
||||
3: (*hexutil.Big)(big.NewInt(200)),
|
||||
},
|
||||
expectedRoutes: [][]*PathV2{
|
||||
{
|
||||
{FromChain: network1, AmountIn: (*hexutil.Big)(big.NewInt(100)), AmountInLocked: true},
|
||||
{FromChain: network3, AmountIn: (*hexutil.Big)(big.NewInt(200)), AmountInLocked: true},
|
||||
{FromChain: network1, AmountIn: (*hexutil.Big)(big.NewInt(100))},
|
||||
{FromChain: network3, AmountIn: (*hexutil.Big)(big.NewInt(200))},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -891,23 +914,46 @@ func TestFilterRoutesV2(t *testing.T) {
|
|||
expectedRoutes [][]*PathV2
|
||||
}{
|
||||
{
|
||||
name: "Empty fromLockedAmount",
|
||||
name: "Empty fromLockedAmount and routes don't match amountIn",
|
||||
routes: [][]*PathV2{
|
||||
{path1, path2},
|
||||
{path3, path4},
|
||||
{pathC1A1, pathC2A2},
|
||||
{pathC3A3, pathC4A4},
|
||||
},
|
||||
amountIn: big.NewInt(150),
|
||||
fromLockedAmount: map[uint64]*hexutil.Big{},
|
||||
expectedRoutes: [][]*PathV2{},
|
||||
},
|
||||
{
|
||||
name: "Empty fromLockedAmount and sigle route match amountIn",
|
||||
routes: [][]*PathV2{
|
||||
{pathC1A1, pathC2A2},
|
||||
{pathC3A3, pathC4A4},
|
||||
},
|
||||
amountIn: big.NewInt(300),
|
||||
fromLockedAmount: map[uint64]*hexutil.Big{},
|
||||
expectedRoutes: [][]*PathV2{
|
||||
{path1, path2},
|
||||
{path3, path4},
|
||||
{pathC1A1, pathC2A2},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Empty fromLockedAmount and more routes match amountIn",
|
||||
routes: [][]*PathV2{
|
||||
{pathC1A1, pathC2A2},
|
||||
{pathC3A3, pathC4A4},
|
||||
{pathC1A1, pathC2A1, pathC3A1},
|
||||
},
|
||||
amountIn: big.NewInt(300),
|
||||
fromLockedAmount: map[uint64]*hexutil.Big{},
|
||||
expectedRoutes: [][]*PathV2{
|
||||
{pathC1A1, pathC2A2},
|
||||
{pathC1A1, pathC2A1, pathC3A1},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "All paths appear in fromLockedAmount but not within a single route",
|
||||
routes: [][]*PathV2{
|
||||
{path1, path3},
|
||||
{path2, path4},
|
||||
{pathC1A1, pathC3A3},
|
||||
{pathC2A2, pathC4A4},
|
||||
},
|
||||
amountIn: big.NewInt(500),
|
||||
fromLockedAmount: map[uint64]*hexutil.Big{
|
||||
|
@ -919,11 +965,12 @@ func TestFilterRoutesV2(t *testing.T) {
|
|||
expectedRoutes: [][]*PathV2{},
|
||||
},
|
||||
{
|
||||
name: "Mixed valid and invalid routes",
|
||||
name: "Mixed valid and invalid routes I",
|
||||
routes: [][]*PathV2{
|
||||
{path1, path2},
|
||||
{path2, path3},
|
||||
{path1, path4},
|
||||
{pathC1A1, pathC2A2},
|
||||
{pathC2A2, pathC3A3},
|
||||
{pathC1A1, pathC4A4},
|
||||
{pathC1A1, pathC2A1, pathC3A1},
|
||||
},
|
||||
amountIn: big.NewInt(300),
|
||||
fromLockedAmount: map[uint64]*hexutil.Big{
|
||||
|
@ -931,14 +978,31 @@ func TestFilterRoutesV2(t *testing.T) {
|
|||
2: &amount2,
|
||||
},
|
||||
expectedRoutes: [][]*PathV2{
|
||||
{path1, path2},
|
||||
{pathC1A1, pathC2A2},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Mixed valid and invalid routes II",
|
||||
routes: [][]*PathV2{
|
||||
{pathC1A1, pathC2A2},
|
||||
{pathC2A2, pathC3A3},
|
||||
{pathC1A1, pathC4A4},
|
||||
{pathC1A1, pathC2A1, pathC3A1},
|
||||
},
|
||||
amountIn: big.NewInt(300),
|
||||
fromLockedAmount: map[uint64]*hexutil.Big{
|
||||
1: &amount1,
|
||||
},
|
||||
expectedRoutes: [][]*PathV2{
|
||||
{pathC1A1, pathC2A2},
|
||||
{pathC1A1, pathC2A1, pathC3A1},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "All invalid routes",
|
||||
routes: [][]*PathV2{
|
||||
{path2, path3},
|
||||
{path4, path5},
|
||||
{pathC2A2, pathC3A3},
|
||||
{pathC4A4, pathC5A5},
|
||||
},
|
||||
amountIn: big.NewInt(300),
|
||||
fromLockedAmount: map[uint64]*hexutil.Big{
|
||||
|
@ -949,22 +1013,22 @@ func TestFilterRoutesV2(t *testing.T) {
|
|||
{
|
||||
name: "Single valid route",
|
||||
routes: [][]*PathV2{
|
||||
{path1, path3},
|
||||
{path2, path3},
|
||||
{pathC1A1, pathC3A3},
|
||||
{pathC2A2, pathC3A3},
|
||||
},
|
||||
amountIn: big.NewInt(150),
|
||||
amountIn: big.NewInt(400),
|
||||
fromLockedAmount: map[uint64]*hexutil.Big{
|
||||
1: &amount1,
|
||||
3: &amount3,
|
||||
},
|
||||
expectedRoutes: [][]*PathV2{
|
||||
{path1, path3},
|
||||
{pathC1A1, pathC3A3},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Route with mixed valid and invalid paths",
|
||||
name: "Route with mixed valid and invalid paths I",
|
||||
routes: [][]*PathV2{
|
||||
{path1, path2, path3},
|
||||
{pathC1A1, pathC2A2, pathC3A3},
|
||||
},
|
||||
amountIn: big.NewInt(300),
|
||||
fromLockedAmount: map[uint64]*hexutil.Big{
|
||||
|
@ -973,6 +1037,36 @@ func TestFilterRoutesV2(t *testing.T) {
|
|||
},
|
||||
expectedRoutes: [][]*PathV2{},
|
||||
},
|
||||
{
|
||||
name: "Route with mixed valid and invalid paths II",
|
||||
routes: [][]*PathV2{
|
||||
{pathC1A1, pathC3A3},
|
||||
},
|
||||
amountIn: big.NewInt(400),
|
||||
fromLockedAmount: map[uint64]*hexutil.Big{
|
||||
1: &amount1,
|
||||
2: &amount0, // This path should be filtered out due to being excluded via a zero amount, 0 value locked means this chain is disabled
|
||||
},
|
||||
expectedRoutes: [][]*PathV2{
|
||||
{pathC1A1, pathC3A3},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Route with mixed valid and invalid paths III",
|
||||
routes: [][]*PathV2{
|
||||
{pathC1A1, pathC3A3},
|
||||
{pathC1A1, pathC3A2, pathC4A1},
|
||||
},
|
||||
amountIn: big.NewInt(400),
|
||||
fromLockedAmount: map[uint64]*hexutil.Big{
|
||||
1: &amount1,
|
||||
2: &amount0, // This path should be filtered out due to being excluded via a zero amount, 0 value locked means this chain is disabled
|
||||
},
|
||||
expectedRoutes: [][]*PathV2{
|
||||
{pathC1A1, pathC3A3},
|
||||
{pathC1A1, pathC3A2, pathC4A1},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
|
|
|
@ -305,9 +305,9 @@ func (h *HopBridgeProcessor) GetContractAddress(params ProcessorInputParams) (co
|
|||
}
|
||||
|
||||
func (h *HopBridgeProcessor) sendOrBuild(sendArgs *MultipathProcessorTxArgs, signerFn bind.SignerFn) (tx *ethTypes.Transaction, err error) {
|
||||
fromChain := h.networkManager.Find(sendArgs.ChainID)
|
||||
fromChain := h.networkManager.Find(sendArgs.HopTx.ChainID)
|
||||
if fromChain == nil {
|
||||
return tx, fmt.Errorf("ChainID not supported %d", sendArgs.ChainID)
|
||||
return tx, fmt.Errorf("ChainID not supported %d", sendArgs.HopTx.ChainID)
|
||||
}
|
||||
|
||||
token := h.tokenManager.FindToken(fromChain, sendArgs.HopTx.Symbol)
|
||||
|
@ -344,22 +344,22 @@ func (h *HopBridgeProcessor) sendOrBuild(sendArgs *MultipathProcessorTxArgs, sig
|
|||
|
||||
switch contractType {
|
||||
case hop.CctpL1Bridge:
|
||||
tx, err = h.sendCctpL1BridgeTx(contractAddress, ethClient, sendArgs.HopTx.ChainID, sendArgs.HopTx.Recipient, txOpts, bonderFee)
|
||||
tx, err = h.sendCctpL1BridgeTx(contractAddress, ethClient, sendArgs.HopTx.ChainIDTo, sendArgs.HopTx.Recipient, txOpts, bonderFee)
|
||||
case hop.L1Bridge:
|
||||
tx, err = h.sendL1BridgeTx(contractAddress, ethClient, sendArgs.HopTx.ChainID, sendArgs.HopTx.Recipient, txOpts, token, bonderFee)
|
||||
tx, err = h.sendL1BridgeTx(contractAddress, ethClient, sendArgs.HopTx.ChainIDTo, sendArgs.HopTx.Recipient, txOpts, token, bonderFee)
|
||||
case hop.L2AmmWrapper:
|
||||
tx, err = h.sendL2AmmWrapperTx(contractAddress, ethClient, sendArgs.HopTx.ChainID, sendArgs.HopTx.Recipient, txOpts, bonderFee)
|
||||
tx, err = h.sendL2AmmWrapperTx(contractAddress, ethClient, sendArgs.HopTx.ChainIDTo, sendArgs.HopTx.Recipient, txOpts, bonderFee)
|
||||
case hop.CctpL2Bridge:
|
||||
tx, err = h.sendCctpL2BridgeTx(contractAddress, ethClient, sendArgs.HopTx.ChainID, sendArgs.HopTx.Recipient, txOpts, bonderFee)
|
||||
tx, err = h.sendCctpL2BridgeTx(contractAddress, ethClient, sendArgs.HopTx.ChainIDTo, sendArgs.HopTx.Recipient, txOpts, bonderFee)
|
||||
case hop.L2Bridge:
|
||||
tx, err = h.sendL2BridgeTx(contractAddress, ethClient, sendArgs.HopTx.ChainID, sendArgs.HopTx.Recipient, txOpts, bonderFee)
|
||||
tx, err = h.sendL2BridgeTx(contractAddress, ethClient, sendArgs.HopTx.ChainIDTo, sendArgs.HopTx.Recipient, txOpts, bonderFee)
|
||||
default:
|
||||
return tx, ErrContractTypeNotSupported
|
||||
}
|
||||
if err != nil {
|
||||
return tx, createBridgeHopErrorResponse(err)
|
||||
}
|
||||
err = h.transactor.StoreAndTrackPendingTx(txOpts.From, sendArgs.HopTx.Symbol, sendArgs.ChainID, sendArgs.HopTx.MultiTransactionID, tx)
|
||||
err = h.transactor.StoreAndTrackPendingTx(txOpts.From, sendArgs.HopTx.Symbol, sendArgs.HopTx.ChainID, sendArgs.HopTx.MultiTransactionID, tx)
|
||||
if err != nil {
|
||||
return tx, createBridgeHopErrorResponse(err)
|
||||
}
|
||||
|
@ -367,7 +367,7 @@ func (h *HopBridgeProcessor) sendOrBuild(sendArgs *MultipathProcessorTxArgs, sig
|
|||
}
|
||||
|
||||
func (h *HopBridgeProcessor) Send(sendArgs *MultipathProcessorTxArgs, verifiedAccount *account.SelectedExtKey) (hash types.Hash, err error) {
|
||||
tx, err := h.sendOrBuild(sendArgs, getSigner(sendArgs.ChainID, sendArgs.HopTx.From, verifiedAccount))
|
||||
tx, err := h.sendOrBuild(sendArgs, getSigner(sendArgs.HopTx.ChainID, sendArgs.HopTx.From, verifiedAccount))
|
||||
if err != nil {
|
||||
return types.Hash{}, createBridgeHopErrorResponse(err)
|
||||
}
|
||||
|
@ -450,7 +450,7 @@ func (h *HopBridgeProcessor) CalculateAmountOut(params ProcessorInputParams) (*b
|
|||
return nil, ErrNoBonderFeeFound
|
||||
}
|
||||
bonderFee := bonderFeeIns.(*BonderFee)
|
||||
return bonderFee.EstimatedRecieved.Int, nil
|
||||
return bonderFee.AmountOutMin.Int, nil
|
||||
}
|
||||
|
||||
func (h *HopBridgeProcessor) packCctpL1BridgeTx(abi abi.ABI, toChainID uint64, to common.Address, bonderFee *BonderFee) ([]byte, error) {
|
||||
|
|
|
@ -81,8 +81,9 @@ type routerTestParams struct {
|
|||
}
|
||||
|
||||
type amountOption struct {
|
||||
amount *big.Int
|
||||
locked bool
|
||||
amount *big.Int
|
||||
locked bool
|
||||
subtractFees bool
|
||||
}
|
||||
|
||||
func makeBalanceKey(chainID uint64, symbol string) string {
|
||||
|
@ -99,27 +100,35 @@ type PathV2 struct {
|
|||
AmountInLocked bool // Is the amount locked
|
||||
AmountOut *hexutil.Big // Amount that will be received on the destination chain
|
||||
|
||||
SuggestedLevelsForMaxFeesPerGas *MaxFeesLevels // Suggested max fees for the transaction
|
||||
SuggestedLevelsForMaxFeesPerGas *MaxFeesLevels // Suggested max fees for the transaction (in ETH WEI)
|
||||
MaxFeesPerGas *hexutil.Big // Max fees per gas (determined by client via GasFeeMode, in ETH WEI)
|
||||
|
||||
TxBaseFee *hexutil.Big // Base fee for the transaction
|
||||
TxPriorityFee *hexutil.Big // Priority fee for the transaction
|
||||
TxBaseFee *hexutil.Big // Base fee for the transaction (in ETH WEI)
|
||||
TxPriorityFee *hexutil.Big // Priority fee for the transaction (in ETH WEI)
|
||||
TxGasAmount uint64 // Gas used for the transaction
|
||||
TxBonderFees *hexutil.Big // Bonder fees for the transaction - used for Hop bridge
|
||||
TxTokenFees *hexutil.Big // Token fees for the transaction - used for bridges (represent the difference between the amount in and the amount out)
|
||||
TxL1Fee *hexutil.Big // L1 fee for the transaction - used for for transactions placed on L2 chains
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
ApprovalRequired bool // Is approval required for the transaction
|
||||
ApprovalAmountRequired *hexutil.Big // Amount required for the approval transaction
|
||||
ApprovalContractAddress *common.Address // Address of the contract that needs to be approved
|
||||
ApprovalBaseFee *hexutil.Big // Base fee for the approval transaction
|
||||
ApprovalPriorityFee *hexutil.Big // Priority fee for the approval transaction
|
||||
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
|
||||
ApprovalL1Fee *hexutil.Big // L1 fee for the approval transaction - used for for transactions placed on L2 chains
|
||||
|
||||
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)
|
||||
|
||||
TxTotalFee *hexutil.Big // Total fee for the transaction (includes tx fees, approval fees, l1 fees, l1 approval fees, in ETH WEI)
|
||||
|
||||
EstimatedTime TransactionEstimation
|
||||
|
||||
requiredTokenBalance *big.Int
|
||||
requiredNativeBalance *big.Int
|
||||
requiredTokenBalance *big.Int // (in selected token)
|
||||
requiredNativeBalance *big.Int // (in ETH WEI)
|
||||
subtractFees bool
|
||||
}
|
||||
|
||||
type ProcessorError struct {
|
||||
|
@ -249,7 +258,7 @@ func (n NodeV2) buildAllRoutesV2() [][]*PathV2 {
|
|||
return res
|
||||
}
|
||||
|
||||
func findBestV2(routes [][]*PathV2, tokenPrice float64, nativeChainTokenPrice float64) []*PathV2 {
|
||||
func findBestV2(routes [][]*PathV2, tokenPrice float64, nativeTokenPrice float64) []*PathV2 {
|
||||
var best []*PathV2
|
||||
bestCost := big.NewFloat(math.Inf(1))
|
||||
for _, route := range routes {
|
||||
|
@ -257,69 +266,39 @@ func findBestV2(routes [][]*PathV2, tokenPrice float64, nativeChainTokenPrice fl
|
|||
for _, path := range route {
|
||||
tokenDenominator := big.NewFloat(math.Pow(10, float64(path.FromToken.Decimals)))
|
||||
|
||||
path.requiredTokenBalance = big.NewInt(0)
|
||||
path.requiredNativeBalance = big.NewInt(0)
|
||||
if path.FromToken.IsNative() {
|
||||
path.requiredNativeBalance.Add(path.requiredNativeBalance, path.AmountIn.ToInt())
|
||||
} else {
|
||||
path.requiredTokenBalance.Add(path.requiredTokenBalance, path.AmountIn.ToInt())
|
||||
// calculate the cost of the path
|
||||
nativeTokenPrice := new(big.Float).SetFloat64(nativeTokenPrice)
|
||||
|
||||
// tx fee
|
||||
txFeeInEth := gweiToEth(weiToGwei(path.TxFee.ToInt()))
|
||||
pathCost := new(big.Float).Mul(txFeeInEth, nativeTokenPrice)
|
||||
|
||||
if path.TxL1Fee.ToInt().Cmp(pathprocessor.ZeroBigIntValue) > 0 {
|
||||
txL1FeeInEth := gweiToEth(weiToGwei(path.TxL1Fee.ToInt()))
|
||||
pathCost.Add(pathCost, new(big.Float).Mul(txL1FeeInEth, nativeTokenPrice))
|
||||
}
|
||||
|
||||
// ecaluate the cost of the path
|
||||
pathCost := big.NewFloat(0)
|
||||
nativeTokenPrice := new(big.Float).SetFloat64(nativeChainTokenPrice)
|
||||
if path.TxBaseFee != nil && path.TxPriorityFee != nil {
|
||||
feePerGas := new(big.Int).Add(path.TxBaseFee.ToInt(), path.TxPriorityFee.ToInt())
|
||||
txFeeInWei := new(big.Int).Mul(feePerGas, big.NewInt(int64(path.TxGasAmount)))
|
||||
txFeeInEth := gweiToEth(weiToGwei(txFeeInWei))
|
||||
|
||||
path.requiredNativeBalance.Add(path.requiredNativeBalance, txFeeInWei)
|
||||
pathCost = new(big.Float).Mul(txFeeInEth, nativeTokenPrice)
|
||||
}
|
||||
if path.TxBonderFees != nil && path.TxBonderFees.ToInt().Cmp(pathprocessor.ZeroBigIntValue) > 0 {
|
||||
if path.FromToken.IsNative() {
|
||||
path.requiredNativeBalance.Add(path.requiredNativeBalance, path.TxBonderFees.ToInt())
|
||||
} else {
|
||||
path.requiredTokenBalance.Add(path.requiredTokenBalance, path.TxBonderFees.ToInt())
|
||||
}
|
||||
pathCost.Add(pathCost, new(big.Float).Mul(
|
||||
new(big.Float).Quo(new(big.Float).SetInt(path.TxBonderFees.ToInt()), tokenDenominator),
|
||||
new(big.Float).SetFloat64(tokenPrice)))
|
||||
|
||||
}
|
||||
if path.TxL1Fee != nil && path.TxL1Fee.ToInt().Cmp(pathprocessor.ZeroBigIntValue) > 0 {
|
||||
l1FeeInWei := path.TxL1Fee.ToInt()
|
||||
l1FeeInEth := gweiToEth(weiToGwei(l1FeeInWei))
|
||||
|
||||
path.requiredNativeBalance.Add(path.requiredNativeBalance, l1FeeInWei)
|
||||
pathCost.Add(pathCost, new(big.Float).Mul(l1FeeInEth, nativeTokenPrice))
|
||||
}
|
||||
if path.TxTokenFees != nil && path.TxTokenFees.ToInt().Cmp(pathprocessor.ZeroBigIntValue) > 0 && path.FromToken != nil {
|
||||
if path.FromToken.IsNative() {
|
||||
path.requiredNativeBalance.Add(path.requiredNativeBalance, path.TxTokenFees.ToInt())
|
||||
} else {
|
||||
path.requiredTokenBalance.Add(path.requiredTokenBalance, path.TxTokenFees.ToInt())
|
||||
}
|
||||
pathCost.Add(pathCost, new(big.Float).Mul(
|
||||
new(big.Float).Quo(new(big.Float).SetInt(path.TxTokenFees.ToInt()), tokenDenominator),
|
||||
new(big.Float).SetFloat64(tokenPrice)))
|
||||
}
|
||||
|
||||
if path.ApprovalRequired {
|
||||
if path.ApprovalBaseFee != nil && path.ApprovalPriorityFee != nil {
|
||||
feePerGas := new(big.Int).Add(path.ApprovalBaseFee.ToInt(), path.ApprovalPriorityFee.ToInt())
|
||||
txFeeInWei := new(big.Int).Mul(feePerGas, big.NewInt(int64(path.ApprovalGasAmount)))
|
||||
txFeeInEth := gweiToEth(weiToGwei(txFeeInWei))
|
||||
// tx approval fee
|
||||
approvalFeeInEth := gweiToEth(weiToGwei(path.ApprovalFee.ToInt()))
|
||||
pathCost.Add(pathCost, new(big.Float).Mul(approvalFeeInEth, nativeTokenPrice))
|
||||
|
||||
path.requiredNativeBalance.Add(path.requiredNativeBalance, txFeeInWei)
|
||||
pathCost.Add(pathCost, new(big.Float).Mul(txFeeInEth, nativeTokenPrice))
|
||||
}
|
||||
|
||||
if path.ApprovalL1Fee != nil {
|
||||
l1FeeInWei := path.ApprovalL1Fee.ToInt()
|
||||
l1FeeInEth := gweiToEth(weiToGwei(l1FeeInWei))
|
||||
|
||||
path.requiredNativeBalance.Add(path.requiredNativeBalance, l1FeeInWei)
|
||||
pathCost.Add(pathCost, new(big.Float).Mul(l1FeeInEth, nativeTokenPrice))
|
||||
if path.ApprovalL1Fee.ToInt().Cmp(pathprocessor.ZeroBigIntValue) > 0 {
|
||||
approvalL1FeeInEth := gweiToEth(weiToGwei(path.ApprovalL1Fee.ToInt()))
|
||||
pathCost.Add(pathCost, new(big.Float).Mul(approvalL1FeeInEth, nativeTokenPrice))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -408,9 +387,13 @@ func validateFromLockedAmount(input *RouteInputParams) error {
|
|||
|
||||
var suppNetworks map[uint64]bool
|
||||
if input.testnetMode {
|
||||
suppNetworks = copyMap(supportedTestNetworks)
|
||||
suppNetworks = copyMapGeneric(supportedTestNetworks, nil).(map[uint64]bool)
|
||||
} else {
|
||||
suppNetworks = copyMap(supportedNetworks)
|
||||
suppNetworks = copyMapGeneric(supportedNetworks, nil).(map[uint64]bool)
|
||||
}
|
||||
|
||||
if suppNetworks == nil {
|
||||
return ErrCannotCheckLockedAmounts
|
||||
}
|
||||
|
||||
totalLockedAmount := big.NewInt(0)
|
||||
|
@ -610,8 +593,9 @@ func (r *Router) getOptionsForAmoutToSplitAccrossChainsForProcessingChain(input
|
|||
if tokenBalance.Cmp(pathprocessor.ZeroBigIntValue) > 0 {
|
||||
if tokenBalance.Cmp(amountToSplit) <= 0 {
|
||||
crossChainAmountOptions[chain.ChainID] = append(crossChainAmountOptions[chain.ChainID], amountOption{
|
||||
amount: tokenBalance,
|
||||
locked: false,
|
||||
amount: tokenBalance,
|
||||
locked: false,
|
||||
subtractFees: true, // for chains where we're taking the full balance, we want to subtract the fees
|
||||
})
|
||||
amountToSplit = new(big.Int).Sub(amountToSplit, tokenBalance)
|
||||
} else if amountToSplit.Cmp(pathprocessor.ZeroBigIntValue) > 0 {
|
||||
|
@ -927,6 +911,43 @@ func (r *Router) resolveCandidates(ctx context.Context, input *RouteInputParams,
|
|||
estimatedTime += 1
|
||||
}
|
||||
|
||||
// calculate ETH fees
|
||||
ethTotalFees := big.NewInt(0)
|
||||
txFeeInWei := new(big.Int).Mul(maxFeesPerGas, big.NewInt(int64(gasLimit)))
|
||||
ethTotalFees.Add(ethTotalFees, txFeeInWei)
|
||||
|
||||
txL1FeeInWei := big.NewInt(0)
|
||||
if l1FeeWei > 0 {
|
||||
txL1FeeInWei = big.NewInt(int64(l1FeeWei))
|
||||
ethTotalFees.Add(ethTotalFees, txL1FeeInWei)
|
||||
}
|
||||
|
||||
approvalFeeInWei := big.NewInt(0)
|
||||
approvalL1FeeInWei := big.NewInt(0)
|
||||
if approvalRequired {
|
||||
approvalFeeInWei.Mul(maxFeesPerGas, big.NewInt(int64(approvalGasLimit)))
|
||||
ethTotalFees.Add(ethTotalFees, approvalFeeInWei)
|
||||
|
||||
if l1ApprovalFee > 0 {
|
||||
approvalL1FeeInWei = big.NewInt(int64(l1ApprovalFee))
|
||||
ethTotalFees.Add(ethTotalFees, approvalL1FeeInWei)
|
||||
}
|
||||
}
|
||||
|
||||
// calculate required balances (bonder and token fees are already included in the amountIn by Hop bridge (once we include Celar we need to check how they handle the fees))
|
||||
requiredNativeBalance := big.NewInt(0)
|
||||
requiredTokenBalance := big.NewInt(0)
|
||||
|
||||
if token.IsNative() {
|
||||
requiredNativeBalance.Add(requiredNativeBalance, amountOption.amount)
|
||||
if !amountOption.subtractFees {
|
||||
requiredNativeBalance.Add(requiredNativeBalance, ethTotalFees)
|
||||
}
|
||||
} else {
|
||||
requiredTokenBalance.Add(requiredTokenBalance, amountOption.amount)
|
||||
requiredNativeBalance.Add(requiredNativeBalance, ethTotalFees)
|
||||
}
|
||||
|
||||
appendPathFn(&PathV2{
|
||||
ProcessorName: pProcessor.Name(),
|
||||
FromChain: network,
|
||||
|
@ -938,13 +959,16 @@ func (r *Router) resolveCandidates(ctx context.Context, input *RouteInputParams,
|
|||
AmountOut: (*hexutil.Big)(amountOut),
|
||||
|
||||
SuggestedLevelsForMaxFeesPerGas: fees.MaxFeesLevels,
|
||||
MaxFeesPerGas: (*hexutil.Big)(maxFeesPerGas),
|
||||
|
||||
TxBaseFee: (*hexutil.Big)(fees.BaseFee),
|
||||
TxPriorityFee: (*hexutil.Big)(fees.MaxPriorityFeePerGas),
|
||||
TxGasAmount: gasLimit,
|
||||
TxBonderFees: (*hexutil.Big)(bonderFees),
|
||||
TxTokenFees: (*hexutil.Big)(tokenFees),
|
||||
TxL1Fee: (*hexutil.Big)(big.NewInt(int64(l1FeeWei))),
|
||||
|
||||
TxFee: (*hexutil.Big)(txFeeInWei),
|
||||
TxL1Fee: (*hexutil.Big)(txL1FeeInWei),
|
||||
|
||||
ApprovalRequired: approvalRequired,
|
||||
ApprovalAmountRequired: (*hexutil.Big)(approvalAmountRequired),
|
||||
|
@ -952,9 +976,17 @@ func (r *Router) resolveCandidates(ctx context.Context, input *RouteInputParams,
|
|||
ApprovalBaseFee: (*hexutil.Big)(fees.BaseFee),
|
||||
ApprovalPriorityFee: (*hexutil.Big)(fees.MaxPriorityFeePerGas),
|
||||
ApprovalGasAmount: approvalGasLimit,
|
||||
ApprovalL1Fee: (*hexutil.Big)(big.NewInt(int64(l1ApprovalFee))),
|
||||
|
||||
ApprovalFee: (*hexutil.Big)(approvalFeeInWei),
|
||||
ApprovalL1Fee: (*hexutil.Big)(approvalL1FeeInWei),
|
||||
|
||||
TxTotalFee: (*hexutil.Big)(ethTotalFees),
|
||||
|
||||
EstimatedTime: estimatedTime,
|
||||
|
||||
subtractFees: amountOption.subtractFees,
|
||||
requiredTokenBalance: requiredTokenBalance,
|
||||
requiredNativeBalance: requiredNativeBalance,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -968,22 +1000,41 @@ func (r *Router) resolveCandidates(ctx context.Context, input *RouteInputParams,
|
|||
}
|
||||
|
||||
func (r *Router) checkBalancesForTheBestRoute(ctx context.Context, bestRoute []*PathV2, input *RouteInputParams, balanceMap map[string]*big.Int) (err error) {
|
||||
balanceMapCopy := copyMapGeneric(balanceMap, func(v interface{}) interface{} {
|
||||
return new(big.Int).Set(v.(*big.Int))
|
||||
}).(map[string]*big.Int)
|
||||
if balanceMapCopy == nil {
|
||||
return ErrCannotCheckReceiverBalance
|
||||
}
|
||||
|
||||
// check the best route for the required balances
|
||||
for _, path := range bestRoute {
|
||||
if path.requiredTokenBalance != nil && path.requiredTokenBalance.Cmp(pathprocessor.ZeroBigIntValue) > 0 {
|
||||
if tokenBalance, ok := balanceMap[makeBalanceKey(path.FromChain.ChainID, path.FromToken.Symbol)]; ok {
|
||||
key := makeBalanceKey(path.FromChain.ChainID, path.FromToken.Symbol)
|
||||
if tokenBalance, ok := balanceMapCopy[key]; ok {
|
||||
if tokenBalance.Cmp(path.requiredTokenBalance) == -1 {
|
||||
return ErrNotEnoughTokenBalance
|
||||
err := &errors.ErrorResponse{
|
||||
Code: ErrNotEnoughTokenBalance.Code,
|
||||
Details: fmt.Sprintf(ErrNotEnoughTokenBalance.Details, path.FromToken.Symbol, path.FromChain.ChainID),
|
||||
}
|
||||
return err
|
||||
}
|
||||
balanceMapCopy[key].Sub(tokenBalance, path.requiredTokenBalance)
|
||||
} else {
|
||||
return ErrTokenNotFound
|
||||
}
|
||||
}
|
||||
|
||||
if nativeBalance, ok := balanceMap[makeBalanceKey(path.FromChain.ChainID, pathprocessor.EthSymbol)]; ok {
|
||||
key := makeBalanceKey(path.FromChain.ChainID, pathprocessor.EthSymbol)
|
||||
if nativeBalance, ok := balanceMapCopy[key]; ok {
|
||||
if nativeBalance.Cmp(path.requiredNativeBalance) == -1 {
|
||||
return ErrNotEnoughNativeBalance
|
||||
err := &errors.ErrorResponse{
|
||||
Code: ErrNotEnoughNativeBalance.Code,
|
||||
Details: fmt.Sprintf(ErrNotEnoughNativeBalance.Details, pathprocessor.EthSymbol, path.FromChain.ChainID),
|
||||
}
|
||||
return err
|
||||
}
|
||||
balanceMapCopy[key].Sub(nativeBalance, path.requiredNativeBalance)
|
||||
} else {
|
||||
return ErrNativeTokenNotFound
|
||||
}
|
||||
|
@ -993,7 +1044,8 @@ func (r *Router) checkBalancesForTheBestRoute(ctx context.Context, bestRoute []*
|
|||
}
|
||||
|
||||
func removeBestRouteFromAllRouters(allRoutes [][]*PathV2, best []*PathV2) [][]*PathV2 {
|
||||
for i, route := range allRoutes {
|
||||
for i := len(allRoutes) - 1; i >= 0; i-- {
|
||||
route := allRoutes[i]
|
||||
routeFound := true
|
||||
for _, p := range route {
|
||||
found := false
|
||||
|
@ -1031,13 +1083,13 @@ func (r *Router) resolveRoutes(ctx context.Context, input *RouteInputParams, can
|
|||
}
|
||||
|
||||
tokenPrice := prices[input.TokenID]
|
||||
nativeChainTokenPrice := prices[pathprocessor.EthSymbol]
|
||||
nativeTokenPrice := prices[pathprocessor.EthSymbol]
|
||||
|
||||
var allRoutes [][]*PathV2
|
||||
suggestedRoutes, allRoutes = newSuggestedRoutesV2(input.Uuid, input.AmountIn.ToInt(), candidates, input.FromLockedAmount, tokenPrice, nativeChainTokenPrice)
|
||||
suggestedRoutes, allRoutes = newSuggestedRoutesV2(input.Uuid, input.AmountIn.ToInt(), candidates, input.FromLockedAmount, tokenPrice, nativeTokenPrice)
|
||||
|
||||
for len(allRoutes) > 0 {
|
||||
best := findBestV2(allRoutes, tokenPrice, nativeChainTokenPrice)
|
||||
best := findBestV2(allRoutes, tokenPrice, nativeTokenPrice)
|
||||
|
||||
err := r.checkBalancesForTheBestRoute(ctx, best, input, balanceMap)
|
||||
if err != nil {
|
||||
|
@ -1057,6 +1109,22 @@ func (r *Router) resolveRoutes(ctx context.Context, input *RouteInputParams, can
|
|||
sort.Slice(best, func(i, j int) bool {
|
||||
return best[i].AmountInLocked
|
||||
})
|
||||
|
||||
// At this point we have to do the final check and update the amountIn (subtracting fees) if complete balance is going to be sent for native token (ETH)
|
||||
for _, path := range best {
|
||||
if path.subtractFees && path.FromToken.IsNative() {
|
||||
path.AmountIn.ToInt().Sub(path.AmountIn.ToInt(), path.TxFee.ToInt())
|
||||
if path.TxL1Fee.ToInt().Cmp(pathprocessor.ZeroBigIntValue) > 0 {
|
||||
path.AmountIn.ToInt().Sub(path.AmountIn.ToInt(), path.TxL1Fee.ToInt())
|
||||
}
|
||||
if path.ApprovalRequired {
|
||||
path.AmountIn.ToInt().Sub(path.AmountIn.ToInt(), path.ApprovalFee.ToInt())
|
||||
if path.ApprovalL1Fee.ToInt().Cmp(pathprocessor.ZeroBigIntValue) > 0 {
|
||||
path.AmountIn.ToInt().Sub(path.AmountIn.ToInt(), path.ApprovalL1Fee.ToInt())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
suggestedRoutes.Best = best
|
||||
break
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package router
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
|
@ -2500,6 +2501,90 @@ func getNormalTestParamsList() []normalTestParams {
|
|||
expectedError: ErrNoBestRouteFound,
|
||||
expectedCandidates: []*PathV2{},
|
||||
},
|
||||
{
|
||||
name: "ETH transfer - Not Enough Native Balance",
|
||||
input: &RouteInputParams{
|
||||
testnetMode: false,
|
||||
Uuid: uuid.NewString(),
|
||||
SendType: Transfer,
|
||||
AddrFrom: common.HexToAddress("0x1"),
|
||||
AddrTo: common.HexToAddress("0x2"),
|
||||
AmountIn: (*hexutil.Big)(big.NewInt(testAmount3ETHInWei)),
|
||||
TokenID: pathprocessor.EthSymbol,
|
||||
DisabledFromChainIDs: []uint64{walletCommon.OptimismMainnet, walletCommon.ArbitrumMainnet},
|
||||
DisabledToChainIDs: []uint64{walletCommon.EthereumMainnet, walletCommon.ArbitrumMainnet},
|
||||
|
||||
testsMode: true,
|
||||
testParams: &routerTestParams{
|
||||
tokenFrom: &token.Token{
|
||||
ChainID: 1,
|
||||
Symbol: pathprocessor.EthSymbol,
|
||||
Decimals: 18,
|
||||
},
|
||||
tokenPrices: testTokenPrices,
|
||||
suggestedFees: testSuggestedFees,
|
||||
balanceMap: testBalanceMapPerChain,
|
||||
estimationMap: testEstimationMap,
|
||||
bonderFeeMap: testBbonderFeeMap,
|
||||
approvalGasEstimation: testApprovalGasEstimation,
|
||||
approvalL1Fee: testApprovalL1Fee,
|
||||
},
|
||||
},
|
||||
expectedError: &errors.ErrorResponse{
|
||||
Code: ErrNotEnoughNativeBalance.Code,
|
||||
Details: fmt.Sprintf(ErrNotEnoughNativeBalance.Details, pathprocessor.EthSymbol, walletCommon.EthereumMainnet),
|
||||
},
|
||||
expectedCandidates: []*PathV2{
|
||||
{
|
||||
ProcessorName: pathprocessor.ProcessorBridgeHopName,
|
||||
FromChain: &mainnet,
|
||||
ToChain: &optimism,
|
||||
ApprovalRequired: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ETH transfer - Not Enough Native Balance",
|
||||
input: &RouteInputParams{
|
||||
testnetMode: false,
|
||||
Uuid: uuid.NewString(),
|
||||
SendType: Transfer,
|
||||
AddrFrom: common.HexToAddress("0x1"),
|
||||
AddrTo: common.HexToAddress("0x2"),
|
||||
AmountIn: (*hexutil.Big)(big.NewInt(5 * testAmount100USDC)),
|
||||
TokenID: pathprocessor.UsdcSymbol,
|
||||
DisabledFromChainIDs: []uint64{walletCommon.OptimismMainnet, walletCommon.ArbitrumMainnet},
|
||||
DisabledToChainIDs: []uint64{walletCommon.EthereumMainnet, walletCommon.ArbitrumMainnet},
|
||||
|
||||
testsMode: true,
|
||||
testParams: &routerTestParams{
|
||||
tokenFrom: &token.Token{
|
||||
ChainID: 1,
|
||||
Symbol: pathprocessor.UsdcSymbol,
|
||||
Decimals: 6,
|
||||
},
|
||||
tokenPrices: testTokenPrices,
|
||||
suggestedFees: testSuggestedFees,
|
||||
balanceMap: testBalanceMapPerChain,
|
||||
estimationMap: testEstimationMap,
|
||||
bonderFeeMap: testBbonderFeeMap,
|
||||
approvalGasEstimation: testApprovalGasEstimation,
|
||||
approvalL1Fee: testApprovalL1Fee,
|
||||
},
|
||||
},
|
||||
expectedError: &errors.ErrorResponse{
|
||||
Code: ErrNotEnoughTokenBalance.Code,
|
||||
Details: fmt.Sprintf(ErrNotEnoughTokenBalance.Details, pathprocessor.UsdcSymbol, walletCommon.EthereumMainnet),
|
||||
},
|
||||
expectedCandidates: []*PathV2{
|
||||
{
|
||||
ProcessorName: pathprocessor.ProcessorBridgeHopName,
|
||||
FromChain: &mainnet,
|
||||
ToChain: &optimism,
|
||||
ApprovalRequired: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue