github.com/diadata-org/diadata@v1.4.593/pkg/dia/service/assetservice/source/balancerv3.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  	vault "github.com/diadata-org/diadata/pkg/dia/scraper/exchange-scrapers/balancerv3/vault"
    11  	vaultextension "github.com/diadata-org/diadata/pkg/dia/scraper/exchange-scrapers/balancerv3/vaultextension"
    12  	"go.uber.org/ratelimit"
    13  
    14  	"github.com/diadata-org/diadata/pkg/dia"
    15  	"github.com/diadata-org/diadata/pkg/utils"
    16  	"github.com/ethereum/go-ethereum/accounts/abi/bind"
    17  	"github.com/ethereum/go-ethereum/common"
    18  	"github.com/ethereum/go-ethereum/ethclient"
    19  )
    20  
    21  const (
    22  	balancerV3RateLimitPerSec = 50
    23  	balancerV3FilterPageSize  = 5000
    24  )
    25  
    26  type BalancerV3AssetSource struct {
    27  	RestClient                     *ethclient.Client
    28  	assetChannel                   chan dia.Asset
    29  	doneChannel                    chan bool
    30  	waitTime                       int
    31  	blockchain                     string
    32  	exchangeName                   string
    33  	cachedAssets                   sync.Map // map[string]dia.Asset
    34  	exchangeFactoryContractAddress string
    35  	startblockPoolRegister         uint64
    36  	rl                             ratelimit.Limiter
    37  }
    38  
    39  func NewBalancerV3AssetSource(exchange dia.Exchange) (bas *BalancerV3AssetSource) {
    40  
    41  	bas = makeBalancerV3AssetSource(exchange, "", uniswapWaitMilliseconds)
    42  	bas.rl = ratelimit.New(balancerV3RateLimitPerSec)
    43  
    44  	switch exchange.Name {
    45  	case dia.BalancerV3Exchange:
    46  		bas.startblockPoolRegister = 21332121
    47  	}
    48  
    49  	// TO DO: implement postgres storage of last fetched block.
    50  
    51  	go func() {
    52  		bas.fetchAssets()
    53  	}()
    54  	return bas
    55  
    56  }
    57  
    58  func (bas *BalancerV3AssetSource) fetchAssets() {
    59  
    60  	pools, err := bas.listPools()
    61  	if err != nil {
    62  		log.Fatal("list available pools: ", err)
    63  	}
    64  
    65  	bas.getAssetsFromPools(pools)
    66  
    67  	bas.doneChannel <- true
    68  
    69  }
    70  
    71  // makeBalancerV3AssetSource returns an asset source as used in NewBalancerV3AssetSource.
    72  func makeBalancerV3AssetSource(exchange dia.Exchange, restDial string, waitMilliseconds string) *BalancerV3AssetSource {
    73  	var (
    74  		restClient   *ethclient.Client
    75  		err          error
    76  		assetChannel = make(chan dia.Asset)
    77  		doneChannel  = make(chan bool)
    78  		bas          *BalancerV3AssetSource
    79  	)
    80  
    81  	log.Infof("Init rest client for %s.", exchange.BlockChain.Name)
    82  	restClient, err = ethclient.Dial(utils.Getenv(strings.ToUpper(exchange.BlockChain.Name)+"_URI_REST", restDial))
    83  	if err != nil {
    84  		log.Fatal("init rest client: ", err)
    85  	}
    86  	var waitTime int
    87  	waitTimeString := utils.Getenv(strings.ToUpper(exchange.BlockChain.Name)+"_WAIT_TIME", waitMilliseconds)
    88  	waitTime, err = strconv.Atoi(waitTimeString)
    89  	if err != nil {
    90  		log.Error("could not parse wait time: ", err)
    91  		waitTime = 500
    92  	}
    93  
    94  	bas = &BalancerV3AssetSource{
    95  		RestClient:                     restClient,
    96  		assetChannel:                   assetChannel,
    97  		doneChannel:                    doneChannel,
    98  		blockchain:                     exchange.BlockChain.Name,
    99  		waitTime:                       waitTime,
   100  		exchangeName:                   exchange.Name,
   101  		exchangeFactoryContractAddress: exchange.Contract,
   102  	}
   103  	return bas
   104  }
   105  
   106  // allRegisteredPools returns an iterator for all pools registered.
   107  func (bas *BalancerV3AssetSource) allRegisteredPools() ([]*vault.VaultPoolRegistered, error) {
   108  	var (
   109  		offset     uint64 = balancerV3FilterPageSize
   110  		startBlock uint64 = bas.startblockPoolRegister
   111  		endBlock          = startBlock + offset
   112  		events     []*vault.VaultPoolRegistered
   113  	)
   114  
   115  	filterer, err := vault.NewVaultFilterer(common.HexToAddress(bas.exchangeFactoryContractAddress), bas.RestClient)
   116  	if err != nil {
   117  		return nil, err
   118  	}
   119  
   120  	currBlock, err := bas.RestClient.BlockNumber(context.Background())
   121  	if err != nil {
   122  		return nil, err
   123  	}
   124  
   125  	for {
   126  		if endBlock > currBlock {
   127  			endBlock = currBlock
   128  		}
   129  		log.Infof("startblock - endblock: %v --- %v ", startBlock, endBlock)
   130  
   131  		it, err := filterer.FilterPoolRegistered(&bind.FilterOpts{
   132  			Start: startBlock,
   133  			End:   &endBlock,
   134  		}, nil, nil)
   135  		if err != nil {
   136  			log.Warn("filterpoolregistered: ", err)
   137  			continue
   138  		}
   139  
   140  		for it.Next() {
   141  			events = append(events, it.Event)
   142  		}
   143  		if err := it.Close(); err != nil {
   144  			log.Warn("closing iterator: ", it)
   145  		}
   146  
   147  		if endBlock == currBlock {
   148  			break
   149  		}
   150  
   151  		startBlock = endBlock + 1
   152  		endBlock = endBlock + offset
   153  	}
   154  
   155  	return events, nil
   156  }
   157  
   158  // listPools returns a list of pools given by the addresses of the underlying assets.
   159  func (bas *BalancerV3AssetSource) listPools() ([][]common.Address, error) {
   160  
   161  	events, err := bas.allRegisteredPools()
   162  	if err != nil {
   163  		return nil, err
   164  	}
   165  
   166  	vaultExtensionCaller, err := vaultextension.NewVaultextensionCaller(common.HexToAddress(bas.exchangeFactoryContractAddress), bas.RestClient)
   167  	if err != nil {
   168  		return [][]common.Address{}, err
   169  	}
   170  
   171  	pools := make([][]common.Address, len(events))
   172  	for idx, evt := range events {
   173  
   174  		poolAssets, err := vaultExtensionCaller.GetPoolTokens(&bind.CallOpts{}, evt.Pool)
   175  		if err != nil {
   176  			log.Error("GetPoolTokens: ", err)
   177  		}
   178  		log.Infof("fetch pool %s", evt.Pool.Hex())
   179  
   180  		pools[idx] = poolAssets
   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 *BalancerV3AssetSource) 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 *BalancerV3AssetSource) 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 *BalancerV3AssetSource) Asset() chan dia.Asset {
   225  	return bas.assetChannel
   226  }
   227  
   228  func (bas *BalancerV3AssetSource) Done() chan bool {
   229  	return bas.doneChannel
   230  }