diff --git a/services/wallet/responses/router_suggested_routes.go b/services/wallet/responses/router_suggested_routes.go new file mode 100644 index 000000000..2ff0dcd2a --- /dev/null +++ b/services/wallet/responses/router_suggested_routes.go @@ -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"` +} diff --git a/services/wallet/router/common.go b/services/wallet/router/common.go index 323fa4da3..ffbca801f 100644 --- a/services/wallet/router/common.go +++ b/services/wallet/router/common.go @@ -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) diff --git a/services/wallet/router/filter.go b/services/wallet/router/filter.go index fe24e27cd..21bc79fa6 100644 --- a/services/wallet/router/filter.go +++ b/services/wallet/router/filter.go @@ -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 { diff --git a/services/wallet/router/filter_test.go b/services/wallet/router/filter_test.go index 74c8e4b94..ae0a974e7 100644 --- a/services/wallet/router/filter_test.go +++ b/services/wallet/router/filter_test.go @@ -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: ¶ms.Network{ChainID: 0}}, {FromChain: ¶ms.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: ¶ms.Network{ChainID: 0}}, {FromChain: ¶ms.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: ¶ms.Network{ChainID: uint64(i + 1)}}, {FromChain: ¶ms.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: ¶ms.Network{ChainID: uint64(i + 1)}}, {FromChain: ¶ms.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}, }, diff --git a/services/wallet/router/router.go b/services/wallet/router/router.go index ec423c546..396cb42f9 100644 --- a/services/wallet/router/router.go +++ b/services/wallet/router/router.go @@ -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()) diff --git a/services/wallet/router/router_test.go b/services/wallet/router/router_test.go index 2f3b6143b..0de7e1aa4 100644 --- a/services/wallet/router/router_test.go +++ b/services/wallet/router/router_test.go @@ -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) diff --git a/services/wallet/router/router_test_data.go b/services/wallet/router/router_test_data.go index eadb40d8e..4e63ab4cb 100644 --- a/services/wallet/router/router_test_data.go +++ b/services/wallet/router/router_test_data.go @@ -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, diff --git a/services/wallet/router/router_graph.go b/services/wallet/router/routes/route.go similarity index 56% rename from services/wallet/router/router_graph.go rename to services/wallet/router/routes/route.go index ceaac314f..eee24b007 100644 --- a/services/wallet/router/router_graph.go +++ b/services/wallet/router/routes/route.go @@ -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) diff --git a/services/wallet/router/routes/router_graph.go b/services/wallet/router/routes/router_graph.go new file mode 100644 index 000000000..0daf34314 --- /dev/null +++ b/services/wallet/router/routes/router_graph.go @@ -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 +} diff --git a/services/wallet/router/router_path.go b/services/wallet/router/routes/router_path.go similarity index 95% rename from services/wallet/router/router_path.go rename to services/wallet/router/routes/router_path.go index d3c0020df..1aa8ee421 100644 --- a/services/wallet/router/router_path.go +++ b/services/wallet/router/routes/router_path.go @@ -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 {