feat_: the router returns route there is a balance on even that's not the cheapest route

If there are multiple routes across multiple networks, but the user doesn't have a positive balance
on the network which the router initially suggested as the best (cheapest) route, then we are not returning
an error saying there are not enough balance, but instead try to suggest the route on the network where
the user has a positive balance even that's not the cheapest route (it should be the second cheapest route,
but if there are not enough balance on it we proceed with the third cheapest route and so on...).
This commit is contained in:
Sale Djenic 2024-06-30 22:44:21 +02:00 committed by saledjenic
parent 92361d9e20
commit 443cd412f7
2 changed files with 480 additions and 110 deletions

View File

@ -2,6 +2,7 @@ package router
import (
"context"
"fmt"
"math"
"math/big"
"sort"
@ -78,6 +79,17 @@ type routerTestParams struct {
approvalL1Fee uint64
}
func makeTestBalanceKey(chainID uint64, symbol string) string {
return fmt.Sprintf("%d-%s", chainID, symbol)
}
func (rt routerTestParams) getTestBalance(chainID uint64, symbol string) *big.Int {
if val, ok := rt.balanceMap[makeTestBalanceKey(chainID, symbol)]; ok {
return val
}
return big.NewInt(0)
}
type PathV2 struct {
ProcessorName string
FromChain *params.Network // Source chain
@ -141,7 +153,7 @@ func newSuggestedRoutesV2(
fromLockedAmount map[uint64]*hexutil.Big,
tokenPrice float64,
nativeChainTokenPrice float64,
) *SuggestedRoutesV2 {
) (*SuggestedRoutesV2, [][]*PathV2) {
suggestedRoutes := &SuggestedRoutesV2{
Uuid: uuid,
Candidates: candidates,
@ -150,35 +162,17 @@ func newSuggestedRoutesV2(
NativeChainTokenPrice: nativeChainTokenPrice,
}
if len(candidates) == 0 {
return suggestedRoutes
return suggestedRoutes, nil
}
node := &NodeV2{
Path: nil,
Children: buildGraphV2(amountIn, candidates, 0, []uint64{}),
}
routes := node.buildAllRoutesV2()
routes = filterRoutesV2(routes, amountIn, fromLockedAmount)
best := findBestV2(routes, tokenPrice, nativeChainTokenPrice)
allRoutes := node.buildAllRoutesV2()
allRoutes = filterRoutesV2(allRoutes, amountIn, fromLockedAmount)
if len(best) > 0 {
sort.Slice(best, func(i, j int) bool {
return best[i].AmountInLocked
})
rest := new(big.Int).Set(amountIn)
for _, path := range best {
diff := new(big.Int).Sub(rest, path.AmountIn.ToInt())
if diff.Cmp(pathprocessor.ZeroBigIntValue) >= 0 {
path.AmountIn = (*hexutil.Big)(path.AmountIn.ToInt())
} else {
path.AmountIn = (*hexutil.Big)(new(big.Int).Set(rest))
}
rest.Sub(rest, path.AmountIn.ToInt())
}
}
suggestedRoutes.Best = best
return suggestedRoutes
return suggestedRoutes, allRoutes
}
func newNodeV2(path *PathV2) *NodeV2 {
@ -704,6 +698,82 @@ func (r *Router) resolveCandidates(ctx context.Context, input *RouteInputParams)
return candidates, nil
}
func (r *Router) checkBalancesForTheBestRoute(ctx context.Context, bestRoute []*PathV2, input *RouteInputParams) (err error) {
// check the best route for the required balances
for _, path := range bestRoute {
if path.requiredTokenBalance != nil && path.requiredTokenBalance.Cmp(pathprocessor.ZeroBigIntValue) > 0 {
tokenBalance := big.NewInt(1)
if input.testsMode {
tokenBalance = input.testParams.getTestBalance(path.FromChain.ChainID, path.FromToken.Symbol)
} else {
if input.SendType == ERC1155Transfer {
tokenBalance, err = r.getERC1155Balance(ctx, path.FromChain, path.FromToken, input.AddrFrom)
if err != nil {
return errors.CreateErrorResponseFromError(err)
}
} else if input.SendType != ERC721Transfer {
tokenBalance, err = r.getBalance(ctx, path.FromChain, path.FromToken, input.AddrFrom)
if err != nil {
return errors.CreateErrorResponseFromError(err)
}
}
}
if tokenBalance.Cmp(path.requiredTokenBalance) == -1 {
return ErrNotEnoughTokenBalance
}
}
var nativeBalance *big.Int
if input.testsMode {
nativeBalance = input.testParams.getTestBalance(path.FromChain.ChainID, pathprocessor.EthSymbol)
} else {
nativeToken := r.tokenManager.FindToken(path.FromChain, path.FromChain.NativeCurrencySymbol)
if nativeToken == nil {
return ErrNativeTokenNotFound
}
nativeBalance, err = r.getBalance(ctx, path.FromChain, nativeToken, input.AddrFrom)
if err != nil {
return errors.CreateErrorResponseFromError(err)
}
}
if nativeBalance.Cmp(path.requiredNativeBalance) == -1 {
return ErrNotEnoughNativeBalance
}
}
return nil
}
func removeBestRouteFromAllRouters(allRoutes [][]*PathV2, best []*PathV2) [][]*PathV2 {
for i, route := range allRoutes {
routeFound := true
for _, p := range route {
found := false
for _, b := range best {
if p.ProcessorName == b.ProcessorName &&
(p.FromChain == nil && b.FromChain == nil || p.FromChain.ChainID == b.FromChain.ChainID) &&
(p.ToChain == nil && b.ToChain == nil || p.ToChain.ChainID == b.ToChain.ChainID) &&
(p.FromToken == nil && b.FromToken == nil || p.FromToken.Symbol == b.FromToken.Symbol) {
found = true
break
}
}
if !found {
routeFound = false
break
}
}
if routeFound {
return append(allRoutes[:i], allRoutes[i+1:]...)
}
}
return nil
}
func (r *Router) resolveRoutes(ctx context.Context, input *RouteInputParams, candidates []*PathV2) (suggestedRoutes *SuggestedRoutesV2, err error) {
var prices map[string]float64
if input.testsMode {
@ -715,55 +785,36 @@ func (r *Router) resolveRoutes(ctx context.Context, input *RouteInputParams, can
}
}
suggestedRoutes = newSuggestedRoutesV2(input.Uuid, input.AmountIn.ToInt(), candidates, input.FromLockedAmount, prices[input.TokenID], prices[pathprocessor.EthSymbol])
tokenPrice := prices[input.TokenID]
nativeChainTokenPrice := prices[pathprocessor.EthSymbol]
// check the best route for the required balances
for _, path := range suggestedRoutes.Best {
if path.requiredTokenBalance != nil && path.requiredTokenBalance.Cmp(pathprocessor.ZeroBigIntValue) > 0 {
tokenBalance := big.NewInt(1)
if input.testsMode {
if val, ok := input.testParams.balanceMap[path.FromToken.Symbol]; ok {
tokenBalance = val
}
var allRoutes [][]*PathV2
suggestedRoutes, allRoutes = newSuggestedRoutesV2(input.Uuid, input.AmountIn.ToInt(), candidates, input.FromLockedAmount, tokenPrice, nativeChainTokenPrice)
for len(allRoutes) > 0 {
best := findBestV2(allRoutes, tokenPrice, nativeChainTokenPrice)
err := r.checkBalancesForTheBestRoute(ctx, best, input)
if err != nil {
// If it's about transfer or bridge and there is more routes, but on the best (cheapest) one there is not enugh balance
// we shold check other routes even though there are not the cheapest ones
if (input.SendType == Transfer ||
input.SendType == Bridge) &&
len(allRoutes) > 1 {
allRoutes = removeBestRouteFromAllRouters(allRoutes, best)
continue
} else {
if input.SendType == ERC1155Transfer {
tokenBalance, err = r.getERC1155Balance(ctx, path.FromChain, path.FromToken, input.AddrFrom)
if err != nil {
return suggestedRoutes, errors.CreateErrorResponseFromError(err)
}
} else if input.SendType != ERC721Transfer {
tokenBalance, err = r.getBalance(ctx, path.FromChain, path.FromToken, input.AddrFrom)
if err != nil {
return suggestedRoutes, errors.CreateErrorResponseFromError(err)
}
}
}
if tokenBalance.Cmp(path.requiredTokenBalance) == -1 {
return suggestedRoutes, ErrNotEnoughTokenBalance
}
}
nativeBalance := big.NewInt(0)
if input.testsMode {
if val, ok := input.testParams.balanceMap[pathprocessor.EthSymbol]; ok {
nativeBalance = val
}
} else {
nativeToken := r.tokenManager.FindToken(path.FromChain, path.FromChain.NativeCurrencySymbol)
if nativeToken == nil {
return suggestedRoutes, ErrNativeTokenNotFound
}
nativeBalance, err = r.getBalance(ctx, path.FromChain, nativeToken, input.AddrFrom)
if err != nil {
return suggestedRoutes, errors.CreateErrorResponseFromError(err)
}
}
if nativeBalance.Cmp(path.requiredNativeBalance) == -1 {
return suggestedRoutes, ErrNotEnoughNativeBalance
if len(best) > 0 {
sort.Slice(best, func(i, j int) bool {
return best[i].AmountInLocked
})
}
suggestedRoutes.Best = best
break
}
return suggestedRoutes, nil

View File

@ -74,9 +74,13 @@ var (
EIP1559Enabled: false,
}
testBalanceMap = map[string]*big.Int{
pathprocessor.EthSymbol: big.NewInt(testAmount2ETHInWei),
pathprocessor.UsdcSymbol: big.NewInt(testAmount100USDC),
testBalanceMapPerChain = map[string]*big.Int{
makeTestBalanceKey(walletCommon.EthereumMainnet, pathprocessor.EthSymbol): big.NewInt(testAmount2ETHInWei),
makeTestBalanceKey(walletCommon.EthereumMainnet, pathprocessor.UsdcSymbol): big.NewInt(testAmount100USDC),
makeTestBalanceKey(walletCommon.OptimismMainnet, pathprocessor.EthSymbol): big.NewInt(testAmount2ETHInWei),
makeTestBalanceKey(walletCommon.OptimismMainnet, pathprocessor.UsdcSymbol): big.NewInt(testAmount100USDC),
makeTestBalanceKey(walletCommon.ArbitrumMainnet, pathprocessor.EthSymbol): big.NewInt(testAmount2ETHInWei),
makeTestBalanceKey(walletCommon.ArbitrumMainnet, pathprocessor.UsdcSymbol): big.NewInt(testAmount100USDC),
}
)
@ -203,9 +207,8 @@ func setupTestNetworkDB(t *testing.T) (*sql.DB, func()) {
return db, func() { require.NoError(t, cleanup()) }
}
func TestRouterV2(t *testing.T) {
db, stop := setupTestNetworkDB(t)
defer stop()
func setupRouter(t *testing.T) (*Router, func()) {
db, cleanTmpDb := setupTestNetworkDB(t)
client, _ := rpc.NewClient(nil, 1, params.UpstreamRPCConfig{Enabled: false, URL: ""}, defaultNetworks, db)
@ -238,6 +241,13 @@ func TestRouterV2(t *testing.T) {
buyStickers := pathprocessor.NewStickersBuyProcessor(nil, nil, nil)
router.AddPathProcessor(buyStickers)
return router, cleanTmpDb
}
func TestRouterV2(t *testing.T) {
router, cleanTmpDb := setupRouter(t)
defer cleanTmpDb()
tests := []struct {
name string
input *RouteInputParams
@ -263,7 +273,7 @@ func TestRouterV2(t *testing.T) {
},
tokenPrices: testTokenPrices,
suggestedFees: testSuggestedFees,
balanceMap: testBalanceMap,
balanceMap: testBalanceMapPerChain,
estimationMap: testEstimationMap,
bonderFeeMap: testBbonderFeeMap,
approvalGasEstimation: testApprovalGasEstimation,
@ -348,7 +358,7 @@ func TestRouterV2(t *testing.T) {
tokenPrices: testTokenPrices,
baseFee: big.NewInt(testBaseFee),
suggestedFees: testSuggestedFees,
balanceMap: testBalanceMap,
balanceMap: testBalanceMapPerChain,
estimationMap: testEstimationMap,
bonderFeeMap: testBbonderFeeMap,
approvalGasEstimation: testApprovalGasEstimation,
@ -397,7 +407,7 @@ func TestRouterV2(t *testing.T) {
tokenPrices: testTokenPrices,
baseFee: big.NewInt(testBaseFee),
suggestedFees: testSuggestedFees,
balanceMap: testBalanceMap,
balanceMap: testBalanceMapPerChain,
estimationMap: testEstimationMap,
bonderFeeMap: testBbonderFeeMap,
approvalGasEstimation: testApprovalGasEstimation,
@ -464,7 +474,7 @@ func TestRouterV2(t *testing.T) {
tokenPrices: testTokenPrices,
baseFee: big.NewInt(testBaseFee),
suggestedFees: testSuggestedFees,
balanceMap: testBalanceMap,
balanceMap: testBalanceMapPerChain,
estimationMap: testEstimationMap,
bonderFeeMap: testBbonderFeeMap,
@ -514,7 +524,7 @@ func TestRouterV2(t *testing.T) {
tokenPrices: testTokenPrices,
baseFee: big.NewInt(testBaseFee),
suggestedFees: testSuggestedFees,
balanceMap: testBalanceMap,
balanceMap: testBalanceMapPerChain,
estimationMap: testEstimationMap,
bonderFeeMap: testBbonderFeeMap,
approvalGasEstimation: testApprovalGasEstimation,
@ -582,7 +592,7 @@ func TestRouterV2(t *testing.T) {
tokenPrices: testTokenPrices,
baseFee: big.NewInt(testBaseFee),
suggestedFees: testSuggestedFees,
balanceMap: testBalanceMap,
balanceMap: testBalanceMapPerChain,
estimationMap: testEstimationMap,
bonderFeeMap: testBbonderFeeMap,
approvalGasEstimation: testApprovalGasEstimation,
@ -620,7 +630,7 @@ func TestRouterV2(t *testing.T) {
tokenPrices: testTokenPrices,
baseFee: big.NewInt(testBaseFee),
suggestedFees: testSuggestedFees,
balanceMap: testBalanceMap,
balanceMap: testBalanceMapPerChain,
estimationMap: testEstimationMap,
bonderFeeMap: testBbonderFeeMap,
approvalGasEstimation: testApprovalGasEstimation,
@ -658,7 +668,7 @@ func TestRouterV2(t *testing.T) {
tokenPrices: testTokenPrices,
baseFee: big.NewInt(testBaseFee),
suggestedFees: testSuggestedFees,
balanceMap: testBalanceMap,
balanceMap: testBalanceMapPerChain,
estimationMap: testEstimationMap,
bonderFeeMap: testBbonderFeeMap,
approvalGasEstimation: testApprovalGasEstimation,
@ -696,7 +706,7 @@ func TestRouterV2(t *testing.T) {
tokenPrices: testTokenPrices,
baseFee: big.NewInt(testBaseFee),
suggestedFees: testSuggestedFees,
balanceMap: testBalanceMap,
balanceMap: testBalanceMapPerChain,
estimationMap: testEstimationMap,
bonderFeeMap: testBbonderFeeMap,
approvalGasEstimation: testApprovalGasEstimation,
@ -752,7 +762,7 @@ func TestRouterV2(t *testing.T) {
tokenPrices: testTokenPrices,
baseFee: big.NewInt(testBaseFee),
suggestedFees: testSuggestedFees,
balanceMap: testBalanceMap,
balanceMap: testBalanceMapPerChain,
estimationMap: testEstimationMap,
bonderFeeMap: testBbonderFeeMap,
approvalGasEstimation: testApprovalGasEstimation,
@ -790,7 +800,7 @@ func TestRouterV2(t *testing.T) {
tokenPrices: testTokenPrices,
baseFee: big.NewInt(testBaseFee),
suggestedFees: testSuggestedFees,
balanceMap: testBalanceMap,
balanceMap: testBalanceMapPerChain,
estimationMap: testEstimationMap,
bonderFeeMap: testBbonderFeeMap,
@ -823,7 +833,7 @@ func TestRouterV2(t *testing.T) {
tokenPrices: testTokenPrices,
baseFee: big.NewInt(testBaseFee),
suggestedFees: testSuggestedFees,
balanceMap: testBalanceMap,
balanceMap: testBalanceMapPerChain,
estimationMap: testEstimationMap,
bonderFeeMap: testBbonderFeeMap,
approvalGasEstimation: testApprovalGasEstimation,
@ -921,7 +931,7 @@ func TestRouterV2(t *testing.T) {
tokenPrices: testTokenPrices,
baseFee: big.NewInt(testBaseFee),
suggestedFees: testSuggestedFees,
balanceMap: testBalanceMap,
balanceMap: testBalanceMapPerChain,
estimationMap: testEstimationMap,
bonderFeeMap: testBbonderFeeMap,
approvalGasEstimation: testApprovalGasEstimation,
@ -976,7 +986,7 @@ func TestRouterV2(t *testing.T) {
tokenPrices: testTokenPrices,
baseFee: big.NewInt(testBaseFee),
suggestedFees: testSuggestedFees,
balanceMap: testBalanceMap,
balanceMap: testBalanceMapPerChain,
estimationMap: testEstimationMap,
bonderFeeMap: testBbonderFeeMap,
approvalGasEstimation: testApprovalGasEstimation,
@ -1074,7 +1084,7 @@ func TestRouterV2(t *testing.T) {
tokenPrices: testTokenPrices,
baseFee: big.NewInt(testBaseFee),
suggestedFees: testSuggestedFees,
balanceMap: testBalanceMap,
balanceMap: testBalanceMapPerChain,
estimationMap: testEstimationMap,
bonderFeeMap: testBbonderFeeMap,
approvalGasEstimation: testApprovalGasEstimation,
@ -1172,7 +1182,7 @@ func TestRouterV2(t *testing.T) {
tokenPrices: testTokenPrices,
baseFee: big.NewInt(testBaseFee),
suggestedFees: testSuggestedFees,
balanceMap: testBalanceMap,
balanceMap: testBalanceMapPerChain,
estimationMap: testEstimationMap,
bonderFeeMap: testBbonderFeeMap,
approvalGasEstimation: testApprovalGasEstimation,
@ -1206,7 +1216,7 @@ func TestRouterV2(t *testing.T) {
tokenPrices: testTokenPrices,
baseFee: big.NewInt(testBaseFee),
suggestedFees: testSuggestedFees,
balanceMap: testBalanceMap,
balanceMap: testBalanceMapPerChain,
estimationMap: testEstimationMap,
bonderFeeMap: testBbonderFeeMap,
approvalGasEstimation: testApprovalGasEstimation,
@ -1236,7 +1246,7 @@ func TestRouterV2(t *testing.T) {
tokenPrices: testTokenPrices,
baseFee: big.NewInt(testBaseFee),
suggestedFees: testSuggestedFees,
balanceMap: testBalanceMap,
balanceMap: testBalanceMapPerChain,
estimationMap: testEstimationMap,
bonderFeeMap: testBbonderFeeMap,
approvalGasEstimation: testApprovalGasEstimation,
@ -1321,7 +1331,7 @@ func TestRouterV2(t *testing.T) {
tokenPrices: testTokenPrices,
baseFee: big.NewInt(testBaseFee),
suggestedFees: testSuggestedFees,
balanceMap: testBalanceMap,
balanceMap: testBalanceMapPerChain,
estimationMap: testEstimationMap,
bonderFeeMap: testBbonderFeeMap,
approvalGasEstimation: testApprovalGasEstimation,
@ -1370,7 +1380,7 @@ func TestRouterV2(t *testing.T) {
tokenPrices: testTokenPrices,
baseFee: big.NewInt(testBaseFee),
suggestedFees: testSuggestedFees,
balanceMap: testBalanceMap,
balanceMap: testBalanceMapPerChain,
estimationMap: testEstimationMap,
bonderFeeMap: testBbonderFeeMap,
approvalGasEstimation: testApprovalGasEstimation,
@ -1437,7 +1447,7 @@ func TestRouterV2(t *testing.T) {
tokenPrices: testTokenPrices,
baseFee: big.NewInt(testBaseFee),
suggestedFees: testSuggestedFees,
balanceMap: testBalanceMap,
balanceMap: testBalanceMapPerChain,
estimationMap: testEstimationMap,
bonderFeeMap: testBbonderFeeMap,
approvalGasEstimation: testApprovalGasEstimation,
@ -1486,7 +1496,7 @@ func TestRouterV2(t *testing.T) {
tokenPrices: testTokenPrices,
baseFee: big.NewInt(testBaseFee),
suggestedFees: testSuggestedFees,
balanceMap: testBalanceMap,
balanceMap: testBalanceMapPerChain,
estimationMap: testEstimationMap,
bonderFeeMap: testBbonderFeeMap,
approvalGasEstimation: testApprovalGasEstimation,
@ -1554,7 +1564,7 @@ func TestRouterV2(t *testing.T) {
tokenPrices: testTokenPrices,
baseFee: big.NewInt(testBaseFee),
suggestedFees: testSuggestedFees,
balanceMap: testBalanceMap,
balanceMap: testBalanceMapPerChain,
estimationMap: testEstimationMap,
bonderFeeMap: testBbonderFeeMap,
approvalGasEstimation: testApprovalGasEstimation,
@ -1592,7 +1602,7 @@ func TestRouterV2(t *testing.T) {
tokenPrices: testTokenPrices,
baseFee: big.NewInt(testBaseFee),
suggestedFees: testSuggestedFees,
balanceMap: testBalanceMap,
balanceMap: testBalanceMapPerChain,
estimationMap: testEstimationMap,
bonderFeeMap: testBbonderFeeMap,
approvalGasEstimation: testApprovalGasEstimation,
@ -1630,7 +1640,7 @@ func TestRouterV2(t *testing.T) {
tokenPrices: testTokenPrices,
baseFee: big.NewInt(testBaseFee),
suggestedFees: testSuggestedFees,
balanceMap: testBalanceMap,
balanceMap: testBalanceMapPerChain,
estimationMap: testEstimationMap,
bonderFeeMap: testBbonderFeeMap,
approvalGasEstimation: testApprovalGasEstimation,
@ -1668,7 +1678,7 @@ func TestRouterV2(t *testing.T) {
tokenPrices: testTokenPrices,
baseFee: big.NewInt(testBaseFee),
suggestedFees: testSuggestedFees,
balanceMap: testBalanceMap,
balanceMap: testBalanceMapPerChain,
estimationMap: testEstimationMap,
bonderFeeMap: testBbonderFeeMap,
approvalGasEstimation: testApprovalGasEstimation,
@ -1724,7 +1734,7 @@ func TestRouterV2(t *testing.T) {
tokenPrices: testTokenPrices,
baseFee: big.NewInt(testBaseFee),
suggestedFees: testSuggestedFees,
balanceMap: testBalanceMap,
balanceMap: testBalanceMapPerChain,
estimationMap: testEstimationMap,
bonderFeeMap: testBbonderFeeMap,
approvalGasEstimation: testApprovalGasEstimation,
@ -1762,7 +1772,7 @@ func TestRouterV2(t *testing.T) {
tokenPrices: testTokenPrices,
baseFee: big.NewInt(testBaseFee),
suggestedFees: testSuggestedFees,
balanceMap: testBalanceMap,
balanceMap: testBalanceMapPerChain,
estimationMap: testEstimationMap,
bonderFeeMap: testBbonderFeeMap,
approvalGasEstimation: testApprovalGasEstimation,
@ -1791,7 +1801,7 @@ func TestRouterV2(t *testing.T) {
tokenPrices: testTokenPrices,
baseFee: big.NewInt(testBaseFee),
suggestedFees: testSuggestedFees,
balanceMap: testBalanceMap,
balanceMap: testBalanceMapPerChain,
estimationMap: testEstimationMap,
bonderFeeMap: testBbonderFeeMap,
approvalGasEstimation: testApprovalGasEstimation,
@ -1858,7 +1868,7 @@ func TestRouterV2(t *testing.T) {
tokenPrices: testTokenPrices,
baseFee: big.NewInt(testBaseFee),
suggestedFees: testSuggestedFees,
balanceMap: testBalanceMap,
balanceMap: testBalanceMapPerChain,
estimationMap: testEstimationMap,
bonderFeeMap: testBbonderFeeMap,
approvalGasEstimation: testApprovalGasEstimation,
@ -1901,7 +1911,7 @@ func TestRouterV2(t *testing.T) {
tokenPrices: testTokenPrices,
baseFee: big.NewInt(testBaseFee),
suggestedFees: testSuggestedFees,
balanceMap: testBalanceMap,
balanceMap: testBalanceMapPerChain,
estimationMap: testEstimationMap,
bonderFeeMap: testBbonderFeeMap,
approvalGasEstimation: testApprovalGasEstimation,
@ -1956,7 +1966,7 @@ func TestRouterV2(t *testing.T) {
tokenPrices: testTokenPrices,
baseFee: big.NewInt(testBaseFee),
suggestedFees: testSuggestedFees,
balanceMap: testBalanceMap,
balanceMap: testBalanceMapPerChain,
estimationMap: testEstimationMap,
bonderFeeMap: testBbonderFeeMap,
approvalGasEstimation: testApprovalGasEstimation,
@ -1999,7 +2009,7 @@ func TestRouterV2(t *testing.T) {
tokenPrices: testTokenPrices,
baseFee: big.NewInt(testBaseFee),
suggestedFees: testSuggestedFees,
balanceMap: testBalanceMap,
balanceMap: testBalanceMapPerChain,
estimationMap: testEstimationMap,
bonderFeeMap: testBbonderFeeMap,
approvalGasEstimation: testApprovalGasEstimation,
@ -2055,7 +2065,7 @@ func TestRouterV2(t *testing.T) {
tokenPrices: testTokenPrices,
baseFee: big.NewInt(testBaseFee),
suggestedFees: testSuggestedFees,
balanceMap: testBalanceMap,
balanceMap: testBalanceMapPerChain,
estimationMap: testEstimationMap,
bonderFeeMap: testBbonderFeeMap,
approvalGasEstimation: testApprovalGasEstimation,
@ -2086,7 +2096,7 @@ func TestRouterV2(t *testing.T) {
tokenPrices: testTokenPrices,
baseFee: big.NewInt(testBaseFee),
suggestedFees: testSuggestedFees,
balanceMap: testBalanceMap,
balanceMap: testBalanceMapPerChain,
estimationMap: testEstimationMap,
bonderFeeMap: testBbonderFeeMap,
approvalGasEstimation: testApprovalGasEstimation,
@ -2124,7 +2134,7 @@ func TestRouterV2(t *testing.T) {
tokenPrices: testTokenPrices,
baseFee: big.NewInt(testBaseFee),
suggestedFees: testSuggestedFees,
balanceMap: testBalanceMap,
balanceMap: testBalanceMapPerChain,
estimationMap: testEstimationMap,
bonderFeeMap: testBbonderFeeMap,
approvalGasEstimation: testApprovalGasEstimation,
@ -2155,7 +2165,7 @@ func TestRouterV2(t *testing.T) {
tokenPrices: testTokenPrices,
baseFee: big.NewInt(testBaseFee),
suggestedFees: testSuggestedFees,
balanceMap: testBalanceMap,
balanceMap: testBalanceMapPerChain,
estimationMap: testEstimationMap,
bonderFeeMap: testBbonderFeeMap,
approvalGasEstimation: testApprovalGasEstimation,
@ -2199,7 +2209,7 @@ func TestRouterV2(t *testing.T) {
tokenPrices: testTokenPrices,
baseFee: big.NewInt(testBaseFee),
suggestedFees: testSuggestedFees,
balanceMap: testBalanceMap,
balanceMap: testBalanceMapPerChain,
estimationMap: testEstimationMap,
bonderFeeMap: testBbonderFeeMap,
approvalGasEstimation: testApprovalGasEstimation,
@ -2237,7 +2247,7 @@ func TestRouterV2(t *testing.T) {
tokenPrices: testTokenPrices,
baseFee: big.NewInt(testBaseFee),
suggestedFees: testSuggestedFees,
balanceMap: testBalanceMap,
balanceMap: testBalanceMapPerChain,
estimationMap: testEstimationMap,
bonderFeeMap: testBbonderFeeMap,
approvalGasEstimation: testApprovalGasEstimation,
@ -2280,3 +2290,312 @@ func TestRouterV2(t *testing.T) {
})
}
}
func TestNoBalanceForTheBestRouteRouterV2(t *testing.T) {
router, cleanTmpDb := setupRouter(t)
defer cleanTmpDb()
tests := []struct {
name string
input *RouteInputParams
expectedCandidates []*PathV2
expectedBest []*PathV2
expectedError error
}{
{
name: "ERC20 transfer - Specific FromChain - Specific ToChain - Not Enough Token Balance",
input: &RouteInputParams{
testnetMode: false,
SendType: Transfer,
AddrFrom: common.HexToAddress("0x1"),
AddrTo: common.HexToAddress("0x2"),
AmountIn: (*hexutil.Big)(big.NewInt(testAmount100USDC)),
TokenID: pathprocessor.UsdcSymbol,
DisabledFromChainIDs: []uint64{walletCommon.EthereumMainnet, walletCommon.ArbitrumMainnet},
DisabledToChainIDs: []uint64{walletCommon.EthereumMainnet, walletCommon.ArbitrumMainnet},
testsMode: true,
testParams: &routerTestParams{
tokenFrom: &token.Token{
ChainID: 1,
Symbol: pathprocessor.UsdcSymbol,
Decimals: 6,
},
tokenPrices: testTokenPrices,
suggestedFees: testSuggestedFees,
balanceMap: map[string]*big.Int{},
estimationMap: testEstimationMap,
bonderFeeMap: testBbonderFeeMap,
approvalGasEstimation: testApprovalGasEstimation,
approvalL1Fee: testApprovalL1Fee,
},
},
expectedError: ErrNotEnoughTokenBalance,
expectedCandidates: []*PathV2{
{
ProcessorName: pathprocessor.ProcessorTransferName,
FromChain: &optimism,
ToChain: &optimism,
ApprovalRequired: false,
requiredTokenBalance: big.NewInt(testAmount100USDC),
requiredNativeBalance: big.NewInt((testBaseFee + testPriorityFeeLow) * testApprovalGasEstimation),
},
},
},
{
name: "ERC20 transfer - Specific FromChain - Specific ToChain - Not Enough Native Balance",
input: &RouteInputParams{
testnetMode: false,
SendType: Transfer,
AddrFrom: common.HexToAddress("0x1"),
AddrTo: common.HexToAddress("0x2"),
AmountIn: (*hexutil.Big)(big.NewInt(testAmount100USDC)),
TokenID: pathprocessor.UsdcSymbol,
DisabledFromChainIDs: []uint64{walletCommon.EthereumMainnet, walletCommon.ArbitrumMainnet},
DisabledToChainIDs: []uint64{walletCommon.EthereumMainnet, walletCommon.ArbitrumMainnet},
testsMode: true,
testParams: &routerTestParams{
tokenFrom: &token.Token{
ChainID: 1,
Symbol: pathprocessor.UsdcSymbol,
Decimals: 6,
},
tokenPrices: testTokenPrices,
suggestedFees: testSuggestedFees,
balanceMap: map[string]*big.Int{
makeTestBalanceKey(walletCommon.OptimismMainnet, pathprocessor.UsdcSymbol): big.NewInt(testAmount100USDC),
},
estimationMap: testEstimationMap,
bonderFeeMap: testBbonderFeeMap,
approvalGasEstimation: testApprovalGasEstimation,
approvalL1Fee: testApprovalL1Fee,
},
},
expectedError: ErrNotEnoughNativeBalance,
expectedCandidates: []*PathV2{
{
ProcessorName: pathprocessor.ProcessorTransferName,
FromChain: &optimism,
ToChain: &optimism,
ApprovalRequired: false,
requiredTokenBalance: big.NewInt(testAmount100USDC),
requiredNativeBalance: big.NewInt((testBaseFee + testPriorityFeeLow) * testApprovalGasEstimation),
},
},
},
{
name: "ERC20 transfer - No Specific FromChain - Specific ToChain - Not Enough Token Balance Across All Chains",
input: &RouteInputParams{
testnetMode: false,
SendType: Transfer,
AddrFrom: common.HexToAddress("0x1"),
AddrTo: common.HexToAddress("0x2"),
AmountIn: (*hexutil.Big)(big.NewInt(testAmount100USDC)),
TokenID: pathprocessor.UsdcSymbol,
DisabledToChainIDs: []uint64{walletCommon.EthereumMainnet, walletCommon.ArbitrumMainnet},
testsMode: true,
testParams: &routerTestParams{
tokenFrom: &token.Token{
ChainID: 1,
Symbol: pathprocessor.UsdcSymbol,
Decimals: 6,
},
tokenPrices: testTokenPrices,
suggestedFees: testSuggestedFees,
balanceMap: map[string]*big.Int{},
estimationMap: testEstimationMap,
bonderFeeMap: testBbonderFeeMap,
approvalGasEstimation: testApprovalGasEstimation,
approvalL1Fee: testApprovalL1Fee,
},
},
expectedError: ErrNotEnoughTokenBalance,
expectedCandidates: []*PathV2{
{
ProcessorName: pathprocessor.ProcessorBridgeHopName,
FromChain: &mainnet,
ToChain: &optimism,
ApprovalRequired: true,
},
{
ProcessorName: pathprocessor.ProcessorTransferName,
FromChain: &optimism,
ToChain: &optimism,
ApprovalRequired: false,
},
{
ProcessorName: pathprocessor.ProcessorBridgeHopName,
FromChain: &arbitrum,
ToChain: &optimism,
ApprovalRequired: true,
},
},
},
{
name: "ERC20 transfer - No Specific FromChain - Specific ToChain - Enough Token Balance On Arbitrum Chain But Not Enough Native Balance",
input: &RouteInputParams{
testnetMode: false,
SendType: Transfer,
AddrFrom: common.HexToAddress("0x1"),
AddrTo: common.HexToAddress("0x2"),
AmountIn: (*hexutil.Big)(big.NewInt(testAmount100USDC)),
TokenID: pathprocessor.UsdcSymbol,
DisabledToChainIDs: []uint64{walletCommon.EthereumMainnet, walletCommon.ArbitrumMainnet},
testsMode: true,
testParams: &routerTestParams{
tokenFrom: &token.Token{
ChainID: 1,
Symbol: pathprocessor.UsdcSymbol,
Decimals: 6,
},
tokenPrices: testTokenPrices,
suggestedFees: testSuggestedFees,
balanceMap: map[string]*big.Int{
makeTestBalanceKey(walletCommon.ArbitrumMainnet, pathprocessor.UsdcSymbol): big.NewInt(testAmount100USDC + testAmount100USDC),
},
estimationMap: testEstimationMap,
bonderFeeMap: testBbonderFeeMap,
approvalGasEstimation: testApprovalGasEstimation,
approvalL1Fee: testApprovalL1Fee,
},
},
expectedError: ErrNotEnoughNativeBalance,
expectedCandidates: []*PathV2{
{
ProcessorName: pathprocessor.ProcessorBridgeHopName,
FromChain: &mainnet,
ToChain: &optimism,
ApprovalRequired: true,
},
{
ProcessorName: pathprocessor.ProcessorTransferName,
FromChain: &optimism,
ToChain: &optimism,
ApprovalRequired: false,
requiredTokenBalance: big.NewInt(testAmount100USDC),
requiredNativeBalance: big.NewInt((testBaseFee + testPriorityFeeLow) * testApprovalGasEstimation),
},
{
ProcessorName: pathprocessor.ProcessorBridgeHopName,
FromChain: &arbitrum,
ToChain: &optimism,
ApprovalRequired: true,
},
},
},
{
name: "ERC20 transfer - No Specific FromChain - Specific ToChain - Enough Token Balance On Arbitrum Chain And Enough Native Balance On Arbitrum Chain",
input: &RouteInputParams{
testnetMode: false,
SendType: Transfer,
AddrFrom: common.HexToAddress("0x1"),
AddrTo: common.HexToAddress("0x2"),
AmountIn: (*hexutil.Big)(big.NewInt(testAmount100USDC)),
TokenID: pathprocessor.UsdcSymbol,
DisabledToChainIDs: []uint64{walletCommon.EthereumMainnet, walletCommon.ArbitrumMainnet},
testsMode: true,
testParams: &routerTestParams{
tokenFrom: &token.Token{
ChainID: 1,
Symbol: pathprocessor.UsdcSymbol,
Decimals: 6,
},
tokenPrices: testTokenPrices,
suggestedFees: testSuggestedFees,
balanceMap: map[string]*big.Int{
makeTestBalanceKey(walletCommon.ArbitrumMainnet, pathprocessor.UsdcSymbol): big.NewInt(testAmount100USDC + testAmount100USDC),
makeTestBalanceKey(walletCommon.ArbitrumMainnet, pathprocessor.EthSymbol): big.NewInt(testAmount1ETHInWei),
},
estimationMap: testEstimationMap,
bonderFeeMap: testBbonderFeeMap,
approvalGasEstimation: testApprovalGasEstimation,
approvalL1Fee: testApprovalL1Fee,
},
},
expectedCandidates: []*PathV2{
{
ProcessorName: pathprocessor.ProcessorBridgeHopName,
FromChain: &mainnet,
ToChain: &optimism,
ApprovalRequired: true,
},
{
ProcessorName: pathprocessor.ProcessorTransferName,
FromChain: &optimism,
ToChain: &optimism,
ApprovalRequired: false,
requiredTokenBalance: big.NewInt(testAmount100USDC),
requiredNativeBalance: big.NewInt((testBaseFee + testPriorityFeeLow) * testApprovalGasEstimation),
},
{
ProcessorName: pathprocessor.ProcessorBridgeHopName,
FromChain: &arbitrum,
ToChain: &optimism,
ApprovalRequired: true,
},
},
expectedBest: []*PathV2{
{
ProcessorName: pathprocessor.ProcessorBridgeHopName,
FromChain: &arbitrum,
ToChain: &optimism,
ApprovalRequired: true,
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
routes, err := router.SuggestedRoutesV2(context.Background(), tt.input)
if tt.expectedError != nil {
assert.Error(t, err)
assert.Equal(t, tt.expectedError, err)
assert.NotNil(t, routes)
assert.Equal(t, len(tt.expectedCandidates), len(routes.Candidates))
} else {
assert.NoError(t, err)
assert.Equal(t, len(tt.expectedCandidates), len(routes.Candidates))
assert.Equal(t, len(tt.expectedBest), len(routes.Best))
for _, c := range routes.Candidates {
found := false
for _, expC := range tt.expectedCandidates {
if c.ProcessorName == expC.ProcessorName &&
c.FromChain.ChainID == expC.FromChain.ChainID &&
c.ToChain.ChainID == expC.ToChain.ChainID &&
c.ApprovalRequired == expC.ApprovalRequired &&
(expC.AmountOut == nil || c.AmountOut.ToInt().Cmp(expC.AmountOut.ToInt()) == 0) {
found = true
break
}
}
assert.True(t, found)
}
for _, c := range routes.Best {
found := false
for _, expC := range tt.expectedBest {
if c.ProcessorName == expC.ProcessorName &&
c.FromChain.ChainID == expC.FromChain.ChainID &&
c.ToChain.ChainID == expC.ToChain.ChainID &&
c.ApprovalRequired == expC.ApprovalRequired &&
(expC.AmountOut == nil || c.AmountOut.ToInt().Cmp(expC.AmountOut.ToInt()) == 0) {
found = true
break
}
}
assert.True(t, found)
}
}
})
}
}