def update_sett_gauge(sett_gauge, sett): sett_name = sett.name sett_address = sett_vaults[sett_name] sett_token_name = sett_name[1:] sett_token_address = treasury_tokens[re.sub("harvest", "", sett_token_name)] sett_info = sett.describe() log.info(f"Processing Sett data for [bold]{sett.name}: {sett_address} ...") for param, value in sett_info.items(): sett_gauge.labels(sett_name, param, sett_address, sett_token_name).set(value) try: usd_prices_by_token_address[sett_address] = ( sett_info["pricePerShare"] * usd_prices_by_token_address[sett_token_address]) sett_gauge.labels(sett_name, "usdBalance", sett_address, sett_token_name).set( usd_prices_by_token_address[sett_address] * sett_info["balance"]) except Exception as e: log.warning(f"Error calculating USD price for Sett [bold]{sett_name}") log.debug(e)
def update_price_gauge(coingecko_price_gauge, token_prices, token_name, token_address, countertoken_csv): lp_prefixes = ("uni", "slp", "crv", "cake") try: if not token_name.startswith(lp_prefixes): log.info( f"Processing CoinGecko price for [bold]{token_name}: {token_address} ..." ) price = token_prices[token_address.lower()] for countertoken in countertoken_csv.split(","): coingecko_price_gauge.labels( "ETH" if token_name == "WETH" else "BNB" if token_name == "WBNB" else token_name, countertoken, token_address, ).set(price[countertoken]) usd_prices_by_token_address[token_address] = price["usd"] else: log.info( f"Skipping CoinGecko price for [bold]{token_name}: {token_address} ..." ) except Exception as e: log.warning( f"Error getting CoinGecko price for [bold]{token_name}: {token_address}" ) log.debug(e)
def get_token_prices(treasury_tokens, token_csv, countertoken_csv): log.info("Fetching token prices from CoinGecko ...") token_prices = get_json_request( request_type="get", url= f"https://api.coingecko.com/api/v3/simple/token_price/ethereum?contract_addresses={token_csv}&vs_currencies={countertoken_csv}", ) return token_prices
def update_rewards_gauge(rewards_gauge, badgertree, badger, digg): log.info(f"Calculating Badgertree reward holdings ...") badger_rewards = badger.balanceOf(badgertree.address) / 1e18 digg_rewards = digg.balanceOf(badgertree.address) / 1e9 rewards_gauge.labels("BADGER", treasury_tokens["BADGER"]).set(badger_rewards) rewards_gauge.labels("DIGG", treasury_tokens["DIGG"]).set(digg_rewards)
def restore(self): """Restore the last scan state from a file""" try: self.state = json.load(open(self.fname, "rt")) log.info( f"Restored previous state up to block {self.state['last_scanned_block']}" ) except (IOError, json.decoder.JSONDecodeError): log.info("State JSON not found; starting from scratch") self.reset()
def process_transaction(web3, tx_hash, block_gauge, token_flow_counter, fees_counter): tx = web3.eth.getTransaction(tx_hash) tx_logs = web3.eth.getTransactionReceipt(tx_hash).logs block_number = tx.blockNumber block_timestamp = web3.eth.get_block(block_number)["timestamp"] erc20_abi = json.load(open("interfaces/ERC20.json", "r")) erc20 = web3.eth.contract(abi=erc20_abi) erc20_transfer_abi = erc20.events.Transfer._get_event_abi() tokens = { transfer: 0 for transfer in [ "ren_minted", "ren_received", "ren_bought", "ren_burned", "ren_sent", "wbtc_received", "wbtc_sent", "fee_badger", "fee_renvm", ] } for log in tx_logs: try: event_data = get_event_data(web3.codec, erc20_transfer_abi, log) tokens = update_tokens(tx_hash, event_data, tokens) except MismatchedABI: # not ERC20 transfer, so skip continue balances = calc_balances(tokens) # update counters block_gauge.labels("block_number").set(block_number) block_gauge.labels("block_timestamp").set(block_timestamp) token_flow_counter.labels("BTC", "mint", "in").inc(balances["btc_in"]) token_flow_counter.labels("BTC", "burn", "out").inc(balances["btc_out"]) token_flow_counter.labels("WBTC", "burn", "in").inc(balances["wbtc_received"]) token_flow_counter.labels("WBTC", "mint", "out").inc(balances["wbtc_sent"]) token_flow_counter.labels("renBTC", "burn", "in").inc(balances["ren_received"]) token_flow_counter.labels("renBTC", "mint", "out").inc(balances["ren_sent"]) fees_counter.labels("Badger DAO").inc(balances["fee_badger_dao"]) fees_counter.labels("Badger Bridge Team").inc(balances["fee_badger_bridge"]) fees_counter.labels("RenVM Darknodes").inc(balances["fee_darknodes"]) logger.info( f"Processed event: block timestamp {block_timestamp} block number {block_number}, hash {tx_hash}" ) return tokens, balances
def update_crv_tokens_gauge(crv_tokens_gauge, pool_name, pool_address): log.info( f"Processing crvToken data for [bold]{pool_name}: {pool_address} ...") pool_token_name = pool_name pool_token_address = treasury_tokens[pool_token_name] wbtc_address = treasury_tokens["WBTC"] virtual_price = interface.CRVswap(pool_address).get_virtual_price() / 1e18 usd_price = virtual_price * usd_prices_by_token_address[wbtc_address] crv_tokens_gauge.labels(pool_name, "pricePerShare", wbtc_address).set(virtual_price) crv_tokens_gauge.labels(pool_name, "usdPricePerShare", wbtc_address).set(usd_price) usd_prices_by_token_address[pool_token_address] = usd_price
def update_xchain_bridge_gauge(xchain_bridge_gauge, custodian_name, custodian_address, token_interfaces): log.info( f"Checking balances on bridge [bold]{custodian_name}: {custodian_address} ..." ) for token_name in NATIVE_TOKENS: token_address = treasury_tokens[token_name] token_interface = token_interfaces[token_address] token_scale = 10**token_interface.decimals() token_balance = token_interface.balanceOf(custodian_address) xchain_bridge_gauge.labels("BSC", token_name, custodian_name, "balance").set(token_balance / token_scale) xchain_bridge_gauge.labels( "BSC", token_name, custodian_name, "usdBalance").set( (token_balance / token_scale) * usd_prices_by_token_address[token_address])
def update_wallets_gauge(wallets_gauge, wallet_balances_by_token, token_name, token_address): log.info( f"Processing wallet balances for [bold]{token_name}: {token_address} ..." ) wallet_info = wallet_balances_by_token[token_address] for wallet in wallet_info.describe(): ( token_name, token_address, token_balance, wallet_name, wallet_address, ) = wallet.values() eth_balance = float( w3.fromWei(w3.eth.getBalance(wallet_address), "ether")) wallets_gauge.labels(wallet_name, wallet_address, token_name, token_address, "balance").set(token_balance) wallets_gauge.labels(wallet_name, wallet_address, "ETH", "None", "balance").set(eth_balance) try: wallets_gauge.labels( wallet_name, wallet_address, token_name, token_address, "usdBalance").set(token_balance * usd_prices_by_token_address[token_address]) wallets_gauge.labels( wallet_name, wallet_address, "ETH", "none", "usdBalance").set( eth_balance * usd_prices_by_token_address[treasury_tokens["WETH"]]) except Exception as e: log.warning( f"Error calculating USD balances for wallet [bold]{wallet_name}" ) log.debug(e)
def update_lp_tokens_gauge(lp_tokens_gauge, lp_token, token_interfaces): lp_name = lp_token.name lp_address = lp_tokens[lp_name] log.info( f"Processing lpToken reserves for [bold]{lp_name}: {lp_address} ...") lp_info = lp_token.describe() lp_scale = 10**lp_info["decimals"] lp_supply = lp_info["totalSupply"] token0_address = lp_info["token0"] token1_address = lp_info["token1"] token0 = token_interfaces[token0_address] token1 = token_interfaces[token1_address] token0_reserve = lp_info["token0_reserve"] token1_reserve = lp_info["token1_reserve"] token0_scale = 10**token0.decimals() token1_scale = 10**token1.decimals() lp_tokens_gauge.labels(lp_name, f"{token0.symbol()}_supply", lp_address).set(token0_reserve / token0_scale) lp_tokens_gauge.labels(lp_name, f"{token1.symbol()}_supply", lp_address).set(token1_reserve / token1_scale) lp_tokens_gauge.labels(lp_name, "totalLpTokenSupply", lp_address).set(lp_supply / lp_scale) try: price = (((token1_reserve / token1_scale) / (lp_supply / lp_scale)) * usd_prices_by_token_address[token1_address] * 2) usd_prices_by_token_address[lp_address] = price lp_tokens_gauge.labels(lp_name, "usdPricePerShare", lp_address).set(price) except Exception as e: log.warning(f"Error calculating USD price for lpToken [bold]{lp_name}") log.debug(e)
def run_scan(scanner, state, block_gauge, token_flow_counter, fees_counter): # discard last few blocks in case of chain reorgs scanner.delete_potentially_forked_block_data( state.get_last_scanned_block() - CHAIN_REORG_SAFETY_BLOCKS) # scan from last scanned block to latest block # min starting block is bridge contract creation block start_block = max( state.get_last_scanned_block() - CHAIN_REORG_SAFETY_BLOCKS, BLOCK_START) end_block = scanner.get_suggested_scan_end_block() state.set_intended_end_block(end_block) log.info( f"Scanning for bridge contract transactions from block {start_block} to {end_block}" ) # run the scan result, total_chunks_scanned = scanner.scan(start_block, end_block) state.save() # process new mint/burn transactions tx_hashes = [] for block_number, hash_list in state.state["blocks"].items(): if (int(block_number) >= start_block and int(block_number) < end_block - CHAIN_REORG_SAFETY_BLOCKS): tx_hashes.extend(hash_list) log.info( f"Processing transaction events from {start_block} to {end_block}") for tx_hash in tx_hashes: process_transaction(w3, tx_hash, block_gauge, token_flow_counter, fees_counter) log.info(f"Blocks {start_block} to {end_block} complete.") log.info( f"Sleeping for {POLL_INTERVAL} seconds before starting next block chunks" )
def update_digg_gauge(digg_gauge, digg_prices, slpWbtcDigg, uniWbtcDigg): # process digg oracle price digg_oracle_price = digg_prices.describe() for param, value in digg_oracle_price.items(): log.info( f"Processing Oracle param [bold]{param}[/] for [bold]DIGG: {digg_prices.oracle.address} ..." ) digg_gauge.labels(param).set(value) # process digg AMM prices log.info( f"Processing SushiSwap price for [bold]DIGG: {uniWbtcDigg.address} ..." ) digg_uni_price = (uniWbtcDigg.getReserves()[0] / 1e8) / (uniWbtcDigg.getReserves()[1] / 1e9) digg_gauge.labels("uniswap").set(digg_uni_price) log.info( f"Processing Uniswap price for [bold]DIGG: {slpWbtcDigg.address} ...") digg_sushi_price = (slpWbtcDigg.getReserves()[0] / 1e8) / (slpWbtcDigg.getReserves()[1] / 1e9) digg_gauge.labels("sushiswap").set(digg_sushi_price)
def main(): # set up prometheus log.info( f"Starting Prometheus scout-collector server at http://localhost:{PROMETHEUS_PORT}" ) block_gauge = Gauge( name="blocks", documentation="Info about blocks processed", ) coingecko_price_gauge = Gauge( name="coingecko_prices", documentation="Token price data from Coingecko", labelnames=["token", "countercurrency", "tokenAddress"], ) digg_gauge = Gauge( name="digg_price", documentation="Digg price data from oracle and AMMs", labelnames=["value"], ) lp_tokens_gauge = Gauge( name="lptokens", documentation="LP token data", labelnames=["token", "param", "tokenAddress"], ) crv_tokens_gauge = Gauge( name="crvtokens", documentation="CRV token data", labelnames=["token", "param", "tokenAddress"], ) sett_gauge = Gauge( name="sett", documentation="Badger Sett vaults data", labelnames=["sett", "param", "tokenAddress", "token"], ) wallets_gauge = Gauge( name="wallets", documentation="Watched wallet balances", labelnames=[ "walletName", "walletAddress", "token", "tokenAddress", "param" ], ) xchain_bridge_gauge = Gauge( name="xchainBridge", documentation="Info about tokens in custody", labelnames=["chain", "token", "bridge", "param"], ) rewards_gauge = Gauge( name="rewards", documentation="Badgertree reward holdings", labelnames=["token", "tokenAddress"], ) cycle_gauge = Gauge( name="badgertree", documentation="Badgertree reward timestamp", labelnames=["lastCycleUnixtime"], ) start_http_server(PROMETHEUS_PORT) # get all data num_treasury_tokens = len(treasury_tokens) str_treasury_tokens = "".join([ f"\n\t[bold]{token_name}: {token_address}" for token_name, token_address in treasury_tokens.items() ]) log.info( f"Loading ERC20 interfaces for treasury tokens ... {str_treasury_tokens}" ) token_interfaces = get_token_interfaces(treasury_tokens) badger = token_interfaces[treasury_tokens["BADGER"]] digg = token_interfaces[treasury_tokens["DIGG"]] wbtc = token_interfaces[treasury_tokens["WBTC"]] wallet_balances_by_token = get_wallet_balances_by_token( badger_wallets, treasury_tokens) lp_data = get_lp_data(lp_tokens) sett_data = get_sett_data(sett_vaults) digg_prices = get_digg_data(oracles["oracle"], oracles["oracle_provider"]) slpWbtcDigg = interface.Pair(lp_tokens["slpWbtcDigg"]) uniWbtcDigg = interface.Pair(lp_tokens["uniWbtcDigg"]) badgertree = interface.Badgertree(badger_wallets["badgertree"]) badgertree_cycles = get_badgertree_data(badgertree) # coingecko price query variables token_csv = ",".join(treasury_tokens.values()) countertoken_csv = "usd" # scan new blocks and update gauges for step, block in enumerate(chain.new_blocks(height_buffer=1)): timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") console.print() console.rule( title= f"[green]{timestamp} step number {step}, block number {block.number}" ) block_gauge.set(block.number) # process token prices token_prices = get_token_prices(treasury_tokens, token_csv, countertoken_csv) for token_name, token_address in treasury_tokens.items(): update_price_gauge( coingecko_price_gauge, token_prices, token_name, token_address, countertoken_csv, ) # process digg oracle prices update_digg_gauge(digg_gauge, digg_prices, slpWbtcDigg, uniWbtcDigg) # process lp data for lp_token in lp_data: update_lp_tokens_gauge(lp_tokens_gauge, lp_token, token_interfaces) # process curve pool data for pool_name, pool_address in crv_pools.items(): update_crv_tokens_gauge(crv_tokens_gauge, pool_name, pool_address) # process sett data for sett in sett_data: update_sett_gauge(sett_gauge, sett) # process wallet balances for *one* treasury token token_name, token_address = list( treasury_tokens.items())[step % num_treasury_tokens] update_wallets_gauge(wallets_gauge, wallet_balances_by_token, token_name, token_address) # process bridged tokens for custodian_name, custodian_address in custodians.items(): update_xchain_bridge_gauge(xchain_bridge_gauge, custodian_name, custodian_address, token_interfaces) # process rewards balances update_rewards_gauge(rewards_gauge, badgertree, badger, digg) # process badgertree cycles last_cycle_unixtime = badgertree_cycles.describe() update_cycle_gauge(cycle_gauge, last_cycle_unixtime)
def update_cycle_gauge(cycle_gauge, last_cycle_unixtime): for param, value in last_cycle_unixtime.items(): log.info(f"Processing Badgertree [bold]{param} ...") cycle_gauge.labels(param).set(value)
def main(): # set up prometheus log.info( f"Starting Prometheus events server at http://localhost:{PROMETHEUS_PORT_FORWARDED}" ) block_gauge = Gauge(name="block_info", documentation="block_info", labelnames=["info"]) token_flow_counter = Counter( name="token_flow", documentation="token,event,direction", labelnames=["token", "event", "direction"], ) fees_counter = Counter( name="fees", documentation="entity", labelnames=["entity"], ) start_http_server(PROMETHEUS_PORT) # set up event filters # filter bridge contract events # bridge_abi = open("interfaces/Bridge.json", "r").read() # bridge = w3.eth.contract(address=ADDRESSES["bridge_v2"], abi=bridge_abi) # console.log(f"Read Badger BTC Bridge contract at address {ADDRESSES['bridge_v2']}") # filters = [ # bridge.events.Mint.createFilter(fromBlock=BLOCK_START, toBlock="latest"), # bridge.events.Burn.createFilter(fromBlock=BLOCK_START, toBlock="latest"), # ] # watch events # chain = Chain() # console.log( # f"Processing prior events from block {BLOCK_START} to {w3.eth.blockNumber}" # ) # process_prior_events(chain, filters, block_gauge, token_flow_counter, fees_counter) # console.log("Listening for new events in latest blocks...") # listen_new_events( # chain, filters, block_gauge, token_flow_counter, fees_counter, POLL_INTERVAL # ) # --------------------------------------------------------------------------------- # set up scanner and scanner state # scan all blocks for Mint/Burn events with `eth_getLog` # works with nodes where `eth_newFilter` is not supported # erc20_abi = json.loads("interfaces/ERC20.json") # erc20 = web3.eth.contract(abi=abi) # wbtc = w3.eth.contract(address=ADDRESSES["WBTC"], abi=erc20_abi) # renbtc = w3.eth.contract(address=ADDRESSES["renBTC"], abi=erc20_abi) log.info( f"Reading Badger BTC Bridge contract at address {ADDRESSES['bridge_v2']}" ) bridge_abi = json.load(open("interfaces/Bridge.json", "r")) bridge = w3.eth.contract(address=ADDRESSES["bridge_v2"], abi=bridge_abi) state = BridgeScannerState() state.restore() scanner = EventScanner( web3=w3, contract=bridge, state=state, events=[bridge.events.Mint, bridge.events.Burn], filters={}, num_blocks_rescan_for_forks=CHAIN_REORG_SAFETY_BLOCKS, max_chunk_scan_size=10000, ) while True: run_scan(scanner, state, block_gauge, token_flow_counter, fees_counter) time.sleep(POLL_INTERVAL)