github.com/status-im/status-go@v1.1.0/services/wallet/router/filter.go (about) 1 package router 2 3 import ( 4 "math/big" 5 6 "github.com/ethereum/go-ethereum/common/hexutil" 7 "github.com/status-im/status-go/services/wallet/common" 8 "github.com/status-im/status-go/services/wallet/router/pathprocessor" 9 "github.com/status-im/status-go/services/wallet/router/routes" 10 11 "go.uber.org/zap" 12 ) 13 14 var logger *zap.Logger 15 16 func init() { 17 var err error 18 logger, err = zap.NewProduction() 19 if err != nil { 20 panic(err) 21 } 22 } 23 24 func filterRoutes(routes []routes.Route, amountIn *big.Int, fromLockedAmount map[uint64]*hexutil.Big) []routes.Route { 25 for i := len(routes) - 1; i >= 0; i-- { 26 routeAmount := big.NewInt(0) 27 for _, p := range routes[i] { 28 routeAmount.Add(routeAmount, p.AmountIn.ToInt()) 29 } 30 31 if routeAmount.Cmp(amountIn) == 0 { 32 continue 33 } 34 35 routes = append(routes[:i], routes[i+1:]...) 36 } 37 38 if len(fromLockedAmount) == 0 { 39 return routes 40 } 41 42 routesAfterNetworkCompliance := filterNetworkCompliance(routes, fromLockedAmount) 43 return filterCapacityValidation(routesAfterNetworkCompliance, amountIn, fromLockedAmount) 44 } 45 46 // filterNetworkCompliance performs the first level of filtering based on network inclusion/exclusion criteria. 47 func filterNetworkCompliance(allRoutes []routes.Route, fromLockedAmount map[uint64]*hexutil.Big) []routes.Route { 48 filteredRoutes := make([]routes.Route, 0) 49 if allRoutes == nil || fromLockedAmount == nil { 50 return filteredRoutes 51 } 52 53 fromIncluded, fromExcluded := setupRouteValidationMaps(fromLockedAmount) 54 55 for _, route := range allRoutes { 56 if route == nil { 57 continue 58 } 59 60 // Create fresh copies of the maps for each route check, because they are manipulated 61 if isValidForNetworkCompliance(route, common.CopyMapGeneric(fromIncluded, nil).(map[uint64]bool), common.CopyMapGeneric(fromExcluded, nil).(map[uint64]bool)) { 62 filteredRoutes = append(filteredRoutes, route) 63 } 64 } 65 return filteredRoutes 66 } 67 68 // isValidForNetworkCompliance checks if a route complies with network inclusion/exclusion criteria. 69 func isValidForNetworkCompliance(route routes.Route, fromIncluded, fromExcluded map[uint64]bool) bool { 70 logger.Debug("Initial inclusion/exclusion maps", 71 zap.Any("fromIncluded", fromIncluded), 72 zap.Any("fromExcluded", fromExcluded), 73 ) 74 75 if fromIncluded == nil || fromExcluded == nil { 76 return false 77 } 78 79 for _, path := range route { 80 if path == nil || path.FromChain == nil { 81 logger.Debug("Invalid path", zap.Any("path", path)) 82 return false 83 } 84 if _, ok := fromExcluded[path.FromChain.ChainID]; ok { 85 logger.Debug("Excluded chain ID", zap.Uint64("chainID", path.FromChain.ChainID)) 86 return false 87 } 88 if _, ok := fromIncluded[path.FromChain.ChainID]; ok { 89 fromIncluded[path.FromChain.ChainID] = true 90 } 91 } 92 93 logger.Debug("fromIncluded after loop", zap.Any("fromIncluded", fromIncluded)) 94 95 for chainID, included := range fromIncluded { 96 if !included { 97 logger.Debug("Missing included chain ID", zap.Uint64("chainID", chainID)) 98 return false 99 } 100 } 101 102 return true 103 } 104 105 // setupRouteValidationMaps initializes maps for network inclusion and exclusion based on locked amounts. 106 func setupRouteValidationMaps(fromLockedAmount map[uint64]*hexutil.Big) (map[uint64]bool, map[uint64]bool) { 107 fromIncluded := make(map[uint64]bool) 108 fromExcluded := make(map[uint64]bool) 109 110 for chainID, amount := range fromLockedAmount { 111 if amount.ToInt().Cmp(pathprocessor.ZeroBigIntValue) <= 0 { 112 fromExcluded[chainID] = false 113 } else { 114 fromIncluded[chainID] = false 115 } 116 } 117 return fromIncluded, fromExcluded 118 } 119 120 // filterCapacityValidation performs the second level of filtering based on amount and capacity validation. 121 func filterCapacityValidation(allRoutes []routes.Route, amountIn *big.Int, fromLockedAmount map[uint64]*hexutil.Big) []routes.Route { 122 filteredRoutes := make([]routes.Route, 0) 123 124 for _, route := range allRoutes { 125 if hasSufficientCapacity(route, amountIn, fromLockedAmount) { 126 filteredRoutes = append(filteredRoutes, route) 127 } 128 } 129 return filteredRoutes 130 } 131 132 // hasSufficientCapacity checks if a route has sufficient capacity to handle the required amount. 133 func hasSufficientCapacity(route routes.Route, amountIn *big.Int, fromLockedAmount map[uint64]*hexutil.Big) bool { 134 for _, path := range route { 135 if amount, ok := fromLockedAmount[path.FromChain.ChainID]; ok { 136 if path.AmountIn.ToInt().Cmp(amount.ToInt()) != 0 { 137 logger.Debug("Amount in does not match locked amount", zap.Any("path", path)) 138 return false 139 } 140 requiredAmountIn := new(big.Int).Sub(amountIn, amount.ToInt()) 141 restAmountIn := calculateRestAmountIn(route, path) 142 143 logger.Debug("Checking path", zap.Any("path", path)) 144 logger.Debug("Required amount in", zap.String("requiredAmountIn", requiredAmountIn.String())) 145 logger.Debug("Rest amount in", zap.String("restAmountIn", restAmountIn.String())) 146 147 if restAmountIn.Cmp(requiredAmountIn) < 0 { 148 logger.Debug("Path does not have sufficient capacity", zap.Any("path", path)) 149 return false 150 } 151 } 152 } 153 return true 154 } 155 156 // calculateRestAmountIn calculates the remaining amount in for the route excluding the specified path 157 func calculateRestAmountIn(route routes.Route, excludePath *routes.Path) *big.Int { 158 restAmountIn := big.NewInt(0) 159 for _, path := range route { 160 if path != excludePath { 161 restAmountIn.Add(restAmountIn, path.AmountIn.ToInt()) 162 } 163 } 164 return restAmountIn 165 }