chore_: router response moved to wallet responses location

Following the approach we did for keeping requests at the same location, these changes introduce
new location for responses. `SuggestedRoutesResponse` is moved there and renamed to
`RouterSuggestedRoutes` and code is updated accordingly.

New type `Route` defined (since a single route is composed of zero or more paths).

Types `Route`, `Path`, `Graph` and `Node` belong to a new `routs` package now.
This commit is contained in:
Sale Djenic 2024-08-28 14:06:50 +02:00 committed by saledjenic
parent 00559692bc
commit 1bb9cbc573
10 changed files with 342 additions and 321 deletions

View File

@ -0,0 +1,15 @@
package responses
import (
"github.com/status-im/status-go/errors"
"github.com/status-im/status-go/services/wallet/router/routes"
)
type RouterSuggestedRoutes struct {
Uuid string `json:"Uuid"`
Best routes.Route `json:"Best,omitempty"`
Candidates routes.Route `json:"Candidates,omitempty"`
TokenPrice *float64 `json:"TokenPrice,omitempty"`
NativeChainTokenPrice *float64 `json:"NativeChainTokenPrice,omitempty"`
ErrorResponse *errors.ErrorResponse `json:"ErrorResponse,omitempty"`
}

View File

@ -2,9 +2,10 @@ package router
import (
"github.com/status-im/status-go/services/wallet/common"
"github.com/status-im/status-go/services/wallet/router/routes"
)
func removeBestRouteFromAllRouters(allRoutes [][]*Path, best []*Path) [][]*Path {
func removeBestRouteFromAllRouters(allRoutes []routes.Route, best routes.Route) []routes.Route {
for i := len(allRoutes) - 1; i >= 0; i-- {
route := allRoutes[i]
routeFound := true
@ -45,7 +46,7 @@ func getChainPriority(chainID uint64) int {
}
}
func getRoutePriority(route []*Path) int {
func getRoutePriority(route routes.Route) int {
priority := 0
for _, path := range route {
priority += getChainPriority(path.FromChain.ChainID)

View File

@ -6,6 +6,7 @@ import (
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/status-im/status-go/services/wallet/common"
"github.com/status-im/status-go/services/wallet/router/pathprocessor"
"github.com/status-im/status-go/services/wallet/router/routes"
"go.uber.org/zap"
)
@ -20,7 +21,7 @@ func init() {
}
}
func filterRoutes(routes [][]*Path, amountIn *big.Int, fromLockedAmount map[uint64]*hexutil.Big) [][]*Path {
func filterRoutes(routes []routes.Route, amountIn *big.Int, fromLockedAmount map[uint64]*hexutil.Big) []routes.Route {
for i := len(routes) - 1; i >= 0; i-- {
routeAmount := big.NewInt(0)
for _, p := range routes[i] {
@ -43,15 +44,15 @@ func filterRoutes(routes [][]*Path, amountIn *big.Int, fromLockedAmount map[uint
}
// filterNetworkCompliance performs the first level of filtering based on network inclusion/exclusion criteria.
func filterNetworkCompliance(routes [][]*Path, fromLockedAmount map[uint64]*hexutil.Big) [][]*Path {
filteredRoutes := make([][]*Path, 0)
if routes == nil || fromLockedAmount == nil {
func filterNetworkCompliance(allRoutes []routes.Route, fromLockedAmount map[uint64]*hexutil.Big) []routes.Route {
filteredRoutes := make([]routes.Route, 0)
if allRoutes == nil || fromLockedAmount == nil {
return filteredRoutes
}
fromIncluded, fromExcluded := setupRouteValidationMaps(fromLockedAmount)
for _, route := range routes {
for _, route := range allRoutes {
if route == nil {
continue
}
@ -65,7 +66,7 @@ func filterNetworkCompliance(routes [][]*Path, fromLockedAmount map[uint64]*hexu
}
// isValidForNetworkCompliance checks if a route complies with network inclusion/exclusion criteria.
func isValidForNetworkCompliance(route []*Path, fromIncluded, fromExcluded map[uint64]bool) bool {
func isValidForNetworkCompliance(route routes.Route, fromIncluded, fromExcluded map[uint64]bool) bool {
logger.Debug("Initial inclusion/exclusion maps",
zap.Any("fromIncluded", fromIncluded),
zap.Any("fromExcluded", fromExcluded),
@ -117,10 +118,10 @@ func setupRouteValidationMaps(fromLockedAmount map[uint64]*hexutil.Big) (map[uin
}
// filterCapacityValidation performs the second level of filtering based on amount and capacity validation.
func filterCapacityValidation(routes [][]*Path, amountIn *big.Int, fromLockedAmount map[uint64]*hexutil.Big) [][]*Path {
filteredRoutes := make([][]*Path, 0)
func filterCapacityValidation(allRoutes []routes.Route, amountIn *big.Int, fromLockedAmount map[uint64]*hexutil.Big) []routes.Route {
filteredRoutes := make([]routes.Route, 0)
for _, route := range routes {
for _, route := range allRoutes {
if hasSufficientCapacity(route, amountIn, fromLockedAmount) {
filteredRoutes = append(filteredRoutes, route)
}
@ -129,7 +130,7 @@ func filterCapacityValidation(routes [][]*Path, amountIn *big.Int, fromLockedAmo
}
// hasSufficientCapacity checks if a route has sufficient capacity to handle the required amount.
func hasSufficientCapacity(route []*Path, amountIn *big.Int, fromLockedAmount map[uint64]*hexutil.Big) bool {
func hasSufficientCapacity(route routes.Route, 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 {
@ -153,7 +154,7 @@ func hasSufficientCapacity(route []*Path, amountIn *big.Int, fromLockedAmount ma
}
// calculateRestAmountIn calculates the remaining amount in for the route excluding the specified path
func calculateRestAmountIn(route []*Path, excludePath *Path) *big.Int {
func calculateRestAmountIn(route routes.Route, excludePath *routes.Path) *big.Int {
restAmountIn := big.NewInt(0)
for _, path := range route {
if path != excludePath {

View File

@ -8,6 +8,7 @@ import (
"github.com/status-im/status-go/params"
"github.com/status-im/status-go/services/wallet/router/pathprocessor"
"github.com/status-im/status-go/services/wallet/router/routes"
"github.com/stretchr/testify/assert"
)
@ -26,24 +27,24 @@ var (
amount4 = hexutil.Big(*big.NewInt(400))
amount5 = hexutil.Big(*big.NewInt(500))
path0 = &Path{FromChain: network4, AmountIn: &amount0}
path0 = &routes.Path{FromChain: network4, AmountIn: &amount0}
pathC1A1 = &Path{FromChain: network1, AmountIn: &amount1}
pathC1A1 = &routes.Path{FromChain: network1, AmountIn: &amount1}
pathC2A1 = &Path{FromChain: network2, AmountIn: &amount1}
pathC2A2 = &Path{FromChain: network2, AmountIn: &amount2}
pathC2A1 = &routes.Path{FromChain: network2, AmountIn: &amount1}
pathC2A2 = &routes.Path{FromChain: network2, AmountIn: &amount2}
pathC3A1 = &Path{FromChain: network3, AmountIn: &amount1}
pathC3A2 = &Path{FromChain: network3, AmountIn: &amount2}
pathC3A3 = &Path{FromChain: network3, AmountIn: &amount3}
pathC3A1 = &routes.Path{FromChain: network3, AmountIn: &amount1}
pathC3A2 = &routes.Path{FromChain: network3, AmountIn: &amount2}
pathC3A3 = &routes.Path{FromChain: network3, AmountIn: &amount3}
pathC4A1 = &Path{FromChain: network4, AmountIn: &amount1}
pathC4A4 = &Path{FromChain: network4, AmountIn: &amount4}
pathC4A1 = &routes.Path{FromChain: network4, AmountIn: &amount1}
pathC4A4 = &routes.Path{FromChain: network4, AmountIn: &amount4}
pathC5A5 = &Path{FromChain: network5, AmountIn: &amount5}
pathC5A5 = &routes.Path{FromChain: network5, AmountIn: &amount5}
)
func routesEqual(t *testing.T, expected, actual [][]*Path) bool {
func routesEqual(t *testing.T, expected, actual []routes.Route) bool {
if len(expected) != len(actual) {
return false
}
@ -55,7 +56,7 @@ func routesEqual(t *testing.T, expected, actual [][]*Path) bool {
return true
}
func pathsEqual(t *testing.T, expected, actual []*Path) bool {
func pathsEqual(t *testing.T, expected, actual routes.Route) bool {
if len(expected) != len(actual) {
return false
}
@ -67,7 +68,7 @@ func pathsEqual(t *testing.T, expected, actual []*Path) bool {
return true
}
func pathEqual(t *testing.T, expected, actual *Path) bool {
func pathEqual(t *testing.T, expected, actual *routes.Path) bool {
if expected.FromChain.ChainID != actual.FromChain.ChainID {
t.Logf("expected chain ID '%d' , actual chain ID '%d'", expected.FromChain.ChainID, actual.FromChain.ChainID)
return false
@ -171,43 +172,43 @@ func TestSetupRouteValidationMaps(t *testing.T) {
func TestCalculateRestAmountIn(t *testing.T) {
tests := []struct {
name string
route []*Path
excludePath *Path
route routes.Route
excludePath *routes.Path
expected *big.Int
}{
{
name: "Exclude pathC1A1",
route: []*Path{pathC1A1, pathC2A2, pathC3A3},
route: routes.Route{pathC1A1, pathC2A2, pathC3A3},
excludePath: pathC1A1,
expected: big.NewInt(500), // 200 + 300
},
{
name: "Exclude pathC2A2",
route: []*Path{pathC1A1, pathC2A2, pathC3A3},
route: routes.Route{pathC1A1, pathC2A2, pathC3A3},
excludePath: pathC2A2,
expected: big.NewInt(400), // 100 + 300
},
{
name: "Exclude pathC3A3",
route: []*Path{pathC1A1, pathC2A2, pathC3A3},
route: routes.Route{pathC1A1, pathC2A2, pathC3A3},
excludePath: pathC3A3,
expected: big.NewInt(300), // 100 + 200
},
{
name: "Single path, exclude that path",
route: []*Path{pathC1A1},
route: routes.Route{pathC1A1},
excludePath: pathC1A1,
expected: big.NewInt(0), // No other paths
},
{
name: "Empty route",
route: []*Path{},
route: routes.Route{},
excludePath: pathC1A1,
expected: big.NewInt(0), // No paths
},
{
name: "Empty route, with nil exclude",
route: []*Path{},
route: routes.Route{},
excludePath: nil,
expected: big.NewInt(0), // No paths
},
@ -224,56 +225,56 @@ func TestCalculateRestAmountIn(t *testing.T) {
func TestIsValidForNetworkCompliance(t *testing.T) {
tests := []struct {
name string
route []*Path
route routes.Route
fromIncluded map[uint64]bool
fromExcluded map[uint64]bool
expectedResult bool
}{
{
name: "Route with all included chain IDs",
route: []*Path{pathC1A1, pathC2A2},
route: routes.Route{pathC1A1, pathC2A2},
fromIncluded: map[uint64]bool{1: true, 2: true},
fromExcluded: map[uint64]bool{},
expectedResult: true,
},
{
name: "Route with fromExcluded only",
route: []*Path{pathC1A1, pathC2A2},
route: routes.Route{pathC1A1, pathC2A2},
fromIncluded: map[uint64]bool{},
fromExcluded: map[uint64]bool{3: false, 4: false},
expectedResult: true,
},
{
name: "Route without excluded chain IDs",
route: []*Path{pathC1A1, pathC2A2},
route: routes.Route{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: []*Path{pathC1A1, pathC3A3},
route: routes.Route{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: []*Path{pathC1A1},
route: routes.Route{pathC1A1},
fromIncluded: map[uint64]bool{1: false, 2: false},
fromExcluded: map[uint64]bool{},
expectedResult: false,
},
{
name: "Route with no fromIncluded or fromExcluded",
route: []*Path{pathC1A1, pathC2A2},
route: routes.Route{pathC1A1, pathC2A2},
fromIncluded: map[uint64]bool{},
fromExcluded: map[uint64]bool{},
expectedResult: true,
},
{
name: "Empty route",
route: []*Path{},
route: routes.Route{},
fromIncluded: map[uint64]bool{1: false, 2: false},
fromExcluded: map[uint64]bool{3: false, 4: false},
expectedResult: false,
@ -291,14 +292,14 @@ func TestIsValidForNetworkCompliance(t *testing.T) {
func TestHasSufficientCapacity(t *testing.T) {
tests := []struct {
name string
route []*Path
route routes.Route
amountIn *big.Int
fromLockedAmount map[uint64]*hexutil.Big
expected bool
}{
{
name: "All paths meet required amount",
route: []*Path{pathC1A1, pathC2A2, pathC3A3},
route: routes.Route{pathC1A1, pathC2A2, pathC3A3},
amountIn: big.NewInt(600),
fromLockedAmount: map[uint64]*hexutil.Big{1: &amount1, 2: &amount2, 3: &amount3},
expected: true,
@ -308,7 +309,7 @@ func TestHasSufficientCapacity(t *testing.T) {
/*
{
name: "A path does not meet required amount",
route: []*Path{pathC1A1, pathC2A2, pathC3A3},
route: routes.Route{pathC1A1, pathC2A2, pathC3A3},
amountIn: big.NewInt(600),
fromLockedAmount: map[uint64]*hexutil.Big{1: &amount1, 2: &amount2, 4: &amount4},
expected: false,
@ -316,42 +317,42 @@ func TestHasSufficientCapacity(t *testing.T) {
*/
{
name: "No fromLockedAmount",
route: []*Path{pathC1A1, pathC2A2, pathC3A3},
route: routes.Route{pathC1A1, pathC2A2, pathC3A3},
amountIn: big.NewInt(600),
fromLockedAmount: map[uint64]*hexutil.Big{},
expected: true,
},
{
name: "Single path meets required amount",
route: []*Path{pathC1A1},
route: routes.Route{pathC1A1},
amountIn: big.NewInt(100),
fromLockedAmount: map[uint64]*hexutil.Big{1: &amount1},
expected: true,
},
{
name: "Single path does not meet required amount",
route: []*Path{pathC1A1},
route: routes.Route{pathC1A1},
amountIn: big.NewInt(200),
fromLockedAmount: map[uint64]*hexutil.Big{1: &amount1},
expected: false,
},
{
name: "Path meets required amount with excess",
route: []*Path{pathC1A1, pathC2A2},
route: routes.Route{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: []*Path{pathC1A1, pathC2A2, pathC4A4},
route: routes.Route{pathC1A1, pathC2A2, pathC4A4},
amountIn: big.NewInt(800),
fromLockedAmount: map[uint64]*hexutil.Big{1: &amount1, 4: &amount4},
expected: false,
},
{
name: "Empty route",
route: []*Path{},
route: routes.Route{},
amountIn: big.NewInt(500),
fromLockedAmount: map[uint64]*hexutil.Big{1: &amount1, 2: &amount2},
expected: true,
@ -369,13 +370,13 @@ func TestHasSufficientCapacity(t *testing.T) {
func TestFilterNetworkCompliance(t *testing.T) {
tests := []struct {
name string
routes [][]*Path
routes []routes.Route
fromLockedAmount map[uint64]*hexutil.Big
expected [][]*Path
expected []routes.Route
}{
{
name: "Mixed routes with valid and invalid paths",
routes: [][]*Path{
routes: []routes.Route{
{
{FromChain: network1},
{FromChain: network3},
@ -394,7 +395,7 @@ func TestFilterNetworkCompliance(t *testing.T) {
1: (*hexutil.Big)(big.NewInt(100)),
2: (*hexutil.Big)(big.NewInt(0)),
},
expected: [][]*Path{
expected: []routes.Route{
{
{FromChain: network1},
{FromChain: network3},
@ -403,7 +404,7 @@ func TestFilterNetworkCompliance(t *testing.T) {
},
{
name: "All valid routes",
routes: [][]*Path{
routes: []routes.Route{
{
{FromChain: network1},
{FromChain: network3},
@ -416,7 +417,7 @@ func TestFilterNetworkCompliance(t *testing.T) {
fromLockedAmount: map[uint64]*hexutil.Big{
1: (*hexutil.Big)(big.NewInt(100)),
},
expected: [][]*Path{
expected: []routes.Route{
{
{FromChain: network1},
{FromChain: network3},
@ -429,7 +430,7 @@ func TestFilterNetworkCompliance(t *testing.T) {
},
{
name: "All invalid routes",
routes: [][]*Path{
routes: []routes.Route{
{
{FromChain: network2},
{FromChain: network3},
@ -443,19 +444,19 @@ func TestFilterNetworkCompliance(t *testing.T) {
1: (*hexutil.Big)(big.NewInt(100)),
2: (*hexutil.Big)(big.NewInt(0)),
},
expected: [][]*Path{},
expected: []routes.Route{},
},
{
name: "Empty routes",
routes: [][]*Path{},
routes: []routes.Route{},
fromLockedAmount: map[uint64]*hexutil.Big{
1: (*hexutil.Big)(big.NewInt(100)),
},
expected: [][]*Path{},
expected: []routes.Route{},
},
{
name: "No locked amounts",
routes: [][]*Path{
routes: []routes.Route{
{
{FromChain: network1},
{FromChain: network2},
@ -466,7 +467,7 @@ func TestFilterNetworkCompliance(t *testing.T) {
},
},
fromLockedAmount: map[uint64]*hexutil.Big{},
expected: [][]*Path{
expected: []routes.Route{
{
{FromChain: network1},
{FromChain: network2},
@ -479,7 +480,7 @@ func TestFilterNetworkCompliance(t *testing.T) {
},
{
name: "Single route with mixed valid and invalid paths",
routes: [][]*Path{
routes: []routes.Route{
{
{FromChain: network1},
{FromChain: network2},
@ -490,11 +491,11 @@ func TestFilterNetworkCompliance(t *testing.T) {
1: (*hexutil.Big)(big.NewInt(100)),
2: (*hexutil.Big)(big.NewInt(0)),
},
expected: [][]*Path{},
expected: []routes.Route{},
},
{
name: "Routes with duplicate chain IDs",
routes: [][]*Path{
routes: []routes.Route{
{
{FromChain: network1},
{FromChain: network1},
@ -504,7 +505,7 @@ func TestFilterNetworkCompliance(t *testing.T) {
fromLockedAmount: map[uint64]*hexutil.Big{
1: (*hexutil.Big)(big.NewInt(100)),
},
expected: [][]*Path{
expected: []routes.Route{
{
{FromChain: network1},
{FromChain: network1},
@ -514,7 +515,7 @@ func TestFilterNetworkCompliance(t *testing.T) {
},
{
name: "Minimum and maximum chain IDs",
routes: [][]*Path{
routes: []routes.Route{
{
{FromChain: &params.Network{ChainID: 0}},
{FromChain: &params.Network{ChainID: ^uint64(0)}},
@ -524,7 +525,7 @@ func TestFilterNetworkCompliance(t *testing.T) {
0: (*hexutil.Big)(big.NewInt(100)),
^uint64(0): (*hexutil.Big)(big.NewInt(100)),
},
expected: [][]*Path{
expected: []routes.Route{
{
{FromChain: &params.Network{ChainID: 0}},
{FromChain: &params.Network{ChainID: ^uint64(0)}},
@ -533,34 +534,34 @@ func TestFilterNetworkCompliance(t *testing.T) {
},
{
name: "Large number of routes",
routes: func() [][]*Path {
var routes [][]*Path
routes: func() []routes.Route {
var routes1 []routes.Route
for i := 0; i < 1000; i++ {
routes = append(routes, []*Path{
routes1 = append(routes1, routes.Route{
{FromChain: &params.Network{ChainID: uint64(i + 1)}},
{FromChain: &params.Network{ChainID: uint64(i + 1001)}},
})
}
return routes
return routes1
}(),
fromLockedAmount: map[uint64]*hexutil.Big{
1: (*hexutil.Big)(big.NewInt(100)),
1001: (*hexutil.Big)(big.NewInt(100)),
},
expected: func() [][]*Path {
var routes [][]*Path
expected: func() []routes.Route {
var routes1 []routes.Route
for i := 0; i < 1; i++ {
routes = append(routes, []*Path{
routes1 = append(routes1, routes.Route{
{FromChain: &params.Network{ChainID: uint64(i + 1)}},
{FromChain: &params.Network{ChainID: uint64(i + 1001)}},
})
}
return routes
return routes1
}(),
},
{
name: "Routes with missing data",
routes: [][]*Path{
routes: []routes.Route{
{
{FromChain: nil},
{FromChain: network2},
@ -574,11 +575,11 @@ func TestFilterNetworkCompliance(t *testing.T) {
1: (*hexutil.Big)(big.NewInt(100)),
2: (*hexutil.Big)(big.NewInt(0)),
},
expected: [][]*Path{},
expected: []routes.Route{},
},
{
name: "Consistency check",
routes: [][]*Path{
routes: []routes.Route{
{
{FromChain: network1},
{FromChain: network2},
@ -591,7 +592,7 @@ func TestFilterNetworkCompliance(t *testing.T) {
fromLockedAmount: map[uint64]*hexutil.Big{
1: (*hexutil.Big)(big.NewInt(100)),
},
expected: [][]*Path{
expected: []routes.Route{
{
{FromChain: network1},
{FromChain: network2},
@ -604,77 +605,77 @@ func TestFilterNetworkCompliance(t *testing.T) {
},
{
name: "Routes without excluded chain IDs, missing included path",
routes: [][]*Path{
routes: []routes.Route{
{pathC1A1, pathC2A2},
{pathC2A2, pathC3A3},
},
fromLockedAmount: map[uint64]*hexutil.Big{1: &amount1, 2: &amount2},
expected: [][]*Path{
expected: []routes.Route{
{pathC1A1, pathC2A2},
},
},
{
name: "Routes with an excluded chain ID",
routes: [][]*Path{
routes: []routes.Route{
{pathC1A1, pathC2A2},
{pathC2A2, pathC3A3, path0},
},
fromLockedAmount: map[uint64]*hexutil.Big{1: &amount1, 2: &amount2, 4: &amount0},
expected: [][]*Path{
expected: []routes.Route{
{pathC1A1, pathC2A2},
},
},
{
name: "Routes with all included chain IDs",
routes: [][]*Path{
routes: []routes.Route{
{pathC1A1, pathC2A2, pathC3A3},
},
fromLockedAmount: map[uint64]*hexutil.Big{1: &amount1, 2: &amount2, 3: &amount3},
expected: [][]*Path{
expected: []routes.Route{
{pathC1A1, pathC2A2, pathC3A3},
},
},
{
name: "Routes missing one included chain ID",
routes: [][]*Path{
routes: []routes.Route{
{pathC1A1, pathC2A2},
{pathC1A1},
},
fromLockedAmount: map[uint64]*hexutil.Big{1: &amount1, 2: &amount2, 3: &amount3},
expected: [][]*Path{},
expected: []routes.Route{},
},
{
name: "Routes with no fromLockedAmount",
routes: [][]*Path{
routes: []routes.Route{
{pathC1A1, pathC2A2},
{pathC2A2, pathC3A3},
},
fromLockedAmount: map[uint64]*hexutil.Big{},
expected: [][]*Path{
expected: []routes.Route{
{pathC1A1, pathC2A2},
{pathC2A2, pathC3A3},
},
},
{
name: "Routes with fromExcluded only",
routes: [][]*Path{
routes: []routes.Route{
{pathC1A1, pathC2A2},
{pathC2A2, pathC3A3},
},
fromLockedAmount: map[uint64]*hexutil.Big{4: &amount0},
expected: [][]*Path{
expected: []routes.Route{
{pathC1A1, pathC2A2},
{pathC2A2, pathC3A3},
},
},
{
name: "Routes with all excluded chain IDs",
routes: [][]*Path{
routes: []routes.Route{
{path0, pathC1A1},
{path0, pathC2A2},
},
fromLockedAmount: map[uint64]*hexutil.Big{1: &amount1, 2: &amount2, 3: &amount3, 4: &amount0},
expected: [][]*Path{},
expected: []routes.Route{},
},
}
@ -691,14 +692,14 @@ func TestFilterNetworkCompliance(t *testing.T) {
func TestFilterCapacityValidation(t *testing.T) {
tests := []struct {
name string
routes [][]*Path
routes []routes.Route
amountIn *big.Int
fromLockedAmount map[uint64]*hexutil.Big
expectedRoutes [][]*Path
expectedRoutes []routes.Route
}{
{
name: "Sufficient capacity with multiple paths",
routes: [][]*Path{
routes: []routes.Route{
{
{FromChain: network1, AmountIn: (*hexutil.Big)(big.NewInt(50))},
{FromChain: network2, AmountIn: (*hexutil.Big)(big.NewInt(100))},
@ -717,7 +718,7 @@ func TestFilterCapacityValidation(t *testing.T) {
fromLockedAmount: map[uint64]*hexutil.Big{
1: (*hexutil.Big)(big.NewInt(50)),
},
expectedRoutes: [][]*Path{
expectedRoutes: []routes.Route{
{
{FromChain: network1, AmountIn: (*hexutil.Big)(big.NewInt(50))},
{FromChain: network2, AmountIn: (*hexutil.Big)(big.NewInt(100))},
@ -731,7 +732,7 @@ func TestFilterCapacityValidation(t *testing.T) {
},
{
name: "Insufficient capacity",
routes: [][]*Path{
routes: []routes.Route{
{
{FromChain: network1, AmountIn: (*hexutil.Big)(big.NewInt(100))},
{FromChain: network2, AmountIn: (*hexutil.Big)(big.NewInt(50))},
@ -742,11 +743,11 @@ func TestFilterCapacityValidation(t *testing.T) {
1: (*hexutil.Big)(big.NewInt(50)),
2: (*hexutil.Big)(big.NewInt(50)),
},
expectedRoutes: [][]*Path{},
expectedRoutes: []routes.Route{},
},
{
name: "Exact capacity match",
routes: [][]*Path{
routes: []routes.Route{
{
{FromChain: network1, AmountIn: (*hexutil.Big)(big.NewInt(100))},
{FromChain: network2, AmountIn: (*hexutil.Big)(big.NewInt(50))},
@ -757,7 +758,7 @@ func TestFilterCapacityValidation(t *testing.T) {
1: (*hexutil.Big)(big.NewInt(100)),
2: (*hexutil.Big)(big.NewInt(50)),
},
expectedRoutes: [][]*Path{
expectedRoutes: []routes.Route{
{
{FromChain: network1, AmountIn: (*hexutil.Big)(big.NewInt(100))},
{FromChain: network2, AmountIn: (*hexutil.Big)(big.NewInt(50))},
@ -766,7 +767,7 @@ func TestFilterCapacityValidation(t *testing.T) {
},
{
name: "No locked amounts",
routes: [][]*Path{
routes: []routes.Route{
{
{FromChain: network1, AmountIn: (*hexutil.Big)(big.NewInt(100))},
{FromChain: network2, AmountIn: (*hexutil.Big)(big.NewInt(50))},
@ -774,7 +775,7 @@ func TestFilterCapacityValidation(t *testing.T) {
},
amountIn: big.NewInt(150),
fromLockedAmount: map[uint64]*hexutil.Big{},
expectedRoutes: [][]*Path{
expectedRoutes: []routes.Route{
{
{FromChain: network1, AmountIn: (*hexutil.Big)(big.NewInt(100))},
{FromChain: network2, AmountIn: (*hexutil.Big)(big.NewInt(50))},
@ -783,7 +784,7 @@ func TestFilterCapacityValidation(t *testing.T) {
},
{
name: "Single route with sufficient capacity",
routes: [][]*Path{
routes: []routes.Route{
{
{FromChain: network1, AmountIn: (*hexutil.Big)(big.NewInt(50))},
{FromChain: network2, AmountIn: (*hexutil.Big)(big.NewInt(100))},
@ -793,7 +794,7 @@ func TestFilterCapacityValidation(t *testing.T) {
fromLockedAmount: map[uint64]*hexutil.Big{
1: (*hexutil.Big)(big.NewInt(50)),
},
expectedRoutes: [][]*Path{
expectedRoutes: []routes.Route{
{
{FromChain: network1, AmountIn: (*hexutil.Big)(big.NewInt(50))},
{FromChain: network2, AmountIn: (*hexutil.Big)(big.NewInt(100))},
@ -802,7 +803,7 @@ func TestFilterCapacityValidation(t *testing.T) {
},
{
name: "Single route with inappropriately locked amount",
routes: [][]*Path{
routes: []routes.Route{
{
{FromChain: network1, AmountIn: (*hexutil.Big)(big.NewInt(100))},
},
@ -811,11 +812,11 @@ func TestFilterCapacityValidation(t *testing.T) {
fromLockedAmount: map[uint64]*hexutil.Big{
1: (*hexutil.Big)(big.NewInt(50)),
},
expectedRoutes: [][]*Path{},
expectedRoutes: []routes.Route{},
},
{
name: "Single route with insufficient capacity",
routes: [][]*Path{
routes: []routes.Route{
{
{FromChain: network1, AmountIn: (*hexutil.Big)(big.NewInt(50))},
},
@ -824,20 +825,20 @@ func TestFilterCapacityValidation(t *testing.T) {
fromLockedAmount: map[uint64]*hexutil.Big{
1: (*hexutil.Big)(big.NewInt(50)),
},
expectedRoutes: [][]*Path{},
expectedRoutes: []routes.Route{},
},
{
name: "Empty routes",
routes: [][]*Path{},
routes: []routes.Route{},
amountIn: big.NewInt(150),
fromLockedAmount: map[uint64]*hexutil.Big{
1: (*hexutil.Big)(big.NewInt(50)),
},
expectedRoutes: [][]*Path{},
expectedRoutes: []routes.Route{},
},
{
name: "Partial locked amounts",
routes: [][]*Path{
routes: []routes.Route{
{
{FromChain: network1, AmountIn: (*hexutil.Big)(big.NewInt(50))},
{FromChain: network3, AmountIn: (*hexutil.Big)(big.NewInt(100))},
@ -850,7 +851,7 @@ func TestFilterCapacityValidation(t *testing.T) {
2: (*hexutil.Big)(big.NewInt(0)), // Excluded path
3: (*hexutil.Big)(big.NewInt(100)),
},
expectedRoutes: [][]*Path{
expectedRoutes: []routes.Route{
{
{FromChain: network1, AmountIn: (*hexutil.Big)(big.NewInt(50))},
{FromChain: network3, AmountIn: (*hexutil.Big)(big.NewInt(100))},
@ -860,7 +861,7 @@ func TestFilterCapacityValidation(t *testing.T) {
},
{
name: "Mixed networks with sufficient capacity",
routes: [][]*Path{
routes: []routes.Route{
{
{FromChain: network1, AmountIn: (*hexutil.Big)(big.NewInt(100))},
{FromChain: network3, AmountIn: (*hexutil.Big)(big.NewInt(200))},
@ -871,7 +872,7 @@ func TestFilterCapacityValidation(t *testing.T) {
1: (*hexutil.Big)(big.NewInt(100)),
3: (*hexutil.Big)(big.NewInt(200)),
},
expectedRoutes: [][]*Path{
expectedRoutes: []routes.Route{
{
{FromChain: network1, AmountIn: (*hexutil.Big)(big.NewInt(100))},
{FromChain: network3, AmountIn: (*hexutil.Big)(big.NewInt(200))},
@ -880,7 +881,7 @@ func TestFilterCapacityValidation(t *testing.T) {
},
{
name: "Mixed networks with insufficient capacity",
routes: [][]*Path{
routes: []routes.Route{
{
{FromChain: network1, AmountIn: (*hexutil.Big)(big.NewInt(100))},
{FromChain: network3, AmountIn: (*hexutil.Big)(big.NewInt(100))},
@ -891,7 +892,7 @@ func TestFilterCapacityValidation(t *testing.T) {
1: (*hexutil.Big)(big.NewInt(50)),
3: (*hexutil.Big)(big.NewInt(100)),
},
expectedRoutes: [][]*Path{},
expectedRoutes: []routes.Route{},
},
}
@ -908,50 +909,50 @@ func TestFilterCapacityValidation(t *testing.T) {
func TestFilterRoutes(t *testing.T) {
tests := []struct {
name string
routes [][]*Path
routes []routes.Route
amountIn *big.Int
fromLockedAmount map[uint64]*hexutil.Big
expectedRoutes [][]*Path
expectedRoutes []routes.Route
}{
{
name: "Empty fromLockedAmount and routes don't match amountIn",
routes: [][]*Path{
routes: []routes.Route{
{pathC1A1, pathC2A2},
{pathC3A3, pathC4A4},
},
amountIn: big.NewInt(150),
fromLockedAmount: map[uint64]*hexutil.Big{},
expectedRoutes: [][]*Path{},
expectedRoutes: []routes.Route{},
},
{
name: "Empty fromLockedAmount and sigle route match amountIn",
routes: [][]*Path{
routes: []routes.Route{
{pathC1A1, pathC2A2},
{pathC3A3, pathC4A4},
},
amountIn: big.NewInt(300),
fromLockedAmount: map[uint64]*hexutil.Big{},
expectedRoutes: [][]*Path{
expectedRoutes: []routes.Route{
{pathC1A1, pathC2A2},
},
},
{
name: "Empty fromLockedAmount and more routes match amountIn",
routes: [][]*Path{
routes: []routes.Route{
{pathC1A1, pathC2A2},
{pathC3A3, pathC4A4},
{pathC1A1, pathC2A1, pathC3A1},
},
amountIn: big.NewInt(300),
fromLockedAmount: map[uint64]*hexutil.Big{},
expectedRoutes: [][]*Path{
expectedRoutes: []routes.Route{
{pathC1A1, pathC2A2},
{pathC1A1, pathC2A1, pathC3A1},
},
},
{
name: "All paths appear in fromLockedAmount but not within a single route",
routes: [][]*Path{
routes: []routes.Route{
{pathC1A1, pathC3A3},
{pathC2A2, pathC4A4},
},
@ -962,11 +963,11 @@ func TestFilterRoutes(t *testing.T) {
3: &amount3,
4: &amount4,
},
expectedRoutes: [][]*Path{},
expectedRoutes: []routes.Route{},
},
{
name: "Mixed valid and invalid routes I",
routes: [][]*Path{
routes: []routes.Route{
{pathC1A1, pathC2A2},
{pathC2A2, pathC3A3},
{pathC1A1, pathC4A4},
@ -977,13 +978,13 @@ func TestFilterRoutes(t *testing.T) {
1: &amount1,
2: &amount2,
},
expectedRoutes: [][]*Path{
expectedRoutes: []routes.Route{
{pathC1A1, pathC2A2},
},
},
{
name: "Mixed valid and invalid routes II",
routes: [][]*Path{
routes: []routes.Route{
{pathC1A1, pathC2A2},
{pathC2A2, pathC3A3},
{pathC1A1, pathC4A4},
@ -993,14 +994,14 @@ func TestFilterRoutes(t *testing.T) {
fromLockedAmount: map[uint64]*hexutil.Big{
1: &amount1,
},
expectedRoutes: [][]*Path{
expectedRoutes: []routes.Route{
{pathC1A1, pathC2A2},
{pathC1A1, pathC2A1, pathC3A1},
},
},
{
name: "All invalid routes",
routes: [][]*Path{
routes: []routes.Route{
{pathC2A2, pathC3A3},
{pathC4A4, pathC5A5},
},
@ -1008,11 +1009,11 @@ func TestFilterRoutes(t *testing.T) {
fromLockedAmount: map[uint64]*hexutil.Big{
1: &amount1,
},
expectedRoutes: [][]*Path{},
expectedRoutes: []routes.Route{},
},
{
name: "Single valid route",
routes: [][]*Path{
routes: []routes.Route{
{pathC1A1, pathC3A3},
{pathC2A2, pathC3A3},
},
@ -1021,13 +1022,13 @@ func TestFilterRoutes(t *testing.T) {
1: &amount1,
3: &amount3,
},
expectedRoutes: [][]*Path{
expectedRoutes: []routes.Route{
{pathC1A1, pathC3A3},
},
},
{
name: "Route with mixed valid and invalid paths I",
routes: [][]*Path{
routes: []routes.Route{
{pathC1A1, pathC2A2, pathC3A3},
},
amountIn: big.NewInt(300),
@ -1035,11 +1036,11 @@ func TestFilterRoutes(t *testing.T) {
1: &amount1,
2: &amount0, // This path should be filtered out due to being excluded via a zero amount
},
expectedRoutes: [][]*Path{},
expectedRoutes: []routes.Route{},
},
{
name: "Route with mixed valid and invalid paths II",
routes: [][]*Path{
routes: []routes.Route{
{pathC1A1, pathC3A3},
},
amountIn: big.NewInt(400),
@ -1047,13 +1048,13 @@ func TestFilterRoutes(t *testing.T) {
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: [][]*Path{
expectedRoutes: []routes.Route{
{pathC1A1, pathC3A3},
},
},
{
name: "Route with mixed valid and invalid paths III",
routes: [][]*Path{
routes: []routes.Route{
{pathC1A1, pathC3A3},
{pathC1A1, pathC3A2, pathC4A1},
},
@ -1062,7 +1063,7 @@ func TestFilterRoutes(t *testing.T) {
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: [][]*Path{
expectedRoutes: []routes.Route{
{pathC1A1, pathC3A3},
{pathC1A1, pathC3A2, pathC4A1},
},

View File

@ -21,8 +21,10 @@ import (
walletCommon "github.com/status-im/status-go/services/wallet/common"
"github.com/status-im/status-go/services/wallet/market"
"github.com/status-im/status-go/services/wallet/requests"
"github.com/status-im/status-go/services/wallet/responses"
"github.com/status-im/status-go/services/wallet/router/fees"
"github.com/status-im/status-go/services/wallet/router/pathprocessor"
"github.com/status-im/status-go/services/wallet/router/routes"
"github.com/status-im/status-go/services/wallet/router/sendtype"
"github.com/status-im/status-go/services/wallet/token"
walletToken "github.com/status-im/status-go/services/wallet/token"
@ -54,21 +56,12 @@ type ProcessorError struct {
type SuggestedRoutes struct {
Uuid string
Best []*Path
Candidates []*Path
Best routes.Route
Candidates routes.Route
TokenPrice float64
NativeChainTokenPrice float64
}
type SuggestedRoutesResponse struct {
Uuid string `json:"Uuid"`
Best []*Path `json:"Best,omitempty"`
Candidates []*Path `json:"Candidates,omitempty"`
TokenPrice *float64 `json:"TokenPrice,omitempty"`
NativeChainTokenPrice *float64 `json:"NativeChainTokenPrice,omitempty"`
ErrorResponse *errors.ErrorResponse `json:"ErrorResponse,omitempty"`
}
type Router struct {
rpcClient *rpc.Client
tokenManager *token.Manager
@ -121,11 +114,11 @@ func (r *Router) GetPathProcessors() map[string]pathprocessor.PathProcessor {
func newSuggestedRoutes(
uuid string,
amountIn *big.Int,
candidates []*Path,
candidates routes.Route,
fromLockedAmount map[uint64]*hexutil.Big,
tokenPrice float64,
nativeChainTokenPrice float64,
) (*SuggestedRoutes, [][]*Path) {
) (*SuggestedRoutes, []routes.Route) {
suggestedRoutes := &SuggestedRoutes{
Uuid: uuid,
Candidates: candidates,
@ -136,11 +129,11 @@ func newSuggestedRoutes(
return suggestedRoutes, nil
}
node := &Node{
node := &routes.Node{
Path: nil,
Children: buildGraph(amountIn, candidates, 0, []uint64{}),
Children: routes.BuildGraph(amountIn, candidates, 0, []uint64{}),
}
allRoutes := node.buildAllRoutes()
allRoutes := node.BuildAllRoutes()
allRoutes = filterRoutes(allRoutes, amountIn, fromLockedAmount)
if len(allRoutes) > 0 {
@ -158,7 +151,7 @@ func (r *Router) SuggestedRoutesAsync(input *requests.RouteInputParams) {
r.scheduler.Enqueue(routerTask, func(ctx context.Context) (interface{}, error) {
return r.SuggestedRoutes(ctx, input)
}, func(result interface{}, taskType async.TaskType, err error) {
routesResponse := SuggestedRoutesResponse{
routesResponse := responses.RouterSuggestedRoutes{
Uuid: input.Uuid,
}
@ -511,7 +504,7 @@ func (r *Router) getSelectedChains(input *requests.RouteInputParams) (selectedFr
}
func (r *Router) resolveCandidates(ctx context.Context, input *requests.RouteInputParams, selectedFromChains []*params.Network,
selectedToChains []*params.Network, balanceMap map[string]*big.Int) (candidates []*Path, processorErrors []*ProcessorError, err error) {
selectedToChains []*params.Network, balanceMap map[string]*big.Int) (candidates routes.Route, processorErrors []*ProcessorError, err error) {
var (
testsMode = input.TestsMode && input.TestParams != nil
group = async.NewAtomicGroup(ctx)
@ -533,7 +526,7 @@ func (r *Router) resolveCandidates(ctx context.Context, input *requests.RouteInp
})
}
appendPathFn := func(path *Path) {
appendPathFn := func(path *routes.Path) {
mu.Lock()
defer mu.Unlock()
candidates = append(candidates, path)
@ -731,7 +724,7 @@ func (r *Router) resolveCandidates(ctx context.Context, input *requests.RouteInp
requiredNativeBalance.Add(requiredNativeBalance, ethTotalFees)
}
appendPathFn(&Path{
appendPathFn(&routes.Path{
ProcessorName: pProcessor.Name(),
FromChain: network,
ToChain: dest,
@ -767,9 +760,9 @@ func (r *Router) resolveCandidates(ctx context.Context, input *requests.RouteInp
EstimatedTime: estimatedTime,
subtractFees: amountOption.subtractFees,
requiredTokenBalance: requiredTokenBalance,
requiredNativeBalance: requiredNativeBalance,
SubtractFees: amountOption.subtractFees,
RequiredTokenBalance: requiredTokenBalance,
RequiredNativeBalance: requiredNativeBalance,
})
}
}
@ -788,7 +781,7 @@ func (r *Router) resolveCandidates(ctx context.Context, input *requests.RouteInp
return candidates, processorErrors, nil
}
func (r *Router) checkBalancesForTheBestRoute(ctx context.Context, bestRoute []*Path, balanceMap map[string]*big.Int) (hasPositiveBalance bool, err error) {
func (r *Router) checkBalancesForTheBestRoute(ctx context.Context, bestRoute routes.Route, balanceMap map[string]*big.Int) (hasPositiveBalance bool, err error) {
balanceMapCopy := walletCommon.CopyMapGeneric(balanceMap, func(v interface{}) interface{} {
return new(big.Int).Set(v.(*big.Int))
}).(map[string]*big.Int)
@ -811,16 +804,16 @@ func (r *Router) checkBalancesForTheBestRoute(ctx context.Context, bestRoute []*
}
}
if path.requiredTokenBalance != nil && path.requiredTokenBalance.Cmp(pathprocessor.ZeroBigIntValue) > 0 {
if path.RequiredTokenBalance != nil && path.RequiredTokenBalance.Cmp(pathprocessor.ZeroBigIntValue) > 0 {
if tokenBalance, ok := balanceMapCopy[tokenKey]; ok {
if tokenBalance.Cmp(path.requiredTokenBalance) == -1 {
if tokenBalance.Cmp(path.RequiredTokenBalance) == -1 {
err := &errors.ErrorResponse{
Code: ErrNotEnoughTokenBalance.Code,
Details: fmt.Sprintf(ErrNotEnoughTokenBalance.Details, path.FromToken.Symbol, path.FromChain.ChainID),
}
return hasPositiveBalance, err
}
balanceMapCopy[tokenKey].Sub(tokenBalance, path.requiredTokenBalance)
balanceMapCopy[tokenKey].Sub(tokenBalance, path.RequiredTokenBalance)
} else {
return hasPositiveBalance, ErrTokenNotFound
}
@ -828,14 +821,14 @@ func (r *Router) checkBalancesForTheBestRoute(ctx context.Context, bestRoute []*
ethKey := makeBalanceKey(path.FromChain.ChainID, pathprocessor.EthSymbol)
if nativeBalance, ok := balanceMapCopy[ethKey]; ok {
if nativeBalance.Cmp(path.requiredNativeBalance) == -1 {
if nativeBalance.Cmp(path.RequiredNativeBalance) == -1 {
err := &errors.ErrorResponse{
Code: ErrNotEnoughNativeBalance.Code,
Details: fmt.Sprintf(ErrNotEnoughNativeBalance.Details, pathprocessor.EthSymbol, path.FromChain.ChainID),
}
return hasPositiveBalance, err
}
balanceMapCopy[ethKey].Sub(nativeBalance, path.requiredNativeBalance)
balanceMapCopy[ethKey].Sub(nativeBalance, path.RequiredNativeBalance)
} else {
return hasPositiveBalance, ErrNativeTokenNotFound
}
@ -844,7 +837,7 @@ func (r *Router) checkBalancesForTheBestRoute(ctx context.Context, bestRoute []*
return hasPositiveBalance, nil
}
func (r *Router) resolveRoutes(ctx context.Context, input *requests.RouteInputParams, candidates []*Path, balanceMap map[string]*big.Int) (suggestedRoutes *SuggestedRoutes, err error) {
func (r *Router) resolveRoutes(ctx context.Context, input *requests.RouteInputParams, candidates routes.Route, balanceMap map[string]*big.Int) (suggestedRoutes *SuggestedRoutes, err error) {
var prices map[string]float64
if input.TestsMode {
prices = input.TestParams.TokenPrices
@ -858,7 +851,7 @@ func (r *Router) resolveRoutes(ctx context.Context, input *requests.RouteInputPa
tokenPrice := prices[input.TokenID]
nativeTokenPrice := prices[pathprocessor.EthSymbol]
var allRoutes [][]*Path
var allRoutes []routes.Route
suggestedRoutes, allRoutes = newSuggestedRoutes(input.Uuid, input.AmountIn.ToInt(), candidates, input.FromLockedAmount, tokenPrice, nativeTokenPrice)
defer func() {
@ -872,13 +865,13 @@ func (r *Router) resolveRoutes(ctx context.Context, input *requests.RouteInputPa
}()
var (
bestRoute []*Path
lastBestRouteWithPositiveBalance []*Path
bestRoute routes.Route
lastBestRouteWithPositiveBalance routes.Route
lastBestRouteErr error
)
for len(allRoutes) > 0 {
bestRoute = findBest(allRoutes, tokenPrice, nativeTokenPrice)
bestRoute = routes.FindBestRoute(allRoutes, tokenPrice, nativeTokenPrice)
var hasPositiveBalance bool
hasPositiveBalance, err = r.checkBalancesForTheBestRoute(ctx, bestRoute, balanceMap)
@ -913,7 +906,7 @@ func (r *Router) resolveRoutes(ctx context.Context, input *requests.RouteInputPa
if len(bestRoute) > 0 {
// 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 bestRoute {
if path.subtractFees && path.FromToken.IsNative() {
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())

View File

@ -10,7 +10,9 @@ import (
"github.com/status-im/status-go/appdatabase"
"github.com/status-im/status-go/params"
"github.com/status-im/status-go/rpc"
"github.com/status-im/status-go/services/wallet/responses"
"github.com/status-im/status-go/services/wallet/router/pathprocessor"
"github.com/status-im/status-go/services/wallet/router/routes"
"github.com/status-im/status-go/signal"
"github.com/status-im/status-go/t/helpers"
@ -58,7 +60,7 @@ func amountOptionsMapsEqual(map1, map2 map[uint64][]amountOption) bool {
return true
}
func assertPathsEqual(t *testing.T, expected, actual []*Path) {
func assertPathsEqual(t *testing.T, expected, actual routes.Route) {
assert.Equal(t, len(expected), len(actual))
if len(expected) == 0 {
return
@ -124,19 +126,19 @@ func setupRouter(t *testing.T) (*Router, func()) {
return router, cleanTmpDb
}
type suggestedRoutesResponseEnvelope struct {
Type string `json:"type"`
Routes SuggestedRoutesResponse `json:"event"`
type routerSuggestedRoutesEnvelope struct {
Type string `json:"type"`
Routes responses.RouterSuggestedRoutes `json:"event"`
}
func setupSignalHandler(t *testing.T) (chan SuggestedRoutesResponse, func()) {
suggestedRoutesCh := make(chan SuggestedRoutesResponse)
func setupSignalHandler(t *testing.T) (chan responses.RouterSuggestedRoutes, func()) {
suggestedRoutesCh := make(chan responses.RouterSuggestedRoutes)
signalHandler := signal.MobileSignalHandler(func(data []byte) {
var envelope signal.Envelope
err := json.Unmarshal(data, &envelope)
assert.NoError(t, err)
if envelope.Type == string(signal.SuggestedRoutes) {
var response suggestedRoutesResponseEnvelope
var response routerSuggestedRoutesEnvelope
err := json.Unmarshal(data, &response)
assert.NoError(t, err)

View File

@ -15,6 +15,7 @@ import (
"github.com/status-im/status-go/services/wallet/requests"
"github.com/status-im/status-go/services/wallet/router/fees"
"github.com/status-im/status-go/services/wallet/router/pathprocessor"
"github.com/status-im/status-go/services/wallet/router/routes"
"github.com/status-im/status-go/services/wallet/router/sendtype"
"github.com/status-im/status-go/services/wallet/token"
)
@ -205,7 +206,7 @@ var defaultNetworks = []params.Network{
type normalTestParams struct {
name string
input *requests.RouteInputParams
expectedCandidates []*Path
expectedCandidates routes.Route
expectedError *errors.ErrorResponse
}
@ -249,7 +250,7 @@ func getNormalTestParamsList() []normalTestParams {
Code: errors.GenericErrorCode,
Details: fmt.Sprintf("failed with 50000000 gas: insufficient funds for gas * price + value: address %s", common.HexToAddress("0x1")),
},
expectedCandidates: []*Path{},
expectedCandidates: routes.Route{},
},
{
name: "ETH transfer - No Specific FromChain - No Specific ToChain - 0 AmountIn",
@ -278,7 +279,7 @@ func getNormalTestParamsList() []normalTestParams {
ApprovalL1Fee: testApprovalL1Fee,
},
},
expectedCandidates: []*Path{
expectedCandidates: routes.Route{
{
ProcessorName: pathprocessor.ProcessorTransferName,
FromChain: &mainnet,
@ -326,7 +327,7 @@ func getNormalTestParamsList() []normalTestParams {
ApprovalL1Fee: testApprovalL1Fee,
},
},
expectedCandidates: []*Path{
expectedCandidates: routes.Route{
{
ProcessorName: pathprocessor.ProcessorTransferName,
FromChain: &mainnet,
@ -412,7 +413,7 @@ func getNormalTestParamsList() []normalTestParams {
ApprovalL1Fee: testApprovalL1Fee,
},
},
expectedCandidates: []*Path{
expectedCandidates: routes.Route{
{
ProcessorName: pathprocessor.ProcessorTransferName,
FromChain: &mainnet,
@ -462,7 +463,7 @@ func getNormalTestParamsList() []normalTestParams {
ApprovalL1Fee: testApprovalL1Fee,
},
},
expectedCandidates: []*Path{
expectedCandidates: routes.Route{
{
ProcessorName: pathprocessor.ProcessorTransferName,
FromChain: &optimism,
@ -531,7 +532,7 @@ func getNormalTestParamsList() []normalTestParams {
ApprovalL1Fee: testApprovalL1Fee,
},
},
expectedCandidates: []*Path{
expectedCandidates: routes.Route{
{
ProcessorName: pathprocessor.ProcessorTransferName,
FromChain: &arbitrum,
@ -581,7 +582,7 @@ func getNormalTestParamsList() []normalTestParams {
ApprovalL1Fee: testApprovalL1Fee,
},
},
expectedCandidates: []*Path{
expectedCandidates: routes.Route{
{
ProcessorName: pathprocessor.ProcessorTransferName,
FromChain: &optimism,
@ -650,7 +651,7 @@ func getNormalTestParamsList() []normalTestParams {
ApprovalL1Fee: testApprovalL1Fee,
},
},
expectedCandidates: []*Path{
expectedCandidates: routes.Route{
{
ProcessorName: pathprocessor.ProcessorTransferName,
FromChain: &arbitrum,
@ -689,7 +690,7 @@ func getNormalTestParamsList() []normalTestParams {
ApprovalL1Fee: testApprovalL1Fee,
},
},
expectedCandidates: []*Path{
expectedCandidates: routes.Route{
{
ProcessorName: pathprocessor.ProcessorBridgeHopName,
FromChain: &arbitrum,
@ -728,7 +729,7 @@ func getNormalTestParamsList() []normalTestParams {
ApprovalL1Fee: testApprovalL1Fee,
},
},
expectedCandidates: []*Path{
expectedCandidates: routes.Route{
{
ProcessorName: pathprocessor.ProcessorTransferName,
FromChain: &arbitrum,
@ -767,7 +768,7 @@ func getNormalTestParamsList() []normalTestParams {
ApprovalL1Fee: testApprovalL1Fee,
},
},
expectedCandidates: []*Path{
expectedCandidates: routes.Route{
{
ProcessorName: pathprocessor.ProcessorTransferName,
FromChain: &mainnet,
@ -824,7 +825,7 @@ func getNormalTestParamsList() []normalTestParams {
ApprovalL1Fee: testApprovalL1Fee,
},
},
expectedCandidates: []*Path{
expectedCandidates: routes.Route{
{
ProcessorName: pathprocessor.ProcessorBridgeHopName,
FromChain: &arbitrum,
@ -865,7 +866,7 @@ func getNormalTestParamsList() []normalTestParams {
},
},
expectedError: ErrNoBestRouteFound,
expectedCandidates: []*Path{},
expectedCandidates: routes.Route{},
},
{
name: "ETH transfer - No Specific FromChain - No Specific ToChain - Single Chain LockedAmount",
@ -898,7 +899,7 @@ func getNormalTestParamsList() []normalTestParams {
ApprovalL1Fee: testApprovalL1Fee,
},
},
expectedCandidates: []*Path{
expectedCandidates: routes.Route{
{
ProcessorName: pathprocessor.ProcessorTransferName,
FromChain: &mainnet,
@ -997,7 +998,7 @@ func getNormalTestParamsList() []normalTestParams {
ApprovalL1Fee: testApprovalL1Fee,
},
},
expectedCandidates: []*Path{
expectedCandidates: routes.Route{
{
ProcessorName: pathprocessor.ProcessorTransferName,
FromChain: &optimism,
@ -1053,7 +1054,7 @@ func getNormalTestParamsList() []normalTestParams {
ApprovalL1Fee: testApprovalL1Fee,
},
},
expectedCandidates: []*Path{
expectedCandidates: routes.Route{
{
ProcessorName: pathprocessor.ProcessorTransferName,
FromChain: &mainnet,
@ -1152,7 +1153,7 @@ func getNormalTestParamsList() []normalTestParams {
ApprovalL1Fee: testApprovalL1Fee,
},
},
expectedCandidates: []*Path{
expectedCandidates: routes.Route{
{
ProcessorName: pathprocessor.ProcessorTransferName,
FromChain: &mainnet,
@ -1252,7 +1253,7 @@ func getNormalTestParamsList() []normalTestParams {
},
},
expectedError: requests.ErrLockedAmountLessThanSendAmountAllNetworks,
expectedCandidates: []*Path{},
expectedCandidates: routes.Route{},
},
{
name: "ETH transfer - No Specific FromChain - No Specific ToChain - LockedAmount exceeds sending amount",
@ -1287,7 +1288,7 @@ func getNormalTestParamsList() []normalTestParams {
},
},
expectedError: requests.ErrLockedAmountExceedsTotalSendAmount,
expectedCandidates: []*Path{},
expectedCandidates: routes.Route{},
},
{
name: "ERC20 transfer - No Specific FromChain - No Specific ToChain",
@ -1317,7 +1318,7 @@ func getNormalTestParamsList() []normalTestParams {
ApprovalL1Fee: testApprovalL1Fee,
},
},
expectedCandidates: []*Path{
expectedCandidates: routes.Route{
{
ProcessorName: pathprocessor.ProcessorTransferName,
FromChain: &mainnet,
@ -1403,7 +1404,7 @@ func getNormalTestParamsList() []normalTestParams {
ApprovalL1Fee: testApprovalL1Fee,
},
},
expectedCandidates: []*Path{
expectedCandidates: routes.Route{
{
ProcessorName: pathprocessor.ProcessorTransferName,
FromChain: &mainnet,
@ -1453,7 +1454,7 @@ func getNormalTestParamsList() []normalTestParams {
ApprovalL1Fee: testApprovalL1Fee,
},
},
expectedCandidates: []*Path{
expectedCandidates: routes.Route{
{
ProcessorName: pathprocessor.ProcessorTransferName,
FromChain: &optimism,
@ -1521,7 +1522,7 @@ func getNormalTestParamsList() []normalTestParams {
ApprovalL1Fee: testApprovalL1Fee,
},
},
expectedCandidates: []*Path{
expectedCandidates: routes.Route{
{
ProcessorName: pathprocessor.ProcessorTransferName,
FromChain: &arbitrum,
@ -1571,7 +1572,7 @@ func getNormalTestParamsList() []normalTestParams {
ApprovalL1Fee: testApprovalL1Fee,
},
},
expectedCandidates: []*Path{
expectedCandidates: routes.Route{
{
ProcessorName: pathprocessor.ProcessorTransferName,
FromChain: &optimism,
@ -1640,7 +1641,7 @@ func getNormalTestParamsList() []normalTestParams {
ApprovalL1Fee: testApprovalL1Fee,
},
},
expectedCandidates: []*Path{
expectedCandidates: routes.Route{
{
ProcessorName: pathprocessor.ProcessorTransferName,
FromChain: &arbitrum,
@ -1679,7 +1680,7 @@ func getNormalTestParamsList() []normalTestParams {
ApprovalL1Fee: testApprovalL1Fee,
},
},
expectedCandidates: []*Path{
expectedCandidates: routes.Route{
{
ProcessorName: pathprocessor.ProcessorBridgeHopName,
FromChain: &arbitrum,
@ -1718,7 +1719,7 @@ func getNormalTestParamsList() []normalTestParams {
ApprovalL1Fee: testApprovalL1Fee,
},
},
expectedCandidates: []*Path{
expectedCandidates: routes.Route{
{
ProcessorName: pathprocessor.ProcessorTransferName,
FromChain: &arbitrum,
@ -1757,7 +1758,7 @@ func getNormalTestParamsList() []normalTestParams {
ApprovalL1Fee: testApprovalL1Fee,
},
},
expectedCandidates: []*Path{
expectedCandidates: routes.Route{
{
ProcessorName: pathprocessor.ProcessorTransferName,
FromChain: &mainnet,
@ -1814,7 +1815,7 @@ func getNormalTestParamsList() []normalTestParams {
ApprovalL1Fee: testApprovalL1Fee,
},
},
expectedCandidates: []*Path{
expectedCandidates: routes.Route{
{
ProcessorName: pathprocessor.ProcessorBridgeHopName,
FromChain: &arbitrum,
@ -1854,7 +1855,7 @@ func getNormalTestParamsList() []normalTestParams {
},
},
expectedError: ErrNoBestRouteFound,
expectedCandidates: []*Path{},
expectedCandidates: routes.Route{},
},
{
name: "ERC20 transfer - All FromChains - No Locked Amount - Enough Token Balance Across All Chains",
@ -1884,7 +1885,7 @@ func getNormalTestParamsList() []normalTestParams {
ApprovalL1Fee: testApprovalL1Fee,
},
},
expectedCandidates: []*Path{
expectedCandidates: routes.Route{
{
ProcessorName: pathprocessor.ProcessorTransferName,
FromChain: &mainnet,
@ -2083,7 +2084,7 @@ func getNormalTestParamsList() []normalTestParams {
ApprovalL1Fee: testApprovalL1Fee,
},
},
expectedCandidates: []*Path{
expectedCandidates: routes.Route{
{
ProcessorName: pathprocessor.ProcessorBridgeHopName,
FromChain: &mainnet,
@ -2151,7 +2152,7 @@ func getNormalTestParamsList() []normalTestParams {
ApprovalL1Fee: testApprovalL1Fee,
},
},
expectedCandidates: []*Path{
expectedCandidates: routes.Route{
{
ProcessorName: pathprocessor.ProcessorBridgeHopName,
FromChain: &optimism,
@ -2195,7 +2196,7 @@ func getNormalTestParamsList() []normalTestParams {
ApprovalL1Fee: testApprovalL1Fee,
},
},
expectedCandidates: []*Path{
expectedCandidates: routes.Route{
{
ProcessorName: pathprocessor.ProcessorBridgeHopName,
FromChain: &mainnet,
@ -2251,7 +2252,7 @@ func getNormalTestParamsList() []normalTestParams {
ApprovalL1Fee: testApprovalL1Fee,
},
},
expectedCandidates: []*Path{
expectedCandidates: routes.Route{
{
ProcessorName: pathprocessor.ProcessorBridgeHopName,
FromChain: &arbitrum,
@ -2295,7 +2296,7 @@ func getNormalTestParamsList() []normalTestParams {
ApprovalL1Fee: testApprovalL1Fee,
},
},
expectedCandidates: []*Path{
expectedCandidates: routes.Route{
{
ProcessorName: pathprocessor.ProcessorBridgeHopName,
FromChain: &optimism,
@ -2353,7 +2354,7 @@ func getNormalTestParamsList() []normalTestParams {
},
},
expectedError: ErrNoBestRouteFound,
expectedCandidates: []*Path{},
expectedCandidates: routes.Route{},
},
{
name: "Bridge - Specific Single FromChain - Specific Single ToChain - Different Chains",
@ -2385,7 +2386,7 @@ func getNormalTestParamsList() []normalTestParams {
ApprovalL1Fee: testApprovalL1Fee,
},
},
expectedCandidates: []*Path{
expectedCandidates: routes.Route{
{
ProcessorName: pathprocessor.ProcessorBridgeHopName,
FromChain: &arbitrum,
@ -2425,7 +2426,7 @@ func getNormalTestParamsList() []normalTestParams {
},
},
expectedError: ErrNoBestRouteFound,
expectedCandidates: []*Path{},
expectedCandidates: routes.Route{},
},
{
name: "Bridge - Specific Multiple FromChain - Specific Multiple ToChain - Multiple Common Chains",
@ -2457,7 +2458,7 @@ func getNormalTestParamsList() []normalTestParams {
ApprovalL1Fee: testApprovalL1Fee,
},
},
expectedCandidates: []*Path{
expectedCandidates: routes.Route{
{
ProcessorName: pathprocessor.ProcessorBridgeHopName,
FromChain: &mainnet,
@ -2502,7 +2503,7 @@ func getNormalTestParamsList() []normalTestParams {
ApprovalL1Fee: testApprovalL1Fee,
},
},
expectedCandidates: []*Path{
expectedCandidates: routes.Route{
{
ProcessorName: pathprocessor.ProcessorBridgeHopName,
FromChain: &arbitrum,
@ -2542,7 +2543,7 @@ func getNormalTestParamsList() []normalTestParams {
},
},
expectedError: ErrNoBestRouteFound,
expectedCandidates: []*Path{},
expectedCandidates: routes.Route{},
},
{
name: "ETH transfer - Not Enough Native Balance",
@ -2577,7 +2578,7 @@ func getNormalTestParamsList() []normalTestParams {
Code: ErrNotEnoughNativeBalance.Code,
Details: fmt.Sprintf(ErrNotEnoughNativeBalance.Details, pathprocessor.EthSymbol, walletCommon.EthereumMainnet),
},
expectedCandidates: []*Path{
expectedCandidates: routes.Route{
{
ProcessorName: pathprocessor.ProcessorBridgeHopName,
FromChain: &mainnet,
@ -2619,7 +2620,7 @@ func getNormalTestParamsList() []normalTestParams {
Code: ErrNotEnoughTokenBalance.Code,
Details: fmt.Sprintf(ErrNotEnoughTokenBalance.Details, pathprocessor.UsdcSymbol, walletCommon.EthereumMainnet),
},
expectedCandidates: []*Path{
expectedCandidates: routes.Route{
{
ProcessorName: pathprocessor.ProcessorBridgeHopName,
FromChain: &mainnet,
@ -2659,7 +2660,7 @@ func getNormalTestParamsList() []normalTestParams {
},
},
expectedError: ErrLowAmountInForHopBridge,
expectedCandidates: []*Path{
expectedCandidates: routes.Route{
{
ProcessorName: pathprocessor.ProcessorBridgeHopName,
FromChain: &arbitrum,
@ -2674,8 +2675,8 @@ func getNormalTestParamsList() []normalTestParams {
type noBalanceTestParams struct {
name string
input *requests.RouteInputParams
expectedCandidates []*Path
expectedBest []*Path
expectedCandidates routes.Route
expectedBest routes.Route
expectedError *errors.ErrorResponse
}
@ -2750,14 +2751,14 @@ func getNoBalanceTestParamsList() []noBalanceTestParams {
Code: ErrNotEnoughNativeBalance.Code,
Details: fmt.Sprintf(ErrNotEnoughNativeBalance.Details, pathprocessor.EthSymbol, walletCommon.OptimismMainnet),
},
expectedCandidates: []*Path{
expectedCandidates: routes.Route{
{
ProcessorName: pathprocessor.ProcessorTransferName,
FromChain: &optimism,
ToChain: &optimism,
ApprovalRequired: false,
requiredTokenBalance: big.NewInt(testAmount100USDC),
requiredNativeBalance: big.NewInt((testBaseFee + testPriorityFeeLow) * testApprovalGasEstimation),
RequiredTokenBalance: big.NewInt(testAmount100USDC),
RequiredNativeBalance: big.NewInt((testBaseFee + testPriorityFeeLow) * testApprovalGasEstimation),
},
},
},
@ -2837,7 +2838,7 @@ func getNoBalanceTestParamsList() []noBalanceTestParams {
Code: ErrNotEnoughNativeBalance.Code,
Details: fmt.Sprintf(ErrNotEnoughNativeBalance.Details, pathprocessor.EthSymbol, walletCommon.ArbitrumMainnet),
},
expectedCandidates: []*Path{
expectedCandidates: routes.Route{
{
ProcessorName: pathprocessor.ProcessorBridgeHopName,
FromChain: &mainnet,
@ -2849,8 +2850,8 @@ func getNoBalanceTestParamsList() []noBalanceTestParams {
FromChain: &optimism,
ToChain: &optimism,
ApprovalRequired: false,
requiredTokenBalance: big.NewInt(testAmount100USDC),
requiredNativeBalance: big.NewInt((testBaseFee + testPriorityFeeLow) * testApprovalGasEstimation),
RequiredTokenBalance: big.NewInt(testAmount100USDC),
RequiredNativeBalance: big.NewInt((testBaseFee + testPriorityFeeLow) * testApprovalGasEstimation),
},
{
ProcessorName: pathprocessor.ProcessorBridgeHopName,
@ -2891,7 +2892,7 @@ func getNoBalanceTestParamsList() []noBalanceTestParams {
ApprovalL1Fee: testApprovalL1Fee,
},
},
expectedCandidates: []*Path{
expectedCandidates: routes.Route{
{
ProcessorName: pathprocessor.ProcessorBridgeHopName,
FromChain: &mainnet,
@ -2903,8 +2904,8 @@ func getNoBalanceTestParamsList() []noBalanceTestParams {
FromChain: &optimism,
ToChain: &optimism,
ApprovalRequired: false,
requiredTokenBalance: big.NewInt(testAmount100USDC),
requiredNativeBalance: big.NewInt((testBaseFee + testPriorityFeeLow) * testApprovalGasEstimation),
RequiredTokenBalance: big.NewInt(testAmount100USDC),
RequiredNativeBalance: big.NewInt((testBaseFee + testPriorityFeeLow) * testApprovalGasEstimation),
},
{
ProcessorName: pathprocessor.ProcessorBridgeHopName,
@ -2913,7 +2914,7 @@ func getNoBalanceTestParamsList() []noBalanceTestParams {
ApprovalRequired: true,
},
},
expectedBest: []*Path{
expectedBest: routes.Route{
{
ProcessorName: pathprocessor.ProcessorBridgeHopName,
FromChain: &arbitrum,

View File

@ -1,4 +1,4 @@
package router
package routes
import (
"math"
@ -8,80 +8,10 @@ import (
"github.com/status-im/status-go/services/wallet/router/pathprocessor"
)
type Graph []*Node
type Route []*Path
type Node struct {
Path *Path
Children Graph
}
func newNode(path *Path) *Node {
return &Node{Path: path, Children: make(Graph, 0)}
}
func buildGraph(AmountIn *big.Int, routes []*Path, level int, sourceChainIDs []uint64) Graph {
graph := make(Graph, 0)
for _, route := range routes {
found := false
for _, chainID := range sourceChainIDs {
if chainID == route.FromChain.ChainID {
found = true
break
}
}
if found {
continue
}
node := newNode(route)
newRoutes := make([]*Path, 0)
for _, r := range routes {
if route.Equal(r) {
continue
}
newRoutes = append(newRoutes, r)
}
newAmountIn := new(big.Int).Sub(AmountIn, route.AmountIn.ToInt())
if newAmountIn.Sign() > 0 {
newSourceChainIDs := make([]uint64, len(sourceChainIDs))
copy(newSourceChainIDs, sourceChainIDs)
newSourceChainIDs = append(newSourceChainIDs, route.FromChain.ChainID)
node.Children = buildGraph(newAmountIn, newRoutes, level+1, newSourceChainIDs)
if len(node.Children) == 0 {
continue
}
}
graph = append(graph, node)
}
return graph
}
func (n Node) buildAllRoutes() [][]*Path {
res := make([][]*Path, 0)
if len(n.Children) == 0 && n.Path != nil {
res = append(res, []*Path{n.Path})
}
for _, node := range n.Children {
for _, route := range node.buildAllRoutes() {
extendedRoute := route
if n.Path != nil {
extendedRoute = append([]*Path{n.Path}, route...)
}
res = append(res, extendedRoute)
}
}
return res
}
func findBest(routes [][]*Path, tokenPrice float64, nativeTokenPrice float64) []*Path {
var best []*Path
func FindBestRoute(routes []Route, tokenPrice float64, nativeTokenPrice float64) Route {
var best Route
bestCost := big.NewFloat(math.Inf(1))
for _, route := range routes {
currentCost := big.NewFloat(0)

View File

@ -0,0 +1,77 @@
package routes
import (
"math/big"
)
type Graph []*Node
type Node struct {
Path *Path
Children Graph
}
func newNode(path *Path) *Node {
return &Node{Path: path, Children: make(Graph, 0)}
}
func BuildGraph(AmountIn *big.Int, route Route, level int, sourceChainIDs []uint64) Graph {
graph := make(Graph, 0)
for _, path := range route {
found := false
for _, chainID := range sourceChainIDs {
if chainID == path.FromChain.ChainID {
found = true
break
}
}
if found {
continue
}
node := newNode(path)
newRoute := make(Route, 0)
for _, p := range route {
if path.Equal(p) {
continue
}
newRoute = append(newRoute, p)
}
newAmountIn := new(big.Int).Sub(AmountIn, path.AmountIn.ToInt())
if newAmountIn.Sign() > 0 {
newSourceChainIDs := make([]uint64, len(sourceChainIDs))
copy(newSourceChainIDs, sourceChainIDs)
newSourceChainIDs = append(newSourceChainIDs, path.FromChain.ChainID)
node.Children = BuildGraph(newAmountIn, newRoute, level+1, newSourceChainIDs)
if len(node.Children) == 0 {
continue
}
}
graph = append(graph, node)
}
return graph
}
func (n Node) BuildAllRoutes() []Route {
res := make([]Route, 0)
if len(n.Children) == 0 && n.Path != nil {
res = append(res, Route{n.Path})
}
for _, node := range n.Children {
for _, route := range node.BuildAllRoutes() {
extendedRoute := route
if n.Path != nil {
extendedRoute = append(Route{n.Path}, route...)
}
res = append(res, extendedRoute)
}
}
return res
}

View File

@ -1,4 +1,4 @@
package router
package routes
import (
"math/big"
@ -46,9 +46,9 @@ type Path struct {
EstimatedTime fees.TransactionEstimation
requiredTokenBalance *big.Int // (in selected token)
requiredNativeBalance *big.Int // (in ETH WEI)
subtractFees bool
RequiredTokenBalance *big.Int // (in selected token)
RequiredNativeBalance *big.Int // (in ETH WEI)
SubtractFees bool
}
func (p *Path) Equal(o *Path) bool {