Пример #1
0
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)
Пример #2
0
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)
Пример #3
0
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
Пример #4
0
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)
Пример #5
0
 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()
Пример #6
0
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
Пример #7
0
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
Пример #8
0
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])
Пример #9
0
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)
Пример #10
0
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)
Пример #11
0
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"
    )
Пример #12
0
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)
Пример #13
0
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)
Пример #14
0
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)
Пример #15
0
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)