github.com/diadata-org/diadata@v1.4.593/pkg/dia/scraper/liquidity-scrapers/BalancerV2Scraper.go (about)

     1  package liquidityscrapers
     2  
     3  import (
     4  	"context"
     5  	"math"
     6  	"math/big"
     7  	"strings"
     8  	"time"
     9  
    10  	"github.com/ethereum/go-ethereum/accounts/abi/bind"
    11  	"github.com/ethereum/go-ethereum/common"
    12  	"github.com/ethereum/go-ethereum/ethclient"
    13  
    14  	"github.com/diadata-org/diadata/pkg/dia/helpers/ethhelper"
    15  	balancervault "github.com/diadata-org/diadata/pkg/dia/scraper/exchange-scrapers/balancerv2/vault"
    16  	models "github.com/diadata-org/diadata/pkg/model"
    17  	"github.com/diadata-org/diadata/pkg/utils"
    18  
    19  	"github.com/diadata-org/diadata/pkg/dia"
    20  )
    21  
    22  const (
    23  	balancerV2FilterPageSize = 5000
    24  	balancerV2RestDial       = ""
    25  )
    26  
    27  type BalancerV2Scraper struct {
    28  	RestClient             *ethclient.Client
    29  	relDB                  *models.RelDB
    30  	datastore              *models.DB
    31  	poolChannel            chan dia.Pool
    32  	doneChannel            chan bool
    33  	blockchain             string
    34  	exchangeName           string
    35  	vaultContract          string
    36  	startblockPoolRegister uint64
    37  	cachedAssets           map[string]dia.Asset
    38  }
    39  
    40  // NewBalancerV2Scraper returns a Balancer V2 scraper
    41  func NewBalancerV2Scraper(exchange dia.Exchange, relDB *models.RelDB, datastore *models.DB) *BalancerV2Scraper {
    42  	var (
    43  		restClient  *ethclient.Client
    44  		err         error
    45  		poolChannel = make(chan dia.Pool)
    46  		doneChannel = make(chan bool)
    47  		scraper     *BalancerV2Scraper
    48  	)
    49  
    50  	log.Infof("Init rest client for %s.", exchange.BlockChain.Name)
    51  	restClient, err = ethclient.Dial(utils.Getenv(strings.ToUpper(exchange.BlockChain.Name)+"_URI_REST", balancerV2RestDial))
    52  	if err != nil {
    53  		log.Fatal("init rest client: ", err)
    54  	}
    55  
    56  	scraper = &BalancerV2Scraper{
    57  		RestClient:    restClient,
    58  		relDB:         relDB,
    59  		datastore:     datastore,
    60  		poolChannel:   poolChannel,
    61  		doneChannel:   doneChannel,
    62  		blockchain:    exchange.BlockChain.Name,
    63  		exchangeName:  exchange.Name,
    64  		vaultContract: exchange.Contract,
    65  		cachedAssets:  make(map[string]dia.Asset),
    66  	}
    67  
    68  	switch exchange.Name {
    69  	case dia.BalancerV2Exchange:
    70  		scraper.startblockPoolRegister = 12272146
    71  	case dia.BalancerV2ExchangeArbitrum:
    72  		scraper.startblockPoolRegister = 222832
    73  	case dia.BalancerV2ExchangePolygon:
    74  		scraper.startblockPoolRegister = 15832990
    75  	case dia.BeetsExchange:
    76  		scraper.startblockPoolRegister = 16896080
    77  	}
    78  
    79  	go func() {
    80  		scraper.fetchPools()
    81  	}()
    82  
    83  	return scraper
    84  }
    85  
    86  // fetchPools collects all available pools and sends them into the pool channel.
    87  func (scraper *BalancerV2Scraper) fetchPools() {
    88  	events, err := scraper.allRegisteredPools()
    89  	if err != nil {
    90  		log.Fatal("fetch all registered pools: ", err)
    91  	}
    92  
    93  	caller, err := balancervault.NewBalancerVaultCaller(common.HexToAddress(scraper.vaultContract), scraper.RestClient)
    94  	if err != nil {
    95  		log.Fatal("make balancer vault caller: ", err)
    96  	}
    97  
    98  	for _, evt := range events {
    99  		poolTokens, err := caller.GetPoolTokens(&bind.CallOpts{}, evt.PoolId)
   100  		if err != nil {
   101  			log.Warn("get pool tokens: ", err)
   102  		}
   103  		assetvolumes := scraper.extractPoolInfo(poolTokens)
   104  		pool := dia.Pool{
   105  			Exchange:     dia.Exchange{Name: scraper.exchangeName},
   106  			Blockchain:   dia.BlockChain{Name: scraper.blockchain},
   107  			Address:      evt.PoolAddress.String(),
   108  			Assetvolumes: assetvolumes,
   109  			Time:         time.Now(),
   110  		}
   111  
   112  		// Determine USD liquidity.
   113  		if pool.SufficientNativeBalance(GLOBAL_NATIVE_LIQUIDITY_THRESHOLD) {
   114  			scraper.datastore.GetPoolLiquiditiesUSD(&pool, priceCache)
   115  		}
   116  
   117  		scraper.poolChannel <- pool
   118  	}
   119  	scraper.doneChannel <- true
   120  }
   121  
   122  // allRegisteredPools returns a slice of all pool creation events.
   123  func (scraper *BalancerV2Scraper) allRegisteredPools() ([]*balancervault.BalancerVaultPoolRegistered, error) {
   124  	var (
   125  		offset     uint64 = balancerV2FilterPageSize
   126  		startBlock uint64 = scraper.startblockPoolRegister
   127  		endBlock          = startBlock + offset
   128  		events     []*balancervault.BalancerVaultPoolRegistered
   129  	)
   130  
   131  	filterer, err := balancervault.NewBalancerVaultFilterer(common.HexToAddress(scraper.vaultContract), scraper.RestClient)
   132  	if err != nil {
   133  		return nil, err
   134  	}
   135  
   136  	currBlock, err := scraper.RestClient.BlockNumber(context.Background())
   137  	if err != nil {
   138  		return nil, err
   139  	}
   140  
   141  	for {
   142  		if endBlock > currBlock {
   143  			endBlock = currBlock
   144  		}
   145  		log.Infof("startblock - endblock: %v --- %v ", startBlock, endBlock)
   146  
   147  		it, err := filterer.FilterPoolRegistered(&bind.FilterOpts{
   148  			Start: startBlock,
   149  			End:   &endBlock,
   150  		}, nil, nil)
   151  		if err != nil {
   152  			log.Warn("filterpoolregistered: ", err)
   153  			continue
   154  		}
   155  
   156  		for it.Next() {
   157  			events = append(events, it.Event)
   158  			log.Info("pool address: ", it.Event.PoolAddress)
   159  		}
   160  		if err := it.Close(); err != nil {
   161  			log.Warn("closing iterator: ", it)
   162  		}
   163  
   164  		if endBlock == currBlock {
   165  			break
   166  		}
   167  
   168  		startBlock = endBlock + 1
   169  		endBlock = endBlock + offset
   170  	}
   171  
   172  	return events, nil
   173  }
   174  
   175  // extractPoolInfo returns assetvolumes in the correct format for a dia.Pool.
   176  func (scraper *BalancerV2Scraper) extractPoolInfo(poolTokens struct {
   177  	Tokens          []common.Address
   178  	Balances        []*big.Int
   179  	LastChangeBlock *big.Int
   180  }) (assetvolumes []dia.AssetVolume) {
   181  	for i := range poolTokens.Tokens {
   182  
   183  		asset, err := scraper.relDB.GetAsset(poolTokens.Tokens[i].Hex(), scraper.blockchain)
   184  		if err != nil {
   185  			asset, err = ethhelper.ETHAddressToAsset(poolTokens.Tokens[i], scraper.RestClient, scraper.blockchain)
   186  			if err != nil {
   187  				log.Warn("cannot fetch asset from address ", poolTokens.Tokens[i].Hex())
   188  				continue
   189  			}
   190  		}
   191  
   192  		volume, _ := new(big.Float).Quo(big.NewFloat(0).SetInt(poolTokens.Balances[i]), new(big.Float).SetFloat64(math.Pow10(int(asset.Decimals)))).Float64()
   193  
   194  		assetvolumes = append(assetvolumes, dia.AssetVolume{Asset: asset, Volume: volume, Index: uint8(i)})
   195  	}
   196  	return
   197  }
   198  
   199  // assetFromToken returns the dia.Asset corresponding to an address on the underlying scraper's blockchain.
   200  func (scraper *BalancerV2Scraper) assetFromToken(token common.Address) (dia.Asset, error) {
   201  	cached, ok := scraper.cachedAssets[token.Hex()]
   202  	if !ok {
   203  		asset, err := ethhelper.ETHAddressToAsset(token, scraper.RestClient, scraper.blockchain)
   204  		if err != nil {
   205  			return dia.Asset{}, err
   206  		}
   207  		scraper.cachedAssets[token.Hex()] = asset
   208  		return asset, nil
   209  	} else {
   210  		return cached, nil
   211  	}
   212  }
   213  
   214  func (scraper *BalancerV2Scraper) Pool() chan dia.Pool {
   215  	return scraper.poolChannel
   216  }
   217  
   218  func (scraper *BalancerV2Scraper) Done() chan bool {
   219  	return scraper.doneChannel
   220  }