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  }