github.com/status-im/status-go@v1.1.0/services/wallet/reader_test.go (about)

     1  package wallet
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"math/big"
     8  	"sync"
     9  	"testing"
    10  	"time"
    11  
    12  	"github.com/golang/mock/gomock"
    13  	"github.com/stretchr/testify/assert"
    14  	"github.com/stretchr/testify/require"
    15  
    16  	"github.com/ethereum/go-ethereum/common"
    17  	"github.com/ethereum/go-ethereum/common/hexutil"
    18  	"github.com/ethereum/go-ethereum/event"
    19  	"github.com/status-im/status-go/rpc/chain"
    20  	mock_client "github.com/status-im/status-go/rpc/chain/mock/client"
    21  	"github.com/status-im/status-go/services/wallet/testutils"
    22  	"github.com/status-im/status-go/services/wallet/token"
    23  	mock_balance_persistence "github.com/status-im/status-go/services/wallet/token/mock/balance_persistence"
    24  	mock_token "github.com/status-im/status-go/services/wallet/token/mock/token"
    25  )
    26  
    27  var (
    28  	testTokenAddress1 = common.Address{0x34}
    29  	testTokenAddress2 = common.Address{0x56}
    30  
    31  	testAccAddress1 = common.Address{0x12}
    32  	testAccAddress2 = common.Address{0x45}
    33  
    34  	expectedTokens = map[common.Address][]token.StorageToken{
    35  		testAccAddress1: []token.StorageToken{
    36  			{
    37  				Token: token.Token{
    38  					Address:  testTokenAddress1,
    39  					Name:     "Token 1",
    40  					Symbol:   "T1",
    41  					Decimals: 18,
    42  				},
    43  				BalancesPerChain: nil,
    44  			},
    45  		},
    46  		testAccAddress2: []token.StorageToken{
    47  			{
    48  				Token: token.Token{
    49  					Address:  testTokenAddress2,
    50  					Name:     "Token 2",
    51  					Symbol:   "T2",
    52  					Decimals: 18,
    53  				},
    54  				BalancesPerChain: map[uint64]token.ChainBalance{
    55  					1: {
    56  						RawBalance: "1000000000000000000",
    57  						Balance:    big.NewFloat(1),
    58  						Address:    common.Address{0x12},
    59  						ChainID:    1,
    60  						HasError:   false,
    61  					},
    62  				},
    63  			},
    64  		},
    65  	}
    66  )
    67  
    68  // This matcher is used to compare the expected and actual map[common.Address][]token.StorageToken in parameters to SaveTokens
    69  type mapTokenWithBalanceMatcher struct {
    70  	expected []interface{}
    71  }
    72  
    73  func (m mapTokenWithBalanceMatcher) Matches(x interface{}) bool {
    74  	actual, ok := x.(map[common.Address][]token.StorageToken)
    75  	if !ok {
    76  		return false
    77  	}
    78  
    79  	if len(m.expected) != len(actual) {
    80  		return false
    81  	}
    82  
    83  	expected := m.expected[0].(map[common.Address][]token.StorageToken)
    84  
    85  	for address, expectedTokens := range expected {
    86  		actualTokens, ok := actual[address]
    87  		if !ok {
    88  			return false
    89  		}
    90  
    91  		if len(expectedTokens) != len(actualTokens) {
    92  			return false
    93  		}
    94  
    95  		for i, expectedToken := range expectedTokens {
    96  			actualToken := actualTokens[i]
    97  			if expectedToken.Token != actualToken.Token {
    98  				return false
    99  			}
   100  
   101  			if len(expectedToken.BalancesPerChain) != len(actualToken.BalancesPerChain) {
   102  				return false
   103  			}
   104  
   105  			// We can't compare the  balances directly because the Balance field is a big.Float
   106  			for chainID, expectedBalance := range expectedToken.BalancesPerChain {
   107  				actualBalance, ok := actualToken.BalancesPerChain[chainID]
   108  				if !ok {
   109  					return false
   110  				}
   111  
   112  				if expectedBalance.Balance.Cmp(actualBalance.Balance) != 0 {
   113  					return false
   114  				}
   115  
   116  				if expectedBalance.RawBalance != actualBalance.RawBalance {
   117  					return false
   118  				}
   119  
   120  				if expectedBalance.Address != actualBalance.Address {
   121  					return false
   122  				}
   123  
   124  				if expectedBalance.ChainID != actualBalance.ChainID {
   125  					return false
   126  				}
   127  
   128  				if expectedBalance.HasError != actualBalance.HasError {
   129  					return false
   130  				}
   131  			}
   132  		}
   133  	}
   134  
   135  	return true
   136  }
   137  
   138  func (m *mapTokenWithBalanceMatcher) String() string {
   139  	return fmt.Sprintf("%v", m.expected)
   140  }
   141  
   142  func newMapTokenWithBalanceMatcher(expected []interface{}) gomock.Matcher {
   143  	return &mapTokenWithBalanceMatcher{
   144  		expected: expected,
   145  	}
   146  }
   147  func testChainBalancesEqual(t *testing.T, expected, actual token.ChainBalance) {
   148  	assert.Equal(t, expected.RawBalance, actual.RawBalance)
   149  	assert.Equal(t, 0, expected.Balance.Cmp(actual.Balance))
   150  	assert.Equal(t, expected.Address, actual.Address)
   151  	assert.Equal(t, expected.ChainID, actual.ChainID)
   152  	assert.Equal(t, expected.HasError, actual.HasError)
   153  	assert.Equal(t, expected.Balance1DayAgo, actual.Balance1DayAgo)
   154  }
   155  
   156  func testBalancePerChainEqual(t *testing.T, expected, actual map[uint64]token.ChainBalance) {
   157  	assert.Len(t, actual, len(expected))
   158  	for chainID, expectedBalance := range expected {
   159  		actualBalance, ok := actual[chainID]
   160  		assert.True(t, ok)
   161  		testChainBalancesEqual(t, expectedBalance, actualBalance)
   162  	}
   163  }
   164  
   165  func setupReader(t *testing.T) (*Reader, *mock_token.MockManagerInterface, *mock_balance_persistence.MockTokenBalancesStorage, *gomock.Controller) {
   166  	mockCtrl := gomock.NewController(t)
   167  	mockTokenManager := mock_token.NewMockManagerInterface(mockCtrl)
   168  	tokenBalanceStorage := mock_balance_persistence.NewMockTokenBalancesStorage(mockCtrl)
   169  	eventsFeed := &event.Feed{}
   170  
   171  	return NewReader(mockTokenManager, nil, tokenBalanceStorage, eventsFeed), mockTokenManager, tokenBalanceStorage, mockCtrl
   172  }
   173  
   174  func TestGetCachedWalletTokensWithoutMarketData(t *testing.T) {
   175  	reader, _, tokenPersistence, mockCtrl := setupReader(t)
   176  	defer mockCtrl.Finish()
   177  
   178  	// Test when there is an error getting the tokens
   179  	tokenPersistence.EXPECT().GetTokens().Return(nil, errors.New("error"))
   180  	tokens, err := reader.getCachedWalletTokensWithoutMarketData()
   181  	require.Error(t, err)
   182  	assert.Nil(t, tokens)
   183  
   184  	// Test happy path
   185  	tokenPersistence.EXPECT().GetTokens().Return(expectedTokens, nil)
   186  	tokens, err = reader.getCachedWalletTokensWithoutMarketData()
   187  	require.NoError(t, err)
   188  	assert.Equal(t, expectedTokens, tokens)
   189  }
   190  
   191  func TestIsBalanceCacheValid(t *testing.T) {
   192  	reader, _, tokenPersistence, mockCtrl := setupReader(t)
   193  	defer mockCtrl.Finish()
   194  
   195  	// Mock the cache to be valid
   196  	addresses := []common.Address{testAccAddress1, testAccAddress2}
   197  	reader.balanceRefreshed()
   198  	reader.updateTokenUpdateTimestamp([]common.Address{testAccAddress1, testAccAddress2})
   199  	tokenPersistence.EXPECT().GetTokens().Return(expectedTokens, nil)
   200  	valid := reader.isBalanceCacheValid(addresses)
   201  	assert.True(t, valid)
   202  
   203  	// Mock the cache to be invalid
   204  	reader.invalidateBalanceCache()
   205  	valid = reader.isBalanceCacheValid(addresses)
   206  	assert.False(t, valid)
   207  
   208  	// Make cached tokens not contain all the addresses
   209  	reader.balanceRefreshed()
   210  	cachedTokens := map[common.Address][]token.StorageToken{
   211  		testAccAddress1: expectedTokens[testAccAddress1],
   212  	}
   213  	tokenPersistence.EXPECT().GetTokens().Return(cachedTokens, nil)
   214  	valid = reader.isBalanceCacheValid(addresses)
   215  	assert.False(t, valid)
   216  
   217  	// Some timestamps are not updated
   218  	reader.balanceRefreshed()
   219  	reader.lastWalletTokenUpdateTimestamp = sync.Map{}
   220  	reader.updateTokenUpdateTimestamp([]common.Address{testAccAddress1})
   221  	cachedTokens = expectedTokens
   222  	tokenPersistence.EXPECT().GetTokens().AnyTimes().Return(cachedTokens, nil)
   223  	assert.False(t, valid)
   224  }
   225  
   226  func TestTokensCachedForAddresses(t *testing.T) {
   227  	reader, _, persistence, mockCtrl := setupReader(t)
   228  	defer mockCtrl.Finish()
   229  
   230  	addresses := []common.Address{testAccAddress1, testAccAddress2}
   231  
   232  	// Test when the cached tokens do not contain all the addresses
   233  	cachedTokens := map[common.Address][]token.StorageToken{
   234  		testAccAddress1: expectedTokens[testAccAddress1],
   235  	}
   236  	persistence.EXPECT().GetTokens().Return(cachedTokens, nil)
   237  
   238  	result := reader.tokensCachedForAddresses(addresses)
   239  	assert.False(t, result)
   240  
   241  	// Test when the cached tokens contain all the addresses
   242  	cachedTokens = expectedTokens
   243  	persistence.EXPECT().GetTokens().Return(cachedTokens, nil)
   244  
   245  	result = reader.tokensCachedForAddresses(addresses)
   246  	assert.True(t, result)
   247  }
   248  
   249  func TestFetchBalancesInternal(t *testing.T) {
   250  	reader, tokenManager, _, mockCtrl := setupReader(t)
   251  	defer mockCtrl.Finish()
   252  
   253  	addresses := []common.Address{testAccAddress1, testAccAddress2}
   254  	tokenAddresses := []common.Address{testTokenAddress1, testTokenAddress2}
   255  	ctx := context.TODO()
   256  	clients := map[uint64]chain.ClientInterface{}
   257  
   258  	// Test when there is an error getting the tokens
   259  	tokenManager.EXPECT().GetBalancesByChain(ctx, clients, addresses, tokenAddresses).Return(nil, errors.New("error"))
   260  	balances, err := reader.fetchBalances(ctx, clients, addresses, tokenAddresses)
   261  	require.Error(t, err)
   262  	assert.Nil(t, balances)
   263  
   264  	// Test happy path
   265  	expectedBalances := map[uint64]map[common.Address]map[common.Address]*hexutil.Big{
   266  		1: {
   267  			testAccAddress2: {
   268  				testTokenAddress2: (*hexutil.Big)(big.NewInt(1)),
   269  			},
   270  		},
   271  	}
   272  	tokenManager.EXPECT().GetBalancesByChain(ctx, clients, addresses, tokenAddresses).Return(expectedBalances, nil)
   273  	balances, err = reader.fetchBalances(ctx, clients, addresses, tokenAddresses)
   274  	require.NoError(t, err)
   275  	assert.Equal(t, balances, expectedBalances)
   276  }
   277  
   278  func TestTokensToBalancesPerChain(t *testing.T) {
   279  	cachedTokens := map[common.Address][]token.StorageToken{
   280  		testAccAddress1: []token.StorageToken{
   281  			{
   282  				Token: token.Token{
   283  					Address:  testTokenAddress1,
   284  					Name:     "Token 1",
   285  					Symbol:   "T1",
   286  					Decimals: 18,
   287  				},
   288  				BalancesPerChain: map[uint64]token.ChainBalance{
   289  					1: {
   290  						RawBalance: "1000000000000000000",
   291  						Balance:    big.NewFloat(1),
   292  						Address:    common.Address{0x12},
   293  						ChainID:    1,
   294  						HasError:   false,
   295  					},
   296  				},
   297  			},
   298  			{
   299  				Token: token.Token{
   300  					Address:  testTokenAddress2,
   301  					Name:     "Token 2",
   302  					Symbol:   "T2",
   303  					Decimals: 18,
   304  				},
   305  				BalancesPerChain: nil, // Skip this token
   306  			},
   307  		},
   308  		testAccAddress2: []token.StorageToken{
   309  			{
   310  				Token: token.Token{
   311  					Address:  testTokenAddress2,
   312  					Name:     "Token 2",
   313  					Symbol:   "T2",
   314  					Decimals: 18,
   315  				},
   316  				BalancesPerChain: map[uint64]token.ChainBalance{
   317  					1: {
   318  						RawBalance: "2000000000000000000",
   319  						Balance:    big.NewFloat(2),
   320  						Address:    common.Address{0x34},
   321  						ChainID:    1,
   322  						HasError:   false,
   323  					},
   324  					2: {
   325  						RawBalance: "3000000000000000000",
   326  						Balance:    big.NewFloat(3),
   327  						Address:    common.Address{0x56},
   328  						ChainID:    2,
   329  						HasError:   false,
   330  					},
   331  				},
   332  			},
   333  		},
   334  	}
   335  
   336  	expectedBalancesPerChain := map[uint64]map[common.Address]map[common.Address]*hexutil.Big{
   337  		1: {
   338  			testAccAddress1: {
   339  				common.Address{0x12}: (*hexutil.Big)(big.NewInt(1000000000000000000)),
   340  			},
   341  			testAccAddress2: {
   342  				common.Address{0x34}: (*hexutil.Big)(big.NewInt(2000000000000000000)),
   343  			},
   344  		},
   345  		2: {
   346  			testAccAddress2: {
   347  				common.Address{0x56}: (*hexutil.Big)(big.NewInt(3000000000000000000)),
   348  			},
   349  		},
   350  	}
   351  
   352  	result := tokensToBalancesPerChain(cachedTokens)
   353  
   354  	assert.Equal(t, expectedBalancesPerChain, result)
   355  }
   356  
   357  func TestGetBalance1DayAgo(t *testing.T) {
   358  	reader, tokenManager, _, mockCtrl := setupReader(t)
   359  	defer mockCtrl.Finish()
   360  
   361  	address := common.Address{0x12}
   362  	chainID := uint64(1)
   363  	symbol := "T1"
   364  	dayAgoTimestamp := time.Now().Add(-24 * time.Hour).Unix()
   365  
   366  	// Test happy path
   367  	expectedBalance := big.NewInt(1000000000000000000)
   368  	tokenManager.EXPECT().GetTokenHistoricalBalance(address, chainID, symbol, dayAgoTimestamp).Return(expectedBalance, nil)
   369  
   370  	balance1DayAgo, err := reader.getBalance1DayAgo(&token.ChainBalance{
   371  		ChainID: chainID,
   372  		Address: address,
   373  	}, dayAgoTimestamp, symbol, address)
   374  
   375  	require.NoError(t, err)
   376  	assert.Equal(t, expectedBalance, balance1DayAgo)
   377  
   378  	// Test error
   379  	tokenManager.EXPECT().GetTokenHistoricalBalance(address, chainID, symbol, dayAgoTimestamp).Return(nil, errors.New("error"))
   380  	balance1DayAgo, err = reader.getBalance1DayAgo(&token.ChainBalance{
   381  		ChainID: chainID,
   382  		Address: address,
   383  	}, dayAgoTimestamp, symbol, address)
   384  
   385  	require.Error(t, err)
   386  	assert.Nil(t, balance1DayAgo)
   387  }
   388  
   389  func TestToChainBalance(t *testing.T) {
   390  	balances := map[uint64]map[common.Address]map[common.Address]*hexutil.Big{
   391  		1: {
   392  			common.Address{0x12}: {
   393  				common.Address{0x34}: (*hexutil.Big)(big.NewInt(1000000000000000000)),
   394  			},
   395  		},
   396  	}
   397  	tok := &token.Token{
   398  		ChainID:  1,
   399  		Address:  common.Address{0x34},
   400  		Symbol:   "T1",
   401  		Decimals: 18,
   402  	}
   403  	address := common.Address{0x12}
   404  	decimals := uint(18)
   405  	cachedTokens := map[common.Address][]token.StorageToken{
   406  		common.Address{0x12}: {
   407  			{
   408  				Token: token.Token{
   409  					Address:  common.Address{0x34},
   410  					Name:     "Token 1",
   411  					Symbol:   "T1",
   412  					Decimals: 18,
   413  				},
   414  				BalancesPerChain: nil,
   415  			},
   416  		},
   417  	}
   418  
   419  	expectedBalance := big.NewFloat(1)
   420  	hasError := false
   421  	expectedChainBalance := &token.ChainBalance{
   422  		RawBalance:     "1000000000000000000",
   423  		Balance:        expectedBalance,
   424  		Balance1DayAgo: "0",
   425  		Address:        common.Address{0x34},
   426  		ChainID:        1,
   427  		HasError:       hasError,
   428  	}
   429  
   430  	chainBalance := toChainBalance(balances, tok, address, decimals, cachedTokens, hasError, false)
   431  	testChainBalancesEqual(t, *expectedChainBalance, *chainBalance)
   432  
   433  	// Test when the token is not visible
   434  	emptyCachedTokens := map[common.Address][]token.StorageToken{}
   435  	isMandatory := false
   436  	noBalances := map[uint64]map[common.Address]map[common.Address]*hexutil.Big{
   437  		tok.ChainID: {
   438  			address: {
   439  				tok.Address: nil, // Idk why this can be nil
   440  			},
   441  		},
   442  	}
   443  	chainBalance = toChainBalance(noBalances, tok, address, decimals, emptyCachedTokens, hasError, isMandatory)
   444  	assert.Nil(t, chainBalance)
   445  }
   446  
   447  func TestIsCachedToken(t *testing.T) {
   448  	cachedTokens := map[common.Address][]token.StorageToken{
   449  		common.Address{0x12}: {
   450  			{
   451  				Token: token.Token{
   452  					Address:  common.Address{0x34},
   453  					Name:     "Token 1",
   454  					Symbol:   "T1",
   455  					Decimals: 18,
   456  				},
   457  				BalancesPerChain: map[uint64]token.ChainBalance{
   458  					1: {
   459  						RawBalance: "1000000000000000000",
   460  						Balance:    big.NewFloat(1),
   461  						Address:    common.Address{0x12},
   462  						ChainID:    1,
   463  						HasError:   false,
   464  					},
   465  				},
   466  			},
   467  		},
   468  	}
   469  
   470  	address := common.Address{0x12}
   471  	symbol := "T1"
   472  	chainID := uint64(1)
   473  
   474  	// Test when the token is cached
   475  	result := isCachedToken(cachedTokens, address, symbol, chainID)
   476  	assert.True(t, result)
   477  
   478  	// Test when the token is not cached
   479  	result = isCachedToken(cachedTokens, address, "T2", chainID)
   480  	assert.False(t, result)
   481  
   482  	// Test when BalancesPerChain for token have no such a chainID
   483  	wrongChainID := chainID + 1
   484  	result = isCachedToken(cachedTokens, address, symbol, wrongChainID)
   485  	assert.False(t, result)
   486  
   487  }
   488  
   489  func TestCreateBalancePerChainPerSymbol(t *testing.T) {
   490  	address := common.Address{0x12}
   491  	balances := map[uint64]map[common.Address]map[common.Address]*hexutil.Big{
   492  		1: {
   493  			address: {
   494  				common.Address{0x34}: (*hexutil.Big)(big.NewInt(1000000000000000000)),
   495  			},
   496  		},
   497  		2: {
   498  			address: {
   499  				common.Address{0x56}: (*hexutil.Big)(big.NewInt(2000000000000000000)),
   500  			},
   501  		},
   502  	}
   503  
   504  	tokens := []*token.Token{
   505  		{
   506  			Name:     "Token 1 mainnet",
   507  			ChainID:  1,
   508  			Address:  common.Address{0x34},
   509  			Symbol:   "T1",
   510  			Decimals: 18,
   511  		},
   512  		{
   513  			Name:     "Token 1 optimism",
   514  			ChainID:  2,
   515  			Address:  common.Address{0x56},
   516  			Symbol:   "T1",
   517  			Decimals: 18,
   518  		},
   519  	}
   520  	// Let cached tokens not have the token for chain 2, it still should be calculated because of positive balance
   521  	cachedTokens := map[common.Address][]token.StorageToken{
   522  		address: {
   523  			{
   524  				Token: token.Token{
   525  					Address:  common.Address{0x34},
   526  					Name:     "Token 1",
   527  					Symbol:   "T1",
   528  					Decimals: 18,
   529  				},
   530  				BalancesPerChain: map[uint64]token.ChainBalance{
   531  					1: {
   532  						RawBalance: "1000000000000000000",
   533  						Balance:    big.NewFloat(1),
   534  						Address:    common.Address{0x12},
   535  						ChainID:    1,
   536  						HasError:   false,
   537  					},
   538  				},
   539  			},
   540  		},
   541  	}
   542  
   543  	clientConnectionPerChain := map[uint64]bool{
   544  		1: true,
   545  		2: false,
   546  	}
   547  	dayAgoTimestamp := time.Now().Add(-24 * time.Hour).Unix()
   548  
   549  	expectedBalancesPerChain := map[uint64]token.ChainBalance{
   550  		1: {
   551  			RawBalance:     "1000000000000000000",
   552  			Balance:        big.NewFloat(1),
   553  			Balance1DayAgo: "0",
   554  			Address:        common.Address{0x34},
   555  			ChainID:        1,
   556  			HasError:       false,
   557  		},
   558  		2: {
   559  			RawBalance:     "2000000000000000000",
   560  			Balance:        big.NewFloat(2),
   561  			Balance1DayAgo: "0",
   562  			Address:        common.Address{0x56},
   563  			ChainID:        2,
   564  			HasError:       true,
   565  		},
   566  	}
   567  
   568  	reader, tokenManager, _, mockCtrl := setupReader(t)
   569  	defer mockCtrl.Finish()
   570  
   571  	tokenManager.EXPECT().GetTokenHistoricalBalance(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Times(2).Return(nil, errors.New("error"))
   572  	result := reader.createBalancePerChainPerSymbol(address, balances, tokens, cachedTokens, clientConnectionPerChain, dayAgoTimestamp)
   573  
   574  	assert.Len(t, result, 2)
   575  	testBalancePerChainEqual(t, expectedBalancesPerChain, result)
   576  }
   577  
   578  func TestCreateBalancePerChainPerSymbolWithMissingBalance(t *testing.T) {
   579  	address := common.Address{0x12}
   580  	tokens := []*token.Token{
   581  		{
   582  			Name:     "Token 1 mainnet",
   583  			ChainID:  1,
   584  			Address:  common.Address{0x34},
   585  			Symbol:   "T1",
   586  			Decimals: 18,
   587  		},
   588  		{
   589  			Name:     "Token 1 optimism",
   590  			ChainID:  2,
   591  			Address:  common.Address{0x56},
   592  			Symbol:   "T1",
   593  			Decimals: 18,
   594  		},
   595  	}
   596  
   597  	clientConnectionPerChain := map[uint64]bool{
   598  		1: true,
   599  		2: false,
   600  	}
   601  
   602  	dayAgoTimestamp := time.Now().Add(-24 * time.Hour).Unix()
   603  	emptyCachedTokens := map[common.Address][]token.StorageToken{}
   604  	oneBalanceMissing := map[uint64]map[common.Address]map[common.Address]*hexutil.Big{
   605  		1: {
   606  			address: {
   607  				common.Address{0x34}: nil, // Idk why this can be nil
   608  			},
   609  		},
   610  
   611  		2: {
   612  			address: {
   613  				common.Address{0x56}: (*hexutil.Big)(big.NewInt(1000000000000000000)),
   614  			},
   615  		},
   616  	}
   617  
   618  	expectedBalancesPerChain := map[uint64]token.ChainBalance{
   619  		2: {
   620  			RawBalance:     "1000000000000000000",
   621  			Balance:        big.NewFloat(1),
   622  			Balance1DayAgo: "1",
   623  			Address:        common.Address{0x56},
   624  			ChainID:        2,
   625  			HasError:       true,
   626  		},
   627  	}
   628  
   629  	reader, tokenManager, _, mockCtrl := setupReader(t)
   630  	defer mockCtrl.Finish()
   631  
   632  	tokenManager.EXPECT().GetTokenHistoricalBalance(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Times(1).Return(big.NewInt(1), nil)
   633  	result := reader.createBalancePerChainPerSymbol(address, oneBalanceMissing, tokens, emptyCachedTokens, clientConnectionPerChain, dayAgoTimestamp)
   634  	assert.Len(t, result, 1)
   635  	testBalancePerChainEqual(t, expectedBalancesPerChain, result)
   636  }
   637  
   638  func TestBalancesToTokensByAddress(t *testing.T) {
   639  	connectedPerChain := map[uint64]bool{
   640  		1: true,
   641  		2: true,
   642  	}
   643  
   644  	addresses := []common.Address{
   645  		common.HexToAddress("0x123"),
   646  		common.HexToAddress("0x456"),
   647  	}
   648  
   649  	allTokens := []*token.Token{
   650  		{
   651  			Name:     "Token 1",
   652  			Symbol:   "T1",
   653  			Decimals: 18,
   654  			Verified: true,
   655  			ChainID:  1,
   656  			Address:  common.HexToAddress("0x789"),
   657  		},
   658  		{
   659  			Name:     "Token 2",
   660  			Symbol:   "T2",
   661  			Decimals: 18,
   662  			Verified: true,
   663  			ChainID:  1,
   664  			Address:  common.HexToAddress("0xdef"),
   665  		},
   666  		{
   667  			Name:     "Token 2 optimism",
   668  			Symbol:   "T2",
   669  			Decimals: 18,
   670  			Verified: true,
   671  			ChainID:  2,
   672  			Address:  common.HexToAddress("0xabc"),
   673  		},
   674  	}
   675  
   676  	balances := map[uint64]map[common.Address]map[common.Address]*hexutil.Big{
   677  		1: {
   678  			addresses[0]: {
   679  				allTokens[0].Address: (*hexutil.Big)(big.NewInt(1000000000000000000)),
   680  			},
   681  			addresses[1]: {
   682  				allTokens[1].Address: (*hexutil.Big)(big.NewInt(2000000000000000000)),
   683  			},
   684  		},
   685  		2: {
   686  			addresses[1]: {
   687  				allTokens[2].Address: (*hexutil.Big)(big.NewInt(3000000000000000000)),
   688  			},
   689  		},
   690  	}
   691  
   692  	cachedTokens := map[common.Address][]token.StorageToken{
   693  		addresses[0]: {
   694  			{
   695  				Token: token.Token{
   696  					Name:     "Token 1",
   697  					Symbol:   "T1",
   698  					Decimals: 18,
   699  					Verified: true,
   700  					Address:  common.HexToAddress("0x789"),
   701  					ChainID:  1,
   702  				},
   703  				BalancesPerChain: map[uint64]token.ChainBalance{
   704  					1: {
   705  						RawBalance: "1000000000000000000",
   706  						Balance:    big.NewFloat(1),
   707  						Address:    common.HexToAddress("0x789"),
   708  						ChainID:    1,
   709  						HasError:   false,
   710  					},
   711  				},
   712  			},
   713  		},
   714  	}
   715  
   716  	expectedTokensPerAddress := map[common.Address][]token.StorageToken{
   717  		addresses[0]: {
   718  			{
   719  				Token: token.Token{
   720  					Name:     "Token 1",
   721  					Symbol:   "T1",
   722  					Decimals: 18,
   723  					Verified: true,
   724  				},
   725  				BalancesPerChain: map[uint64]token.ChainBalance{
   726  					1: {
   727  						RawBalance:     "1000000000000000000",
   728  						Balance:        big.NewFloat(1),
   729  						Address:        common.HexToAddress("0x789"),
   730  						ChainID:        1,
   731  						HasError:       false,
   732  						Balance1DayAgo: "0",
   733  					},
   734  				},
   735  			},
   736  		},
   737  		addresses[1]: {
   738  			{
   739  				Token: token.Token{
   740  					Name:     "Token 2",
   741  					Symbol:   "T2",
   742  					Decimals: 18,
   743  					Verified: true,
   744  				},
   745  				BalancesPerChain: map[uint64]token.ChainBalance{
   746  					1: {
   747  						RawBalance:     "2000000000000000000",
   748  						Balance:        big.NewFloat(2),
   749  						Address:        common.HexToAddress("0xdef"),
   750  						ChainID:        1,
   751  						HasError:       false,
   752  						Balance1DayAgo: "0",
   753  					},
   754  					2: {
   755  						RawBalance:     "3000000000000000000",
   756  						Balance:        big.NewFloat(3),
   757  						Address:        common.HexToAddress("0xabc"),
   758  						ChainID:        2,
   759  						HasError:       false,
   760  						Balance1DayAgo: "0",
   761  					},
   762  				},
   763  			},
   764  		},
   765  	}
   766  
   767  	reader, tokenManager, _, mockCtrl := setupReader(t)
   768  	defer mockCtrl.Finish()
   769  
   770  	tokenManager.EXPECT().GetTokenHistoricalBalance(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().Return(nil, nil)
   771  	tokens := reader.balancesToTokensByAddress(connectedPerChain, addresses, allTokens, balances, cachedTokens)
   772  
   773  	assert.Len(t, tokens, 2)
   774  	assert.Equal(t, 1, len(tokens[addresses[0]]))
   775  	assert.Equal(t, 1, len(tokens[addresses[1]]))
   776  
   777  	for _, address := range addresses {
   778  		for i, token := range tokens[address] {
   779  			assert.Equal(t, expectedTokensPerAddress[address][i].Token, token.Token)
   780  			testBalancePerChainEqual(t, expectedTokensPerAddress[address][i].BalancesPerChain, token.BalancesPerChain)
   781  		}
   782  	}
   783  }
   784  
   785  func TestGetCachedBalances(t *testing.T) {
   786  	reader, tokenManager, persistence, mockCtrl := setupReader(t)
   787  	defer mockCtrl.Finish()
   788  
   789  	addresses := []common.Address{
   790  		common.HexToAddress("0x123"),
   791  		common.HexToAddress("0x456"),
   792  	}
   793  
   794  	mockClientIface1 := mock_client.NewMockClientInterface(mockCtrl)
   795  	mockClientIface2 := mock_client.NewMockClientInterface(mockCtrl)
   796  	clients := map[uint64]chain.ClientInterface{
   797  		1: mockClientIface1,
   798  		2: mockClientIface2,
   799  	}
   800  
   801  	allTokens := []*token.Token{
   802  		{
   803  			Address:  common.HexToAddress("0xabc"),
   804  			Name:     "Token 1",
   805  			Symbol:   "T1",
   806  			Decimals: 18,
   807  			ChainID:  1,
   808  		},
   809  		{
   810  			Address:  common.HexToAddress("0xdef"),
   811  			Name:     "Token 2",
   812  			Symbol:   "T2",
   813  			Decimals: 18,
   814  			ChainID:  2,
   815  		},
   816  		{
   817  			Address:  common.HexToAddress("0x789"),
   818  			Name:     "Token 3",
   819  			Symbol:   "T3",
   820  			Decimals: 10,
   821  			ChainID:  1,
   822  		},
   823  	}
   824  
   825  	cachedTokens := map[common.Address][]token.StorageToken{
   826  		addresses[0]: {
   827  			{
   828  				Token: token.Token{
   829  					Address:  common.HexToAddress("0xabc"),
   830  					Name:     "Token 1",
   831  					Symbol:   "T1",
   832  					Decimals: 18,
   833  					ChainID:  1,
   834  				},
   835  				BalancesPerChain: nil,
   836  			},
   837  		},
   838  		addresses[1]: {
   839  			{
   840  				Token: token.Token{
   841  					Address:  common.HexToAddress("0xdef"),
   842  					Name:     "Token 2",
   843  					Symbol:   "T2",
   844  					Decimals: 18,
   845  					ChainID:  2,
   846  				},
   847  				BalancesPerChain: map[uint64]token.ChainBalance{
   848  					2: {
   849  						RawBalance:     "1000000000000000000",
   850  						Balance:        big.NewFloat(1),
   851  						Balance1DayAgo: "0",
   852  						Address:        common.HexToAddress("0xdef"),
   853  						ChainID:        2,
   854  						HasError:       false,
   855  					},
   856  				},
   857  			},
   858  		},
   859  	}
   860  
   861  	expectedTokens := map[common.Address][]token.StorageToken{
   862  		addresses[1]: {
   863  			{
   864  				Token: token.Token{
   865  					Name:     "Token 2",
   866  					Symbol:   "T2",
   867  					Decimals: 18,
   868  				},
   869  				BalancesPerChain: map[uint64]token.ChainBalance{
   870  					2: {
   871  						RawBalance:     "1000000000000000000",
   872  						Balance:        big.NewFloat(1),
   873  						Balance1DayAgo: "0",
   874  						Address:        common.HexToAddress("0xdef"),
   875  						ChainID:        2,
   876  						HasError:       false,
   877  					},
   878  				},
   879  			},
   880  		},
   881  	}
   882  
   883  	persistence.EXPECT().GetTokens().Return(cachedTokens, nil)
   884  	expectedChains := []uint64{1, 2}
   885  	tokenManager.EXPECT().GetTokensByChainIDs(testutils.NewUint64SliceMatcher(expectedChains)).Return(allTokens, nil)
   886  	tokenManager.EXPECT().GetTokenHistoricalBalance(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nil)
   887  	mockClientIface1.EXPECT().IsConnected().Return(true)
   888  	mockClientIface2.EXPECT().IsConnected().Return(true)
   889  	tokens, err := reader.GetCachedBalances(clients, addresses)
   890  	require.NoError(t, err)
   891  
   892  	for _, address := range addresses {
   893  		for i, token := range tokens[address] {
   894  			assert.Equal(t, expectedTokens[address][i].Token, token.Token)
   895  			testBalancePerChainEqual(t, expectedTokens[address][i].BalancesPerChain, token.BalancesPerChain)
   896  		}
   897  	}
   898  }
   899  
   900  func TestFetchBalances(t *testing.T) {
   901  	reader, tokenManager, persistence, mockCtrl := setupReader(t)
   902  	defer mockCtrl.Finish()
   903  
   904  	addresses := []common.Address{
   905  		common.HexToAddress("0x123"),
   906  		common.HexToAddress("0x456"),
   907  	}
   908  
   909  	mockClientIface1 := mock_client.NewMockClientInterface(mockCtrl)
   910  	mockClientIface2 := mock_client.NewMockClientInterface(mockCtrl)
   911  	clients := map[uint64]chain.ClientInterface{
   912  		1: mockClientIface1,
   913  		2: mockClientIface2,
   914  	}
   915  
   916  	allTokens := []*token.Token{
   917  		{
   918  			Address:  common.HexToAddress("0xabc"),
   919  			Name:     "Token 1",
   920  			Symbol:   "T1",
   921  			Decimals: 18,
   922  			ChainID:  1,
   923  		},
   924  		{
   925  			Address:  common.HexToAddress("0xdef"),
   926  			Name:     "Token 2",
   927  			Symbol:   "T2",
   928  			Decimals: 18,
   929  			ChainID:  2,
   930  		},
   931  		{
   932  			Address:  common.HexToAddress("0x789"),
   933  			Name:     "Token 3",
   934  			Symbol:   "T3",
   935  			Decimals: 10,
   936  			ChainID:  1,
   937  		},
   938  	}
   939  
   940  	cachedTokens := map[common.Address][]token.StorageToken{
   941  		addresses[0]: {
   942  			{
   943  				Token: token.Token{
   944  					Address:  common.HexToAddress("0xabc"),
   945  					Name:     "Token 1",
   946  					Symbol:   "T1",
   947  					Decimals: 18,
   948  					ChainID:  1,
   949  				},
   950  				BalancesPerChain: nil,
   951  			},
   952  		},
   953  		addresses[1]: {
   954  			{
   955  				Token: token.Token{
   956  					Address:  common.HexToAddress("0xdef"),
   957  					Name:     "Token 2",
   958  					Symbol:   "T2",
   959  					Decimals: 18,
   960  					ChainID:  2,
   961  				},
   962  				BalancesPerChain: map[uint64]token.ChainBalance{
   963  					2: {
   964  						RawBalance:     "1000000000000000000",
   965  						Balance:        big.NewFloat(1),
   966  						Balance1DayAgo: "0",
   967  						Address:        common.HexToAddress("0xdef"),
   968  						ChainID:        2,
   969  						HasError:       false,
   970  					},
   971  				},
   972  			},
   973  		},
   974  	}
   975  
   976  	expectedTokens := map[common.Address][]token.StorageToken{
   977  		addresses[1]: {
   978  			{
   979  				Token: token.Token{
   980  					Name:     "Token 2",
   981  					Symbol:   "T2",
   982  					Decimals: 18,
   983  				},
   984  				BalancesPerChain: map[uint64]token.ChainBalance{
   985  					2: {
   986  						RawBalance:     "2000000000000000000",
   987  						Balance:        big.NewFloat(2),
   988  						Balance1DayAgo: "0",
   989  						Address:        common.HexToAddress("0xdef"),
   990  						ChainID:        2,
   991  						HasError:       false,
   992  					},
   993  				},
   994  			},
   995  		},
   996  	}
   997  
   998  	persistence.EXPECT().GetTokens().Times(2).Return(cachedTokens, nil)
   999  	// Verify that proper tokens are saved
  1000  	persistence.EXPECT().SaveTokens(newMapTokenWithBalanceMatcher([]interface{}{expectedTokens})).Return(nil)
  1001  	expectedChains := []uint64{1, 2}
  1002  	tokenManager.EXPECT().GetTokensByChainIDs(testutils.NewUint64SliceMatcher(expectedChains)).Return(allTokens, nil)
  1003  	tokenManager.EXPECT().GetTokenHistoricalBalance(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nil)
  1004  	expectedBalances := map[uint64]map[common.Address]map[common.Address]*hexutil.Big{
  1005  		2: {
  1006  			addresses[1]: {
  1007  				allTokens[1].Address: (*hexutil.Big)(big.NewInt(2000000000000000000)),
  1008  			},
  1009  		},
  1010  	}
  1011  
  1012  	tokenAddresses := getTokenAddresses(allTokens)
  1013  	tokenManager.EXPECT().GetBalancesByChain(context.TODO(), clients, addresses, testutils.NewAddressSliceMatcher(tokenAddresses)).Return(expectedBalances, nil)
  1014  	mockClientIface1.EXPECT().IsConnected().Return(true)
  1015  	mockClientIface2.EXPECT().IsConnected().Return(true)
  1016  	tokens, err := reader.FetchBalances(context.TODO(), clients, addresses)
  1017  	require.NoError(t, err)
  1018  
  1019  	for address, tokenList := range tokens {
  1020  		for i, token := range tokenList {
  1021  			assert.Equal(t, expectedTokens[address][i].Token, token.Token)
  1022  			testBalancePerChainEqual(t, expectedTokens[address][i].BalancesPerChain, token.BalancesPerChain)
  1023  		}
  1024  	}
  1025  
  1026  	require.True(t, reader.isBalanceCacheValid(addresses))
  1027  }
  1028  
  1029  func TestReaderRestart(t *testing.T) {
  1030  	reader, _, _, mockCtrl := setupReader(t)
  1031  	defer mockCtrl.Finish()
  1032  
  1033  	err := reader.Start()
  1034  	require.NoError(t, err)
  1035  	require.NotNil(t, reader.walletEventsWatcher)
  1036  	previousWalletEventsWatcher := reader.walletEventsWatcher
  1037  
  1038  	err = reader.Restart()
  1039  	require.NoError(t, err)
  1040  	require.NotNil(t, reader.walletEventsWatcher)
  1041  	require.NotEqual(t, previousWalletEventsWatcher, reader.walletEventsWatcher)
  1042  }
  1043  
  1044  func TestFetchOrGetCachedWalletBalances(t *testing.T) {
  1045  	// Test the behavior of FetchOrGetCachedWalletBalances when fetching new balances fails.
  1046  	// This focuses on the error handling path where the function should return the cached balances as a fallback.
  1047  	// We don't explicitly test the contents of fetched or cached balances here, as those
  1048  	// are covered in other dedicated tests. The main goal is to ensure the correct flow of
  1049  	// execution and data retrieval in this specific failure scenario.
  1050  
  1051  	reader, _, tokenPersistence, mockCtrl := setupReader(t)
  1052  	defer mockCtrl.Finish()
  1053  
  1054  	reader.invalidateBalanceCache()
  1055  
  1056  	tokenPersistence.EXPECT().GetTokens().Return(nil, errors.New("error")).AnyTimes()
  1057  
  1058  	clients := map[uint64]chain.ClientInterface{}
  1059  	addresses := []common.Address{}
  1060  
  1061  	_, err := reader.FetchOrGetCachedWalletBalances(context.TODO(), clients, addresses, false)
  1062  	require.Error(t, err)
  1063  }