github.com/diadata-org/diadata@v1.4.593/pkg/dia/service/assetservice/source/balancerv2.go (about)

     1  package source
     2  
     3  import (
     4  	"context"
     5  	"strconv"
     6  	"strings"
     7  	"sync"
     8  
     9  	"github.com/diadata-org/diadata/pkg/dia/helpers/ethhelper"
    10  	balancervault "github.com/diadata-org/diadata/pkg/dia/scraper/exchange-scrapers/balancerv2/vault"
    11  	"go.uber.org/ratelimit"
    12  
    13  	"github.com/diadata-org/diadata/pkg/dia"
    14  	"github.com/diadata-org/diadata/pkg/utils"
    15  	"github.com/ethereum/go-ethereum/accounts/abi/bind"
    16  	"github.com/ethereum/go-ethereum/common"
    17  	"github.com/ethereum/go-ethereum/ethclient"
    18  )
    19  
    20  const (
    21  	balancerV2RateLimitPerSec = 50
    22  	balancerV2FilterPageSize  = 5000
    23  )
    24  
    25  type BalancerV2AssetSource struct {
    26  	RestClient                     *ethclient.Client
    27  	assetChannel                   chan dia.Asset
    28  	doneChannel                    chan bool
    29  	waitTime                       int
    30  	blockchain                     string
    31  	exchangeName                   string
    32  	cachedAssets                   sync.Map // map[string]dia.Asset
    33  	exchangeFactoryContractAddress string
    34  	startblockPoolRegister         uint64
    35  	rl                             ratelimit.Limiter
    36  }
    37  
    38  func NewBalancerV2AssetSource(exchange dia.Exchange) (bas *BalancerV2AssetSource) {
    39  
    40  	bas = makeBalancerV2AssetSource(exchange, "", uniswapWaitMilliseconds)
    41  	bas.rl = ratelimit.New(balancerV2RateLimitPerSec)
    42  
    43  	switch exchange.Name {
    44  	case dia.BalancerV2Exchange:
    45  		bas.startblockPoolRegister = 12272146
    46  	case dia.BalancerV2ExchangeArbitrum:
    47  		bas.startblockPoolRegister = 222832
    48  	case dia.BeetsExchange:
    49  		bas.startblockPoolRegister = 16896080
    50  	case dia.BalancerV2ExchangePolygon:
    51  		bas.startblockPoolRegister = 15832990
    52  	}
    53  
    54  	go func() {
    55  		bas.fetchAssets()
    56  	}()
    57  	return bas
    58  
    59  }
    60  
    61  func (bas *BalancerV2AssetSource) fetchAssets() {
    62  
    63  	pools, err := bas.listPools()
    64  	if err != nil {
    65  		log.Fatal("list available pools: ", err)
    66  	}
    67  
    68  	bas.getAssetsFromPools(pools)
    69  
    70  	bas.doneChannel <- true
    71  
    72  }
    73  
    74  // makeBalancerV2AssetSource returns an asset source as used in NewBalancerV2AssetSource.
    75  func makeBalancerV2AssetSource(exchange dia.Exchange, restDial string, waitMilliseconds string) *BalancerV2AssetSource {
    76  	var (
    77  		restClient   *ethclient.Client
    78  		err          error
    79  		assetChannel = make(chan dia.Asset)
    80  		doneChannel  = make(chan bool)
    81  		bas          *BalancerV2AssetSource
    82  	)
    83  
    84  	log.Infof("Init rest client for %s.", exchange.BlockChain.Name)
    85  	restClient, err = ethclient.Dial(utils.Getenv(strings.ToUpper(exchange.BlockChain.Name)+"_URI_REST", restDial))
    86  	if err != nil {
    87  		log.Fatal("init rest client: ", err)
    88  	}
    89  	var waitTime int
    90  	waitTimeString := utils.Getenv(strings.ToUpper(exchange.BlockChain.Name)+"_WAIT_TIME", waitMilliseconds)
    91  	waitTime, err = strconv.Atoi(waitTimeString)
    92  	if err != nil {
    93  		log.Error("could not parse wait time: ", err)
    94  		waitTime = 500
    95  	}
    96  
    97  	bas = &BalancerV2AssetSource{
    98  		RestClient:                     restClient,
    99  		assetChannel:                   assetChannel,
   100  		doneChannel:                    doneChannel,
   101  		blockchain:                     exchange.BlockChain.Name,
   102  		waitTime:                       waitTime,
   103  		exchangeName:                   exchange.Name,
   104  		exchangeFactoryContractAddress: exchange.Contract,
   105  	}
   106  	return bas
   107  }
   108  
   109  // allRegisteredPools returns an iterator for all pools registered.
   110  func (bas *BalancerV2AssetSource) allRegisteredPools() ([]*balancervault.BalancerVaultPoolRegistered, error) {
   111  	var (
   112  		offset     uint64 = balancerV2FilterPageSize
   113  		startBlock uint64 = bas.startblockPoolRegister
   114  		endBlock          = startBlock + offset
   115  		events     []*balancervault.BalancerVaultPoolRegistered
   116  	)
   117  
   118  	filterer, err := balancervault.NewBalancerVaultFilterer(common.HexToAddress(bas.exchangeFactoryContractAddress), bas.RestClient)
   119  	if err != nil {
   120  		return nil, err
   121  	}
   122  
   123  	currBlock, err := bas.RestClient.BlockNumber(context.Background())
   124  	if err != nil {
   125  		return nil, err
   126  	}
   127  
   128  	for {
   129  		if endBlock > currBlock {
   130  			endBlock = currBlock
   131  		}
   132  		log.Infof("startblock - endblock: %v --- %v ", startBlock, endBlock)
   133  
   134  		it, err := filterer.FilterPoolRegistered(&bind.FilterOpts{
   135  			Start: startBlock,
   136  			End:   &endBlock,
   137  		}, nil, nil)
   138  		if err != nil {
   139  			log.Warn("filterpoolregistered: ", err)
   140  			continue
   141  		}
   142  
   143  		for it.Next() {
   144  			events = append(events, it.Event)
   145  		}
   146  		if err := it.Close(); err != nil {
   147  			log.Warn("closing iterator: ", it)
   148  		}
   149  
   150  		if endBlock == currBlock {
   151  			break
   152  		}
   153  
   154  		startBlock = endBlock + 1
   155  		endBlock = endBlock + offset
   156  	}
   157  
   158  	return events, nil
   159  }
   160  
   161  // listPools returns a list of pools given by the addresses of the underlying assets.
   162  func (bas *BalancerV2AssetSource) listPools() ([][]common.Address, error) {
   163  
   164  	events, err := bas.allRegisteredPools()
   165  	if err != nil {
   166  		return nil, err
   167  	}
   168  
   169  	caller, err := balancervault.NewBalancerVaultCaller(common.HexToAddress(bas.exchangeFactoryContractAddress), bas.RestClient)
   170  	if err != nil {
   171  		return nil, err
   172  	}
   173  
   174  	pools := make([][]common.Address, len(events))
   175  	for idx, evt := range events {
   176  		pool, err := caller.GetPoolTokens(&bind.CallOpts{}, evt.PoolId)
   177  		if err != nil {
   178  			log.Errorf("fetch pool %s: %v", evt.PoolAddress.Hex(), err)
   179  		}
   180  		pools[idx] = pool.Tokens
   181  	}
   182  
   183  	return pools, nil
   184  }
   185  
   186  // getAssetsFromPools fetches all assets from @pools and sends them into the asset channel.
   187  func (bas *BalancerV2AssetSource) getAssetsFromPools(pools [][]common.Address) {
   188  
   189  	checkMap := make(map[string]struct{})
   190  
   191  	for _, tokens := range pools {
   192  		for i := 0; i < len(tokens); i++ {
   193  			if _, ok := checkMap[tokens[i].Hex()]; ok {
   194  				continue
   195  			} else {
   196  				checkMap[tokens[i].Hex()] = struct{}{}
   197  			}
   198  			asset, err := bas.assetFromToken(tokens[i])
   199  			if err != nil {
   200  				log.Error("get asset from token: ", err)
   201  			}
   202  			bas.assetChannel <- asset
   203  		}
   204  	}
   205  
   206  }
   207  
   208  func (bas *BalancerV2AssetSource) assetFromToken(token common.Address) (dia.Asset, error) {
   209  	cached, ok := bas.cachedAssets.Load(token.Hex())
   210  	if !ok {
   211  		asset, err := ethhelper.ETHAddressToAsset(token, bas.RestClient, bas.blockchain)
   212  		if err != nil {
   213  			return dia.Asset{}, err
   214  		}
   215  		bas.cachedAssets.Store(token.Hex(), asset)
   216  		return asset, nil
   217  	}
   218  
   219  	asset := cached.(dia.Asset)
   220  
   221  	return asset, nil
   222  }
   223  
   224  func (bas *BalancerV2AssetSource) Asset() chan dia.Asset {
   225  	return bas.assetChannel
   226  }
   227  
   228  func (bas *BalancerV2AssetSource) Done() chan bool {
   229  	return bas.doneChannel
   230  }