def get_price(token, block=None): token = Contract(token) underlying, exchange_rate, decimals = fetch_multicall( [token, 'underlying'], [token, 'exchangeRateCurrent'], [token, 'decimals'], block=block ) exchange_rate /= 1e18 under_decimals = Contract(underlying).decimals() return [exchange_rate * 10 ** (decimals - under_decimals), underlying]
def total_value_at(self, block=None): vaults = self.active_vaults_at_block(block) prices = Parallel(8, "threading")( delayed(magic.get_price)(vault.token, block=block) for vault in vaults) results = fetch_multicall(*[[vault.vault, "pool"] for vault in vaults], block=block) return { vault.name: assets * price / vault.scale for vault, assets, price in zip(vaults, results, prices) }
def describe(self, block=None): vaults = self.active_vaults_at(block) share_prices = fetch_multicall(*[[vault.vault, "getPricePerFullShare"] for vault in vaults], block=block) vaults = [ vault for vault, share_price in zip(vaults, share_prices) if share_price ] data = Parallel(8, "threading")(delayed(vault.describe)(block=block) for vault in vaults) return {vault.name: desc for vault, desc in zip(vaults, data)}
def get_markets(): comptroller = Contract("0x3d9819210A31b4961b30EF54bE2aeD79B9c9Cd3B") creamtroller = Contract('0x3d5BC3c8d13dcB8bF317092d84783c2697AE9258') ironbankroller = Contract("0xAB1c342C7bf5Ec5F02ADEA1c2270670bCa144CbB") results = fetch_multicall( [comptroller, 'getAllMarkets'], [creamtroller, 'getAllMarkets'], [ironbankroller, 'getAllMarkets'], ) names = ['compound', 'cream', 'ironbank'] return dict(zip(names, results))
def get_decimals(self, pool): factory = self.get_factory(pool) source = contract(factory) if factory else self.registry decimals = source.get_decimals(pool) # pool not in registry if not any(decimals): coins = self.get_coins(pool) decimals = fetch_multicall( *[[contract(token), 'decimals'] for token in coins] ) return [dec for dec in decimals if dec != 0]
def metapools_by_factory(self): """ Read cached pools spawned by each factory. TODO Update on factory events """ metapool_factories = [contract(factory) for factory in self.identifiers[3]] pool_counts = fetch_multicall( *[[factory, 'pool_count'] for factory in metapool_factories] ) pool_lists = iter( fetch_multicall( *[ [factory, 'pool_list', i] for factory, pool_count in zip(metapool_factories, pool_counts) for i in range(pool_count) ] ) ) return { str(factory): list(islice(pool_lists, pool_count)) for factory, pool_count in zip(metapool_factories, pool_counts) }
def total_value_at(self, block=None): vaults = self.active_vaults_at(block) balances = fetch_multicall(*[[vault.vault, "balance"] for vault in vaults], block=block) # skip vaults with zero or erroneous balance vaults = [(vault, balance) for vault, balance in zip(vaults, balances) if balance] prices = Parallel(8, "threading")(delayed(vault.get_price)(block) for (vault, balance) in vaults) return { vault.name: balance * price / 10**vault.decimals for (vault, balance), price in zip(vaults, prices) }
def held_assets(self, block=None) -> dict: balances = {} for address in self.addresses: # get token balances tokens = self.token_list(address, block=block) token_balances = fetch_multicall( *[[contract(token), "balanceOf", address] for token in tokens], block=block, ) decimals = fetch_multicall(*[[contract(token), "decimals"] for token in tokens], block=block) token_balances = [ balance / 10**decimal if decimal else 0 for balance, decimal in zip(token_balances, decimals) ] token_prices = Parallel(8, 'threading')( delayed(_get_price)(token, block) for token in tokens) token_balances = [{ 'balance': balance, 'usd value': balance * price } for balance, price in zip(token_balances, token_prices)] balances[address] = dict(zip(tokens, token_balances)) # then, add eth if block: balance = ( web3.eth.get_balance(address, block_identifier=block) / 10**18) else: balance = web3.eth.get_balance(address) / 10**18 balances[address]['ETH'] = { 'balance': balance, 'usd value': balance * get_price(weth, block), } return balances
def active_vaults_at(self, block=None): vaults = self.vaults + self.experiments if block: vaults = [ vault for vault in vaults if contract_creation_block(str(vault.vault)) <= block ] # fixes edge case: a vault is not necessarily initialized on creation activations = fetch_multicall(*[[vault.vault, 'activation'] for vault in vaults], block=block) return [ vault for vault, activation in zip(vaults, activations) if activation ]
def describe(self, block=None): results = fetch_multicall( *[[self.strategy, view] for view in self._views], [self.vault.vault, "strategies", self.strategy], block=block) info = dict(zip(self._views, results)) info.update(results[-1].dict()) for view in STRATEGY_VIEWS_SCALED: if view in info: info[view] = (info[view] or 0) / self.vault.scale # unwrap structs for view in info: if hasattr(info[view], '_dict'): info[view] = info[view].dict() return info
def get_coins(self, pool): """ Get coins of pool. """ factory = self.get_factory(pool) if factory: coins = contract(factory).get_coins(pool) else: coins = self.registry.get_coins(pool) # pool not in registry if set(coins) == {ZERO_ADDRESS}: coins = fetch_multicall( *[[contract(pool), 'coins', i] for i in range(8)] ) return [coin for coin in coins if coin not in {None, ZERO_ADDRESS}]
def get_decimals(self, pool): factory = self.get_factory(pool) if factory: source = contract(factory) elif self.crypto_swap_registry_supports_pool(pool): source = self.crypto_swap_registry else: source = source = self.registry decimals = source.get_decimals(pool) # pool not in registry if not any(decimals): coins = self.get_coins(pool) decimals = fetch_multicall(*[[contract(token), 'decimals'] for token in coins]) return [dec for dec in decimals if dec != 0]
def lp_price(self, address, block=None): """Get Uniswap/Sushiswap LP token price.""" pair = contract(address) token0, token1, supply, reserves = fetch_multicall( [pair, "token0"], [pair, "token1"], [pair, "totalSupply"], [pair, "getReserves"], block=block, ) tokens = [Contract(token) for token in [token0, token1]] scales = [10**token.decimals() for token in tokens] prices = [self.get_price(token, block=block) for token in tokens] supply = supply / 1e18 balances = [ res / scale * price for res, scale, price in zip(reserves, scales, prices) ] return sum(balances) / supply
def markets(self): atoken_to_token = {} for version, provider in address_providers[chain.id].items(): lending_pool = contract(contract(provider).getLendingPool()) if version == 'v1': tokens = lending_pool.getReserves() elif version == 'v2': tokens = lending_pool.getReservesList() else: raise ValueError(f'unsupported aave version {version}') reserves = fetch_multicall( *[[lending_pool, 'getReserveData', token] for token in tokens]) atoken_to_token.update({ reserve['aTokenAddress']: token for token, reserve in zip(tokens, reserves) }) return atoken_to_token
def cache_token(self, token_address: str): i = 0 while i < 10: try: self.cache_address(token_address) if not self.token_exists(token_address): token = contract(token_address) symbol, name, decimals = fetch_multicall( [token, 'symbol'], [token, 'name'], [token, 'decimals']) self.cursor.execute( f"INSERT INTO TOKENS (CHAINID, TOKEN_ADDRESS, SYMBOL, NAME, DECIMALS)\ VALUES ({chainid},'{token_address}','{symbol}','{name}',{decimals})" ) self.conn.commit() print(f'{symbol} added to postgres') return except: i += 1
def calculate_boost(self, gauge, addr, block=None): results = fetch_multicall( [gauge, "balanceOf", addr], [gauge, "totalSupply"], [gauge, "working_balances", addr], [gauge, "working_supply"], [self.voting_escrow, "balanceOf", addr], [self.voting_escrow, "totalSupply"], block=block, ) results = [x / 1e18 for x in results] gauge_balance, gauge_total, working_balance, working_supply, vecrv_balance, vecrv_total = results try: boost = working_balance / gauge_balance * 2.5 except ZeroDivisionError: boost = 1 min_vecrv = vecrv_total * gauge_balance / gauge_total lim = gauge_balance * 0.4 + gauge_total * min_vecrv / vecrv_total * 0.6 lim = min(gauge_balance, lim) _working_supply = working_supply + lim - working_balance noboost_lim = gauge_balance * 0.4 noboost_supply = working_supply + noboost_lim - working_balance try: max_boost_possible = (lim / _working_supply) / (noboost_lim / noboost_supply) except ZeroDivisionError: max_boost_possible = 1 return { "gauge balance": gauge_balance, "gauge total": gauge_total, "vecrv balance": vecrv_balance, "vecrv total": vecrv_total, "working balance": working_balance, "working total": working_supply, "boost": boost, "max boost": max_boost_possible, "min vecrv": min_vecrv, }
def describe(self, block=None): try: results = fetch_multicall(*[[self.vault, view] for view in self._views], block=block) info = dict(zip(self._views, results)) for name in info: if name in VAULT_VIEWS_SCALED: info[name] /= self.scale info["strategies"] = {} except ValueError as e: info = {"strategies": {}} for strategy in self.strategies: info["strategies"][strategy.name] = strategy.describe(block=block) info["token price"] = magic.get_price(self.token, block=block) if "totalAssets" in info: info["tvl"] = info["token price"] * info["totalAssets"] return info
def describe(self, block=None): info = {} strategy = self.strategy if block is not None: strategy = self.get_strategy(block=block) # attrs are fetches as multicall and populate info attrs = { "vault balance": [self.vault, "balance"], "vault total": [self.vault, "totalSupply"], "strategy balance": [strategy, "balanceOf"], "share price": [self.vault, "getPricePerFullShare"], } # some of the oldest vaults don't implement these methods if hasattr(self.vault, "available"): attrs["available"] = [self.vault, "available"] if hasattr(self.vault, "min") and hasattr(self.vault, "max"): attrs["min"] = [self.vault, "min"] attrs["max"] = [self.vault, "max"] # new curve voter proxy vaults if self.is_curve_vault and hasattr(strategy, "proxy"): vote_proxy, gauge = fetch_multicall( [strategy, "voter"], # voter is static, can pin [strategy, "gauge"], # gauge is static per strategy, can cache block=block, ) # guard historical queries where there are no vote_proxy and gauge # for block <= 10635293 (2020-08-11) if vote_proxy and gauge: vote_proxy = interface.CurveYCRVVoter(vote_proxy) gauge = contract(gauge) info.update( curve.calculate_boost(gauge, vote_proxy, block=block)) info.update(curve.calculate_apy(gauge, self.token, block=block)) attrs["earned"] = [gauge, "claimable_tokens", vote_proxy] # / scale if hasattr(strategy, "earned"): attrs["lifetime earned"] = [strategy, "earned"] # /scale if strategy._name == "StrategyYFIGovernance": ygov = interface.YearnGovernance(strategy.gov()) attrs["earned"] = [ygov, "earned", strategy] attrs["reward rate"] = [ygov, "rewardRate"] attrs["ygov balance"] = [ygov, "balanceOf", strategy] attrs["ygov total"] = [ygov, "totalSupply"] # fetch attrs as multicall results = fetch_multicall(*attrs.values(), block=block) scale_overrides = {"share price": 1e18} for name, attr in zip(attrs, results): if attr is not None: info[name] = attr / scale_overrides.get(name, self.scale) else: logger.warning("attr %s rekt %s", name, attr) # some additional post-processing if "min" in info: info["strategy buffer"] = info.pop("min") / info.pop("max") if "token price" not in info: info["token price"] = self.get_price(block=block) info["tvl"] = info["vault balance"] * info["token price"] return info