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

     1  package liquidityscrapers
     2  
     3  import (
     4  	"math"
     5  	"math/big"
     6  	"strings"
     7  	"time"
     8  
     9  	"github.com/diadata-org/diadata/pkg/dia/helpers/ethhelper"
    10  	"github.com/diadata-org/diadata/pkg/dia/scraper/exchange-scrapers/curvefi"
    11  	"github.com/diadata-org/diadata/pkg/dia/scraper/exchange-scrapers/curvefifactory"
    12  	"github.com/diadata-org/diadata/pkg/dia/scraper/exchange-scrapers/curvefimeta"
    13  	models "github.com/diadata-org/diadata/pkg/model"
    14  	"github.com/diadata-org/diadata/pkg/utils"
    15  
    16  	"github.com/diadata-org/diadata/pkg/dia"
    17  	"github.com/ethereum/go-ethereum/accounts/abi/bind"
    18  	"github.com/ethereum/go-ethereum/common"
    19  	"github.com/ethereum/go-ethereum/ethclient"
    20  )
    21  
    22  var (
    23  	curveRestDial = ""
    24  )
    25  
    26  type CurveFIScraper struct {
    27  	RestClient   *ethclient.Client
    28  	relDB        *models.RelDB
    29  	datastore    *models.DB
    30  	poolChannel  chan dia.Pool
    31  	doneChannel  chan bool
    32  	blockchain   string
    33  	exchangeName string
    34  }
    35  
    36  type curveRegistry struct {
    37  	Address common.Address
    38  	Type    int
    39  }
    40  
    41  type CurveCoin struct {
    42  	Symbol   string
    43  	Decimals uint8
    44  	Address  string
    45  	Name     string
    46  }
    47  
    48  func NewCurveFIScraper(exchange dia.Exchange, relDB *models.RelDB, datastore *models.DB) *CurveFIScraper {
    49  	var (
    50  		restClient  *ethclient.Client
    51  		err         error
    52  		poolChannel = make(chan dia.Pool)
    53  		doneChannel = make(chan bool)
    54  		scraper     *CurveFIScraper
    55  		registries  []curveRegistry
    56  	)
    57  
    58  	log.Infof("Init rest client for %s.", exchange.BlockChain.Name)
    59  	restClient, err = ethclient.Dial(utils.Getenv(strings.ToUpper(exchange.BlockChain.Name)+"_URI_REST", curveRestDial))
    60  	if err != nil {
    61  		log.Fatal("init rest client: ", err)
    62  	}
    63  
    64  	switch exchange.Name {
    65  	case dia.CurveFIExchange:
    66  		basePools := curveRegistry{Type: 1, Address: common.HexToAddress(exchange.Contract)}
    67  		cryptoswapPools := curveRegistry{Type: 1, Address: common.HexToAddress("0x8F942C20D02bEfc377D41445793068908E2250D0")}
    68  		metaPools := curveRegistry{Type: 2, Address: common.HexToAddress("0xB9fC157394Af804a3578134A6585C0dc9cc990d4")}
    69  		stableSwapRegistry := curveRegistry{Type: 3, Address: common.HexToAddress("0x6A8cbed756804B16E05E741eDaBd5cB544AE21bf")}
    70  		factoryPools := curveRegistry{Type: 3, Address: common.HexToAddress("0xF18056Bbd320E96A48e3Fbf8bC061322531aac99")}
    71  		factory2Pools := curveRegistry{Type: 3, Address: common.HexToAddress("0x4F8846Ae9380B90d2E71D5e3D042dff3E7ebb40d")}
    72  		registries = []curveRegistry{stableSwapRegistry, factoryPools, factory2Pools, basePools, cryptoswapPools, metaPools}
    73  
    74  	case dia.CurveFIExchangeFantom:
    75  		exchange.Contract = ""
    76  		// basePools := curveRegistry{Type: 1, Address: common.HexToAddress(exchange.Contract)}
    77  		stableSwapFactory := curveRegistry{Type: 2, Address: common.HexToAddress("0x686d67265703D1f124c45E33d47d794c566889Ba")}
    78  		registries = []curveRegistry{stableSwapFactory}
    79  	case dia.CurveFIExchangeMoonbeam:
    80  		// basePools := curveRegistry{Type: 1, Address: common.HexToAddress(exchange.Contract)}
    81  		stableSwapFactory := curveRegistry{Type: 2, Address: common.HexToAddress("0x4244eB811D6e0Ef302326675207A95113dB4E1F8")}
    82  		registries = []curveRegistry{stableSwapFactory}
    83  	case dia.CurveFIExchangePolygon:
    84  		stableSwapFactory := curveRegistry{Type: 2, Address: common.HexToAddress("0x722272D36ef0Da72FF51c5A65Db7b870E2e8D4ee")}
    85  		registries = []curveRegistry{stableSwapFactory}
    86  	case dia.CurveFIExchangeArbitrum:
    87  		stableSwapFactory := curveRegistry{Type: 2, Address: common.HexToAddress("0xb17b674D9c5CB2e441F8e196a2f048A81355d031")}
    88  		registries = []curveRegistry{stableSwapFactory}
    89  
    90  	}
    91  
    92  	scraper = &CurveFIScraper{
    93  		RestClient:   restClient,
    94  		relDB:        relDB,
    95  		datastore:    datastore,
    96  		poolChannel:  poolChannel,
    97  		doneChannel:  doneChannel,
    98  		blockchain:   exchange.BlockChain.Name,
    99  		exchangeName: exchange.Name,
   100  	}
   101  
   102  	go func() {
   103  		for _, registry := range registries {
   104  			poolAddresses, err := scraper.fetchPoolAddresses(registry)
   105  			if err != nil {
   106  				log.Error("fetch pool addresses: ", err)
   107  			}
   108  			for _, poolAddress := range poolAddresses {
   109  				scraper.loadPoolData(poolAddress.Hex(), registry)
   110  			}
   111  		}
   112  		scraper.doneChannel <- true
   113  	}()
   114  
   115  	return scraper
   116  }
   117  
   118  func (scraper *CurveFIScraper) fetchPoolAddresses(registry curveRegistry) (poolAddresses []common.Address, err error) {
   119  
   120  	if registry.Type == 1 {
   121  		log.Info("load base type pools..")
   122  		var (
   123  			contract  *curvefi.CurvefiCaller
   124  			poolCount *big.Int
   125  		)
   126  		contract, err = curvefi.NewCurvefiCaller(registry.Address, scraper.RestClient)
   127  		if err != nil {
   128  			log.Error("NewCurvefiCaller: ", err)
   129  		}
   130  
   131  		poolCount, err = contract.PoolCount(&bind.CallOpts{})
   132  		if err != nil {
   133  			log.Error("PoolCount: ", err)
   134  		}
   135  		log.Infof("poolCount in registry %s: %v ", registry.Address.Hex(), int(poolCount.Int64()))
   136  		for i := 0; i < int(poolCount.Int64()); i++ {
   137  			poolAddress, errPool := contract.PoolList(&bind.CallOpts{}, big.NewInt(int64(i)))
   138  			if errPool != nil {
   139  				log.Error("PoolList: ", err)
   140  			}
   141  			poolAddresses = append(poolAddresses, poolAddress)
   142  		}
   143  	}
   144  
   145  	if registry.Type == 2 || registry.Type == 3 {
   146  		log.Info("load meta / factory type pools...")
   147  		var (
   148  			contract  *curvefimeta.CurvefimetaCaller
   149  			poolCount *big.Int
   150  		)
   151  		contract, err = curvefimeta.NewCurvefimetaCaller(registry.Address, scraper.RestClient)
   152  		if err != nil {
   153  			log.Error("NewCurvefiCaller: ", err)
   154  		}
   155  		poolCount, err = contract.PoolCount(&bind.CallOpts{})
   156  		if err != nil {
   157  			log.Error("PoolCount: ", err)
   158  		}
   159  		log.Infof("poolCount in registry %s: %v ", registry.Address.Hex(), int(poolCount.Int64()))
   160  		for i := 0; i < int(poolCount.Int64()); i++ {
   161  			poolAddress, err := contract.PoolList(&bind.CallOpts{}, big.NewInt(int64(i)))
   162  			if err != nil {
   163  				log.Error("PoolList: ", err)
   164  			}
   165  			poolAddresses = append(poolAddresses, poolAddress)
   166  		}
   167  	}
   168  	return
   169  }
   170  
   171  func (scraper *CurveFIScraper) loadPoolData(poolAddress string, registry curveRegistry) {
   172  	// We need to handle ETH with address 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE because of pools such as:
   173  	// https://etherscan.io/address/0xDC24316b9AE028F1497c275EB9192a3Ea0f67022#readContract
   174  
   175  	var (
   176  		poolCoins [8]common.Address
   177  		pool      dia.Pool
   178  	)
   179  	pool.Blockchain = dia.BlockChain{Name: scraper.blockchain}
   180  	pool.Exchange = dia.Exchange{
   181  		Name:        scraper.exchangeName,
   182  		Centralized: false,
   183  		Bridge:      false,
   184  		BlockChain:  pool.Blockchain,
   185  	}
   186  	pool.Address = poolAddress
   187  
   188  	if registry.Type == 1 {
   189  		contract, err := curvefi.NewCurvefiCaller(registry.Address, scraper.RestClient)
   190  		if err != nil {
   191  			log.Error("loadPoolData - NewCurvefiCaller: ", err)
   192  			return
   193  		}
   194  
   195  		poolCoins, err = contract.GetCoins(&bind.CallOpts{}, common.HexToAddress(poolAddress))
   196  		if err != nil {
   197  			log.Error("loadPoolData - GetCoins: ", err)
   198  			return
   199  		}
   200  
   201  		liquidities, err := contract.GetBalances(&bind.CallOpts{}, common.HexToAddress(poolAddress))
   202  		if err != nil {
   203  			log.Error("loadPoolData - GetBalances: ", err)
   204  			return
   205  		}
   206  
   207  		var i int
   208  		for i < 8 && poolCoins[i] != (common.Address{}) {
   209  			if poolCoins[i].Hex() == "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE" {
   210  				poolCoins[i] = common.HexToAddress("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2")
   211  			}
   212  			asset, err := scraper.relDB.GetAsset(poolCoins[i].Hex(), scraper.blockchain)
   213  			if err != nil {
   214  				asset, err = ethhelper.ETHAddressToAsset(poolCoins[i], scraper.RestClient, scraper.blockchain)
   215  				if err != nil {
   216  					return
   217  				}
   218  			}
   219  			liquidity, _ := new(big.Float).Quo(big.NewFloat(0).SetInt(liquidities[i]), new(big.Float).SetFloat64(math.Pow10(int(asset.Decimals)))).Float64()
   220  			av := dia.AssetVolume{Asset: asset, Volume: liquidity, Index: uint8(i)}
   221  			pool.Assetvolumes = append(pool.Assetvolumes, av)
   222  			pool.Time = time.Now()
   223  			i++
   224  		}
   225  
   226  	}
   227  
   228  	if registry.Type == 2 {
   229  		contract, err := curvefimeta.NewCurvefimetaCaller(registry.Address, scraper.RestClient)
   230  		if err != nil {
   231  			log.Error("loadPoolData - NewCurvefiCaller: ", err)
   232  			return
   233  		}
   234  
   235  		poolCoins, err := contract.GetCoins(&bind.CallOpts{}, common.HexToAddress(poolAddress))
   236  		if err != nil {
   237  			log.Error("loadPoolData - GetCoins: ", err)
   238  			return
   239  		}
   240  
   241  		liquidities, err := contract.GetBalances(&bind.CallOpts{}, common.HexToAddress(poolAddress))
   242  		if err != nil {
   243  			log.Error("loadPoolData - GetBalances: ", err)
   244  			return
   245  		}
   246  
   247  		var i int
   248  		for i < 4 && poolCoins[i] != (common.Address{}) {
   249  			if poolCoins[i].Hex() == "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE" {
   250  				poolCoins[i] = common.HexToAddress("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2")
   251  			}
   252  			asset, err := scraper.relDB.GetAsset(poolCoins[i].Hex(), scraper.blockchain)
   253  			if err != nil {
   254  				asset, err = ethhelper.ETHAddressToAsset(poolCoins[i], scraper.RestClient, scraper.blockchain)
   255  				if err != nil {
   256  					return
   257  				}
   258  			}
   259  			liquidity, _ := new(big.Float).Quo(big.NewFloat(0).SetInt(liquidities[i]), new(big.Float).SetFloat64(math.Pow10(int(asset.Decimals)))).Float64()
   260  			av := dia.AssetVolume{Asset: asset, Volume: liquidity, Index: uint8(i)}
   261  			pool.Assetvolumes = append(pool.Assetvolumes, av)
   262  			pool.Time = time.Now()
   263  			i++
   264  		}
   265  	}
   266  	if registry.Type == 3 {
   267  		contract, err := curvefifactory.NewCurvefifactoryCaller(common.HexToAddress(poolAddress), scraper.RestClient)
   268  		if err != nil {
   269  			log.Error("loadPoolData - NewCurvefiCaller: ", err)
   270  		}
   271  
   272  		var i int64
   273  		for i < 8 {
   274  			poolAssetAddress, err := contract.Coins(&bind.CallOpts{}, big.NewInt(i))
   275  			if err != nil {
   276  				i++
   277  				continue
   278  			}
   279  			if poolAssetAddress == (common.Address{}) {
   280  				i++
   281  				continue
   282  			}
   283  			if poolAssetAddress.Hex() == "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE" {
   284  				poolAssetAddress = common.HexToAddress("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2")
   285  			}
   286  
   287  			liquidityBig, err := contract.Balances(&bind.CallOpts{}, big.NewInt(i))
   288  			if err != nil {
   289  				log.Error("Get Balances: ", err)
   290  			}
   291  
   292  			asset, err := scraper.relDB.GetAsset(poolAssetAddress.Hex(), scraper.blockchain)
   293  			if err != nil {
   294  				asset, err = ethhelper.ETHAddressToAsset(poolAssetAddress, scraper.RestClient, scraper.blockchain)
   295  				if err != nil {
   296  					return
   297  				}
   298  			}
   299  			liquidity, _ := new(big.Float).Quo(big.NewFloat(0).SetInt(liquidityBig), new(big.Float).SetFloat64(math.Pow10(int(asset.Decimals)))).Float64()
   300  			av := dia.AssetVolume{Asset: asset, Volume: liquidity, Index: uint8(i)}
   301  			pool.Assetvolumes = append(pool.Assetvolumes, av)
   302  			pool.Time = time.Now()
   303  			i++
   304  		}
   305  
   306  	}
   307  
   308  	// Determine USD liquidity.
   309  	if pool.SufficientNativeBalance(GLOBAL_NATIVE_LIQUIDITY_THRESHOLD) {
   310  		scraper.datastore.GetPoolLiquiditiesUSD(&pool, priceCache)
   311  	}
   312  
   313  	scraper.poolChannel <- pool
   314  
   315  }
   316  
   317  func (scraper *CurveFIScraper) Pool() chan dia.Pool {
   318  	return scraper.poolChannel
   319  }
   320  
   321  func (scraper *CurveFIScraper) Done() chan bool {
   322  	return scraper.doneChannel
   323  }
   324  
   325  // getAssetFromCache returns the full asset given by blockchain and address, either from the map @localCache
   326  // or from Postgres, in which latter case it also adds the asset to the local cache.
   327  // Remember that maps are always passed by reference.
   328  func (scraper *CurveFIScraper) getAssetFromCache(localCache map[string]dia.Asset, blockchain string, address string) dia.Asset {
   329  	if asset, ok := localCache[assetIdentifier(blockchain, address)]; ok {
   330  		return asset
   331  	}
   332  	fullAsset, err := scraper.relDB.GetAsset(address, blockchain)
   333  	if err != nil {
   334  		log.Warnf("could not find asset with address %s on blockchain %s in postgres: ", address, blockchain)
   335  	}
   336  	localCache[assetIdentifier(blockchain, address)] = fullAsset
   337  	return fullAsset
   338  }
   339  
   340  func assetIdentifier(blockchain string, address string) string {
   341  	return blockchain + "-" + address
   342  }