package api_test import ( gosql "database/sql" "fmt" "math" "math/big" "sort" "time" "github.com/ethereum/go-ethereum/crypto" . "github.com/stretchr/testify/assert" "github.com/synapsecns/sanguine/services/explorer/graphql/server/graph/model" "github.com/brianvoe/gofakeit/v6" "github.com/ethereum/go-ethereum/common" "github.com/synapsecns/sanguine/services/explorer/db/sql" ) //nolint:cyclop func (g APISuite) TestAddressRanking() { var chainID uint32 chainIDs := []uint32{g.chainIDs[0], g.chainIDs[1], g.chainIDs[2]} destinationChainIDA := g.chainIDs[3] destinationChainIDB := g.chainIDs[4] contractAddress := common.BigToAddress(big.NewInt(gofakeit.Int64())).String() address := common.BigToAddress(big.NewInt(gofakeit.Int64())) // used for validation later var addressesTried = make(map[string]int) // this counter lets us have a random variation in address occurrence resetTokenAddrCounter := gofakeit.Number(1, 3) // random token addr tokenAddr := common.BigToAddress(big.NewInt(gofakeit.Int64())).String() // for holding the current token addr in line the gofakeit.Bool() decides to pass true lastTokenAddr := tokenAddr // Generate bridge events for different chain IDs. for blockNumber := uint64(1); blockNumber <= 10; blockNumber++ { var destinationChainID uint32 if blockNumber%2 == 0 { destinationChainID = destinationChainIDA } else { destinationChainID = destinationChainIDB } // if the token counter is zero reset it if resetTokenAddrCounter == 0 { tokenAddr = common.BigToAddress(big.NewInt(gofakeit.Int64())).String() lastTokenAddr = tokenAddr resetTokenAddrCounter = gofakeit.Number(1, 3) } else { // before using the current token addr, let throw in some randomness if gofakeit.Bool() { tokenAddr = common.BigToAddress(big.NewInt(gofakeit.Int64())).String() } else { resetTokenAddrCounter-- } } currentTime := uint64(time.Now().Unix()) // change up chainID (1/3 chance of using a new chain) chainID = chainIDs[gofakeit.Number(0, 2)] txHash := common.BigToHash(big.NewInt(gofakeit.Int64())) g.db.UNSAFE_DB().WithContext(g.GetTestContext()).Create(&sql.BridgeEvent{ InsertTime: 1, ChainID: chainID, Recipient: gosql.NullString{String: address.String(), Valid: true}, DestinationChainID: big.NewInt(int64(destinationChainID)), BlockNumber: blockNumber, TxHash: txHash.String(), EventIndex: gofakeit.Uint64(), Token: tokenAddr, Sender: tokenAddr, TimeStamp: ¤tTime, }) g.db.UNSAFE_DB().WithContext(g.GetTestContext()).Create(&sql.TokenIndex{ ChainID: chainID, TokenAddress: tokenAddr, ContractAddress: contractAddress, TokenIndex: 1, }) // add the tokenAddr inserted to the test map (for validation later) addressesTried[tokenAddr]++ // Set all times after current time, so we can get the events. err := g.eventDB.StoreBlockTime(g.GetTestContext(), chainIDs[0], blockNumber, uint64(time.Now().Unix())*blockNumber) Nil(g.T(), err) err = g.eventDB.StoreBlockTime(g.GetTestContext(), chainIDs[1], blockNumber, uint64(time.Now().Unix())*blockNumber) Nil(g.T(), err) err = g.eventDB.StoreBlockTime(g.GetTestContext(), chainIDs[2], blockNumber, uint64(time.Now().Unix())*blockNumber) Nil(g.T(), err) err = g.eventDB.StoreBlockTime(g.GetTestContext(), destinationChainIDA, blockNumber, uint64(time.Now().Unix())*blockNumber) Nil(g.T(), err) err = g.eventDB.StoreBlockTime(g.GetTestContext(), destinationChainIDB, blockNumber, uint64(time.Now().Unix())*blockNumber) Nil(g.T(), err) // if a random address was inserted, revert to address corresponding to resetTokenAddrCounter if lastTokenAddr != tokenAddr { tokenAddr = lastTokenAddr } } blockNumberInit := uint64(10) err := g.eventDB.StoreBlockTime(g.GetTestContext(), chainID, blockNumberInit, uint64(time.Now().Unix())*blockNumberInit) Nil(g.T(), err) result, err := g.client.GetAddressRanking(g.GetTestContext(), nil) Nil(g.T(), err) NotNil(g.T(), result) // check if the length of the response is same to the number of unique addresses inserted into test db Equal(g.T(), len(addressesTried), len(result.Response)) // Validate contents of response by comparing to addressesTried for k, v := range addressesTried { for _, res := range result.Response { if *res.Address == k { Equal(g.T(), v, *res.Count) } } } } //nolint:cyclop func (g APISuite) TestGetCountByChainID() { chainID := g.chainIDs[0] chainID2 := g.chainIDs[1] chainID3 := g.chainIDs[2] address := common.BigToAddress(big.NewInt(gofakeit.Int64())) contractAddress := common.BigToAddress(big.NewInt(gofakeit.Int64())).String() tokenAddress := common.BigToAddress(big.NewInt(gofakeit.Int64())).String() // Generate bridge events for different chain IDs. for blockNumber := uint64(1); blockNumber <= 10; blockNumber++ { var destinationChainID int64 var inputChain uint32 destinationChainID = int64(g.chainIDs[1]) inputChain = chainID if blockNumber > 1 { if blockNumber%2 == 0 { inputChain = chainID2 destinationChainID = 0 } else { inputChain = chainID3 destinationChainID = int64(g.chainIDs[0]) } } currentTime := uint64(time.Now().Unix()) txHash := common.BigToHash(big.NewInt(gofakeit.Int64())) g.db.UNSAFE_DB().WithContext(g.GetTestContext()).Create(&sql.BridgeEvent{ ChainID: inputChain, EventType: gofakeit.Uint8(), DestinationChainID: big.NewInt(destinationChainID), Recipient: gosql.NullString{String: address.String(), Valid: true}, BlockNumber: blockNumber, TxHash: txHash.String(), EventIndex: gofakeit.Uint64(), TimeStamp: ¤tTime, ContractAddress: contractAddress, Token: tokenAddress, }) g.db.UNSAFE_DB().WithContext(g.GetTestContext()).Create(&sql.TokenIndex{ ChainID: chainID, TokenAddress: tokenAddress, ContractAddress: contractAddress, TokenIndex: 1, }) // Set all times after current time, so we can get the events. err := g.eventDB.StoreBlockTime(g.GetTestContext(), chainID, blockNumber, uint64(time.Now().Unix())*blockNumber) Nil(g.T(), err) err = g.eventDB.StoreBlockTime(g.GetTestContext(), chainID2, blockNumber, uint64(time.Now().Unix())*blockNumber) Nil(g.T(), err) err = g.eventDB.StoreBlockTime(g.GetTestContext(), chainID3, blockNumber, uint64(time.Now().Unix())*blockNumber) Nil(g.T(), err) } addressRef := address.String() directionRef := model.DirectionOut resultOut, err := g.client.GetCountByChainID(g.GetTestContext(), nil, &addressRef, &directionRef, nil) Nil(g.T(), err) // There should be 3 chains, 2 for the destination chain IDs and 1 for the source chain ID. Equal(g.T(), 1, len(resultOut.Response)) // The source chain ID should have 10 events out, and the destination chain IDs should have 0 events out. var reached = 0 for _, res := range resultOut.Response { switch *res.ChainID { case int(chainID): Equal(g.T(), 1, *res.Count) reached++ case int(chainID2): Equal(g.T(), 5, *res.Count) reached++ case int(chainID3): Equal(g.T(), 4, *res.Count) reached++ } } Equal(g.T(), 1, reached) directionRef = model.DirectionIn resultIn, err := g.client.GetCountByChainID(g.GetTestContext(), nil, &addressRef, &directionRef, nil) Nil(g.T(), err) // Again, there should be 3 chains, 2 for the destination chain IDs and 1 for the source chain ID. Equal(g.T(), 2, len(resultIn.Response)) // The source chain ID should have 0 events in, and the destination chain IDs should have 5 events in. reached = 0 for _, res := range resultIn.Response { switch *res.ChainID { case int(chainID): Equal(g.T(), 1, *res.Count) reached++ case int(chainID2): Equal(g.T(), 5, *res.Count) reached++ case int(chainID3): Equal(g.T(), 4, *res.Count) reached++ } } Equal(g.T(), 2, reached) } // nolint (needed for testing all possibilities) func (g APISuite) TestGetCountByTokenAddress() { chainID := g.chainIDs[0] destinationChainID := g.chainIDs[1] tokenAddressA := common.BigToAddress(big.NewInt(gofakeit.Int64())) tokenAddressB := common.BigToAddress(big.NewInt(gofakeit.Int64())) address := common.BigToAddress(big.NewInt(gofakeit.Int64())) contractAddress := common.BigToAddress(big.NewInt(gofakeit.Int64())).String() // Generate bridge events for different chain IDs. for blockNumber := uint64(1); blockNumber <= 10; blockNumber++ { var tokenAddress common.Address if blockNumber%2 == 0 { tokenAddress = tokenAddressA destinationChainID = g.chainIDs[1] } else { tokenAddress = tokenAddressB destinationChainID = 0 } currentTime := uint64(time.Now().Unix()) txHash := common.BigToHash(big.NewInt(gofakeit.Int64())) g.db.UNSAFE_DB().WithContext(g.GetTestContext()).Create(&sql.BridgeEvent{ ChainID: chainID, EventType: gofakeit.Uint8(), Recipient: gosql.NullString{String: address.String(), Valid: true}, DestinationChainID: big.NewInt(int64(destinationChainID)), Token: tokenAddress.String(), BlockNumber: blockNumber, TxHash: txHash.String(), EventIndex: gofakeit.Uint64(), TimeStamp: ¤tTime, ContractAddress: contractAddress, }) g.db.UNSAFE_DB().WithContext(g.GetTestContext()).Create(&sql.TokenIndex{ ChainID: chainID, TokenAddress: tokenAddress.String(), ContractAddress: contractAddress, TokenIndex: 1, }) // Set all times after current time, so we can get the events. err := g.eventDB.StoreBlockTime(g.GetTestContext(), chainID, blockNumber, uint64(time.Now().Unix())*blockNumber) Nil(g.T(), err) err = g.eventDB.StoreBlockTime(g.GetTestContext(), destinationChainID, blockNumber, uint64(time.Now().Unix())*blockNumber) Nil(g.T(), err) } addressRef := address.String() directionRef := model.DirectionOut resultOut, err := g.client.GetCountByTokenAddress(g.GetTestContext(), nil, &addressRef, &directionRef, nil) Nil(g.T(), err) Equal(g.T(), 1, len(resultOut.Response)) reached := 0 for _, res := range resultOut.Response { if *res.ChainID == int(chainID) { if *res.TokenAddress == tokenAddressA.String() { Equal(g.T(), 5, *res.Count) reached++ } if *res.TokenAddress == tokenAddressB.String() { Equal(g.T(), 5, *res.Count) reached++ } } if *res.ChainID == int(destinationChainID) { if *res.TokenAddress == tokenAddressA.String() { Equal(g.T(), 5, *res.Count) reached++ } if *res.TokenAddress == tokenAddressB.String() { Equal(g.T(), 5, *res.Count) reached++ } } } Equal(g.T(), 1, reached) directionRef = model.DirectionIn resultIn, err := g.client.GetCountByTokenAddress(g.GetTestContext(), nil, nil, &directionRef, nil) Nil(g.T(), err) Equal(g.T(), 1, len(resultIn.Response)) reached = 0 for _, res := range resultIn.Response { if *res.ChainID == int(destinationChainID) { if *res.TokenAddress == tokenAddressA.String() { Equal(g.T(), *res.Count, 5) reached++ } if *res.TokenAddress == tokenAddressB.String() { Equal(g.T(), *res.Count, 5) reached++ } } if *res.ChainID == int(chainID) { if *res.TokenAddress == tokenAddressA.String() { Equal(g.T(), 5, *res.Count) reached++ } if *res.TokenAddress == tokenAddressB.String() { Equal(g.T(), 5, *res.Count) reached++ } } } Equal(g.T(), 1, reached) } // TODO add other platforms to make this test more exhaustive // nolint:cyclop func (g APISuite) TestDailyStatisticsByChain() { chainID := g.chainIDs[0] destinationChainIDA := g.chainIDs[1] destinationChainIDB := g.chainIDs[2] address := common.BigToAddress(big.NewInt(gofakeit.Int64())) tokenAddr := common.BigToAddress(big.NewInt(gofakeit.Int64())).String() nowTime := time.Now().Unix() senders := []string{common.BigToHash(big.NewInt(gofakeit.Int64())).String(), common.BigToHash(big.NewInt(gofakeit.Int64())).String(), common.BigToHash(big.NewInt(gofakeit.Int64())).String()} cumulativePrice := []float64{} contract := common.BigToHash(big.NewInt(gofakeit.Int64())) // Generate bridge events for different chain IDs. for blockNumber := uint64(1); blockNumber <= 10; blockNumber++ { var destinationChainID uint32 if blockNumber%2 == 0 { destinationChainID = destinationChainIDA } else { destinationChainID = destinationChainIDB } price := float64(gofakeit.Number(1, 300)) cumulativePrice = append(cumulativePrice, price) txHash := common.BigToHash(big.NewInt(gofakeit.Int64())) timestamp := uint64(nowTime) - (10*blockNumber)*86400 g.db.UNSAFE_DB().WithContext(g.GetTestContext()).Create(&sql.BridgeEvent{ ChainID: chainID, ContractAddress: contract.String(), EventType: gofakeit.Uint8(), Recipient: gosql.NullString{String: address.String(), Valid: true}, DestinationChainID: big.NewInt(int64(destinationChainID)), BlockNumber: blockNumber, TxHash: txHash.String(), EventIndex: gofakeit.Uint64(), Token: tokenAddr, Amount: big.NewInt(int64(gofakeit.Number(1, 300))), AmountUSD: &price, Sender: senders[blockNumber%3], TimeStamp: ×tamp, }) g.db.UNSAFE_DB().WithContext(g.GetTestContext()).Create(&sql.TokenIndex{ ChainID: chainID, TokenAddress: tokenAddr, TokenIndex: 1, }) // Set all times after current time, so we can get the events. err := g.eventDB.StoreBlockTime(g.GetTestContext(), chainID, blockNumber, uint64(time.Now().Unix())*blockNumber) Nil(g.T(), err) err = g.eventDB.StoreBlockTime(g.GetTestContext(), destinationChainIDA, blockNumber, uint64(time.Now().Unix())*blockNumber) Nil(g.T(), err) err = g.eventDB.StoreBlockTime(g.GetTestContext(), destinationChainIDB, blockNumber, uint64(time.Now().Unix())*blockNumber) Nil(g.T(), err) } total := 0.0 for _, v := range cumulativePrice { total += v } platform := model.PlatformBridge days := model.DurationAllTime typeArg := model.DailyStatisticTypeVolume result, err := g.client.GetDailyStatisticsByChain(g.GetTestContext(), nil, &typeArg, &days, &platform, nil) Nil(g.T(), err) NotNil(g.T(), result) Equal(g.T(), cumulativePrice[len(cumulativePrice)-1], *result.Response[0].Total) Equal(g.T(), len(cumulativePrice), len(result.Response)) typeArg = model.DailyStatisticTypeAddresses result, err = g.client.GetDailyStatisticsByChain(g.GetTestContext(), nil, &typeArg, &days, &platform, nil) Nil(g.T(), err) NotNil(g.T(), result) Equal(g.T(), float64(1), *result.Response[0].Total) Equal(g.T(), len(cumulativePrice), len(result.Response)) typeArg = model.DailyStatisticTypeTransactions result, err = g.client.GetDailyStatisticsByChain(g.GetTestContext(), nil, &typeArg, &days, &platform, nil) Nil(g.T(), err) NotNil(g.T(), result) Equal(g.T(), float64(1), *result.Response[0].Total) Equal(g.T(), len(cumulativePrice), len(result.Response)) } // TODO add swap txs. func (g APISuite) TestGetBridgeTransactions() { chainID := g.chainIDs[0] destinationChainID := g.chainIDs[1] contractAddr := common.BigToAddress(big.NewInt(gofakeit.Int64())).String() tokenAddress := common.BigToAddress(big.NewInt(gofakeit.Int64())).String() address := common.BigToAddress(big.NewInt(gofakeit.Int64())) senderAddress := common.BigToAddress(big.NewInt(gofakeit.Int64())) senderString := senderAddress.String() txHashA := common.BigToHash(big.NewInt(gofakeit.Int64())) txHashB := common.BigToHash(big.NewInt(gofakeit.Int64())) kappaString := crypto.Keccak256Hash(txHashA.Bytes()).String() txHashString := txHashA.String() amount := big.NewInt(int64(gofakeit.Uint64())) amountUSD := float64(gofakeit.Number(1, 300)) tokenDecimals := uint8(gofakeit.Number(0, 3)) tokenSymbol := gofakeit.Word() timestamp := uint64(time.Now().Unix()) page := 1 g.db.UNSAFE_DB().WithContext(g.GetTestContext()).Create(&sql.BridgeEvent{ InsertTime: 1, ContractAddress: common.BigToAddress(big.NewInt(gofakeit.Int64())).String(), ChainID: chainID, EventType: gofakeit.Uint8(), Sender: senderString, Recipient: gosql.NullString{String: address.String(), Valid: true}, DestinationChainID: big.NewInt(int64(destinationChainID)), Token: tokenAddress, BlockNumber: 1, TxHash: txHashA.String(), DestinationKappa: kappaString, EventIndex: gofakeit.Uint64(), Amount: amount, AmountUSD: &amountUSD, TokenDecimal: &tokenDecimals, TokenSymbol: gosql.NullString{String: tokenSymbol, Valid: true}, TimeStamp: ×tamp, }) g.db.UNSAFE_DB().WithContext(g.GetTestContext()).Create(&sql.TokenIndex{ ChainID: chainID, TokenAddress: tokenAddress, TokenIndex: 1, ContractAddress: contractAddr, }) g.db.UNSAFE_DB().WithContext(g.GetTestContext()).Create(&sql.BridgeEvent{ InsertTime: 1, ChainID: destinationChainID, EventType: gofakeit.Uint8(), Recipient: gosql.NullString{String: address.String(), Valid: true}, Token: tokenAddress, BlockNumber: 1, TxHash: txHashB.String(), Kappa: gosql.NullString{String: kappaString, Valid: true}, SwapSuccess: big.NewInt(1), EventIndex: gofakeit.Uint64(), Amount: amount, AmountUSD: &amountUSD, TokenDecimal: &tokenDecimals, Sender: gofakeit.Word(), TokenSymbol: gosql.NullString{String: tokenSymbol, Valid: true}, TimeStamp: ×tamp, ContractAddress: contractAddr, }) g.db.UNSAFE_DB().WithContext(g.GetTestContext()).Create(&sql.TokenIndex{ ChainID: destinationChainID, TokenAddress: tokenAddress, ContractAddress: contractAddr, TokenIndex: 1, }) err := g.eventDB.StoreBlockTime(g.GetTestContext(), chainID, 1, timestamp) Nil(g.T(), err) err = g.eventDB.StoreBlockTime(g.GetTestContext(), destinationChainID, 1, timestamp) Nil(g.T(), err) pending := false //nolint:dupword originRes, err := g.client.GetBridgeTransactions(g.GetTestContext(), nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, &txHashString, nil, &pending, &page, nil, nil, nil) Nil(g.T(), err) Equal(g.T(), 1, len(originRes.Response)) originResOne := *originRes.Response[0] Equal(g.T(), kappaString, *originResOne.Kappa) // do pending Equal(g.T(), *originResOne.SwapSuccess, true) fromInfo := *originResOne.FromInfo Equal(g.T(), int(chainID), *fromInfo.ChainID) Equal(g.T(), address.String(), *fromInfo.Address) Equal(g.T(), txHashA.String(), *fromInfo.TxnHash) Equal(g.T(), amount.String(), *fromInfo.Value) Equal(g.T(), amountUSD, *fromInfo.USDValue) formattedValue := uint64((float64(amount.Int64()) / math.Pow10(int(tokenDecimals))) * 1000000) Equal(g.T(), formattedValue, uint64(*fromInfo.FormattedValue*1000000)) Equal(g.T(), tokenSymbol, *fromInfo.TokenSymbol) Equal(g.T(), tokenAddress, *fromInfo.TokenAddress) Equal(g.T(), 1, *fromInfo.BlockNumber) Equal(g.T(), int(timestamp), *fromInfo.Time) toInfo := *originResOne.ToInfo Equal(g.T(), int(destinationChainID), *toInfo.ChainID) Equal(g.T(), address.String(), *toInfo.Address) Equal(g.T(), txHashB.String(), *toInfo.TxnHash) Equal(g.T(), amount.String(), *toInfo.Value) Equal(g.T(), amountUSD, *toInfo.USDValue) Equal(g.T(), formattedValue, uint64(*toInfo.FormattedValue*1000000)) Equal(g.T(), tokenSymbol, *toInfo.TokenSymbol) Equal(g.T(), tokenAddress, *toInfo.TokenAddress) Equal(g.T(), 1, *toInfo.BlockNumber) Equal(g.T(), int(timestamp), *toInfo.Time) pending = false //nolint:dupword destinationRes, err := g.client.GetBridgeTransactions(g.GetTestContext(), nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, &kappaString, &pending, &page, nil, nil, nil) Nil(g.T(), err) Equal(g.T(), 1, len(destinationRes.Response)) destinationResOne := *destinationRes.Response[0] Equal(g.T(), originResOne, destinationResOne) pending = true addressRes, err := g.client.GetBridgeTransactions(g.GetTestContext(), nil, nil, nil, &senderString, nil, nil, nil, nil, nil, nil, nil, nil, &pending, &page, nil, nil, nil) Nil(g.T(), err) Equal(g.T(), 1, len(addressRes.Response)) addressResOne := *addressRes.Response[0] Equal(g.T(), originResOne, addressResOne) } func (g APISuite) TestLeaderboard() { chainID := g.chainIDs[0] address := common.BigToAddress(big.NewInt(gofakeit.Int64())) var addressNS gosql.NullString addressNS.String = address.String() addressNS.Valid = true tokenAddr := common.BigToAddress(big.NewInt(gofakeit.Int64())).String() senders := []string{common.BigToHash(big.NewInt(gofakeit.Int64())).String(), common.BigToHash(big.NewInt(gofakeit.Int64())).String(), common.BigToHash(big.NewInt(gofakeit.Int64())).String()} nowTime := time.Now().Unix() contract := common.BigToHash(big.NewInt(gofakeit.Int64())) // Generate bridge events for different chain IDs. for blockNumber := uint64(1); blockNumber <= 10; blockNumber++ { price := float64(gofakeit.Number(1, 300)) txHash := common.BigToHash(big.NewInt(gofakeit.Int64())) timestamp := uint64(nowTime) - (10*blockNumber)*86400 g.db.UNSAFE_DB().WithContext(g.GetTestContext()).Create(&MvBridgeEvent{ FChainID: chainID, FContractAddress: contract.String(), FEventType: gofakeit.Uint8(), FBlockNumber: blockNumber, FTxHash: txHash.String(), FEventIndex: gofakeit.Uint64(), FAmountUSD: &price, FFeeAmountUSD: &price, FSender: senders[blockNumber%3], FTimeStamp: ×tamp, TChainID: chainID, TContractAddress: contract.String(), TEventType: gofakeit.Uint8(), TBlockNumber: blockNumber, TTxHash: txHash.String(), TEventIndex: gofakeit.Uint64(), TAmountUSD: &price, TFeeAmountUSD: &price, TSender: senders[blockNumber%3], TTimeStamp: ×tamp, }) g.db.UNSAFE_DB().WithContext(g.GetTestContext()).Create(&sql.TokenIndex{ ChainID: chainID, TokenAddress: tokenAddr, TokenIndex: 1, }) // Set all times after current time, so we can get the events. err := g.eventDB.StoreBlockTime(g.GetTestContext(), chainID, blockNumber, uint64(time.Now().Unix())*blockNumber) Nil(g.T(), err) } useMv := true page := 1 duration := model.DurationAllTime result, err := g.client.GetLeaderboard(g.GetTestContext(), &duration, nil, &useMv, &page) Nil(g.T(), err) NotNil(g.T(), result) for i := 0; i < len(result.Response); i++ { NotNil(g.T(), result.Response[i].Address) NotNil(g.T(), result.Response[i].VolumeUsd) NotNil(g.T(), result.Response[i].Fees) NotNil(g.T(), result.Response[i].Txs) NotNil(g.T(), result.Response[i].Rank) NotNil(g.T(), result.Response[i].AvgVolumeUsd) } } // TODO rewrite this test so that it is exhaustive with all platform and statistic types. // nolint:cyclop func (g APISuite) TestAmountStatistic() { chainID := g.chainIDs[0] destinationChainIDA := g.chainIDs[1] destinationChainIDB := g.chainIDs[2] address := common.BigToAddress(big.NewInt(gofakeit.Int64())) contractAddress := common.BigToAddress(big.NewInt(gofakeit.Int64())).String() tokenAddr := common.BigToAddress(big.NewInt(gofakeit.Int64())).String() sender := common.BigToAddress(big.NewInt(gofakeit.Int64())).String() cumulativePrice := []float64{} // Generate bridge events for different chain IDs. for blockNumber := uint64(1); blockNumber <= 10; blockNumber++ { var destinationChainID uint32 if blockNumber%2 == 0 { destinationChainID = destinationChainIDA } else { destinationChainID = destinationChainIDB } currentTime := uint64(time.Now().Unix()) price := float64(gofakeit.Number(1, 300)) cumulativePrice = append(cumulativePrice, price) txHash := common.BigToHash(big.NewInt(gofakeit.Int64())) g.db.UNSAFE_DB().WithContext(g.GetTestContext()).Create(&MvBridgeEvent{ InsertTime: 1, FChainID: chainID, FContractAddress: contractAddress, FEventType: gofakeit.Uint8(), FBlockNumber: blockNumber, FTxHash: txHash.String(), FEventIndex: gofakeit.Uint64(), FAmountUSD: &price, FFeeAmountUSD: &price, FRecipient: gosql.NullString{String: address.String(), Valid: true}, FSender: sender, FTimeStamp: ¤tTime, TChainID: destinationChainID, TContractAddress: contractAddress, TEventType: gofakeit.Uint8(), TBlockNumber: blockNumber, TTxHash: txHash.String(), TEventIndex: gofakeit.Uint64(), TAmountUSD: &price, TFeeAmountUSD: &price, TSender: sender, TTimeStamp: ¤tTime, }) g.db.UNSAFE_DB().WithContext(g.GetTestContext()).Create(&sql.TokenIndex{ ChainID: chainID, TokenAddress: tokenAddr, ContractAddress: contractAddress, TokenIndex: 1, }) // Set all times after current time, so we can get the events. err := g.eventDB.StoreBlockTime(g.GetTestContext(), chainID, blockNumber, uint64(time.Now().Unix())*blockNumber) Nil(g.T(), err) err = g.eventDB.StoreBlockTime(g.GetTestContext(), destinationChainIDA, blockNumber, uint64(time.Now().Unix())*blockNumber) Nil(g.T(), err) err = g.eventDB.StoreBlockTime(g.GetTestContext(), destinationChainIDB, blockNumber, uint64(time.Now().Unix())*blockNumber) Nil(g.T(), err) } total := 0.0 for _, v := range cumulativePrice { total += v } count := float64(len(cumulativePrice)) mean := total / count var median float64 sort.Float64s(cumulativePrice) switch { case count == 0: median = 0.0 case len(cumulativePrice)%2 == 0: median = (cumulativePrice[len(cumulativePrice)/2-1] + cumulativePrice[len(cumulativePrice)/2]) / 2 default: median = cumulativePrice[len(cumulativePrice)/2] } statType := model.StatisticTypeTotalVolumeUsd duration := model.DurationAllTime platform := model.PlatformBridge // nolint:dupword result, err := g.client.GetAmountStatistic(g.GetTestContext(), statType, &platform, &duration, nil, nil, nil, nil) Nil(g.T(), err) NotNil(g.T(), result) Equal(g.T(), fmt.Sprintf("%f", total), *result.Response.Value) statType = model.StatisticTypeCountTransactions // nolint:dupword result, err = g.client.GetAmountStatistic(g.GetTestContext(), statType, &platform, &duration, nil, nil, nil, nil) Nil(g.T(), err) NotNil(g.T(), result) Equal(g.T(), fmt.Sprintf("%f", count), *result.Response.Value) statType = model.StatisticTypeMeanVolumeUsd // nolint:dupword result, err = g.client.GetAmountStatistic(g.GetTestContext(), statType, &platform, &duration, nil, nil, nil, nil) Nil(g.T(), err) NotNil(g.T(), result) Equal(g.T(), fmt.Sprintf("%f", mean), *result.Response.Value) statType = model.StatisticTypeMedianVolumeUsd result, err = g.client.GetAmountStatistic(g.GetTestContext(), statType, &platform, &duration, nil, nil, nil, nil) Nil(g.T(), err) NotNil(g.T(), result) Equal(g.T(), fmt.Sprintf("%f", median), *result.Response.Value) statType = model.StatisticTypeCountAddresses result, err = g.client.GetAmountStatistic(g.GetTestContext(), statType, &platform, &duration, nil, nil, nil, nil) Nil(g.T(), err) NotNil(g.T(), result) Equal(g.T(), "1.000000", *result.Response.Value) } func (g APISuite) TestGetBlockHeight() { chainID1 := 1 chainID2 := 56 type1 := model.ContractTypeCctp type2 := model.ContractTypeBridge contract1 := g.config.Chains[uint32(chainID1)].Contracts.CCTP contract2 := g.config.Chains[uint32(chainID2)].Contracts.Bridge block1 := uint64(3) block2 := uint64(4) contracts := []*model.ContractQuery{ { ChainID: chainID1, Type: type1, }, { ChainID: chainID2, Type: type2, }, } // Store blocks in the database. err := g.db.StoreLastBlock(g.GetTestContext(), uint32(chainID1), block1, contract1) Nil(g.T(), err) err = g.db.StoreLastBlock(g.GetTestContext(), uint32(chainID2), block2, contract2) Nil(g.T(), err) results, err := g.client.GetBlockHeight(g.GetTestContext(), contracts) Nil(g.T(), err) Equal(g.T(), 2, len(results.Response)) Equal(g.T(), int(block1), *results.Response[0].BlockNumber) Equal(g.T(), int(block2), *results.Response[1].BlockNumber) }