Exemple #1
0
async def get_periodic_allocations(network: str, from_block: BlockNumber,
                                   to_block: BlockNumber) -> TokenAllocations:
    """Fetches periodic allocations."""
    last_id = ""
    result: Dict = await execute_sw_gql_query(
        network=network,
        query=PERIODIC_DISTRIBUTIONS_QUERY,
        variables=dict(from_block=from_block,
                       to_block=to_block,
                       last_id=last_id),
    )
    distributions_chunk = result.get("periodicDistributions", [])
    distributions = distributions_chunk

    # accumulate chunks of distributions
    while len(distributions_chunk) >= 1000:
        last_id = distributions_chunk[-1]["id"]
        result: Dict = await execute_sw_gql_query(
            network=network,
            query=PERIODIC_DISTRIBUTIONS_QUERY,
            variables=dict(from_block=from_block,
                           to_block=to_block,
                           last_id=last_id),
        )
        distributions_chunk = result.get("periodicDistributions", [])
        distributions.extend(distributions_chunk)

    allocations: TokenAllocations = {}
    for dist in distributions:
        dist_start_block: BlockNumber = BlockNumber(int(
            dist["startedAtBlock"]))
        dist_end_block: BlockNumber = BlockNumber(int(dist["endedAtBlock"]))

        if dist_end_block <= from_block or dist_start_block >= to_block:
            # distributions are out of current range
            continue

        allocation = TokenAllocation(
            from_block=dist_start_block,
            to_block=dist_end_block,
            reward_token=Web3.toChecksumAddress(dist["token"]),
            reward=int(dist["amount"]),
        )
        allocations.setdefault(Web3.toChecksumAddress(dist["beneficiary"]),
                               []).append(allocation)

    return allocations
Exemple #2
0
async def get_partners_rewards(network: str, from_block: BlockNumber,
                               to_block: BlockNumber,
                               total_reward: Wei) -> Tuple[Rewards, Wei]:
    """Fetches partners rewards."""
    result: Dict = await execute_sw_gql_query(
        network=network,
        query=PARTNERS_QUERY,
        variables=dict(block_number=to_block, ),
    )
    partners = result["partners"]
    reward_token_address = NETWORKS[network]["REWARD_TOKEN_CONTRACT_ADDRESS"]

    # process partners
    points: Dict[ChecksumAddress, int] = {}
    total_points = 0
    total_contributed = 0
    for partner in partners:
        account = Web3.toChecksumAddress(partner["id"])
        if account == EMPTY_ADDR_HEX:
            continue

        contributed_amount = Wei(int(partner["contributedAmount"]))
        total_contributed += contributed_amount

        revenue_share = int(partner["revenueShare"])
        prev_account_points = int(partner["distributorPoints"])
        updated_at_block = BlockNumber(int(partner["updatedAtBlock"]))
        if from_block > updated_at_block:
            updated_at_block = from_block
            prev_account_points = 0

        account_points = prev_account_points + (contributed_amount *
                                                revenue_share *
                                                (to_block - updated_at_block))
        if account_points <= 0:
            continue

        points[account] = account_points
        total_points += account_points

    if total_contributed <= 0:
        return {}, total_reward

    partners_reward = Wei(
        (total_reward * total_points) // (total_contributed * 10000 *
                                          (to_block - from_block)))
    if partners_reward <= 0:
        return {}, total_reward

    partners_reward = min(total_reward, partners_reward)
    rewards = calculate_points_based_rewards(
        total_reward=partners_reward,
        points=points,
        total_points=total_points,
        reward_token=reward_token_address,
    )

    return rewards, Wei(total_reward - partners_reward)
Exemple #3
0
async def has_synced_block(network: str, block_number: BlockNumber) -> bool:
    result: Dict = await execute_ethereum_gql_query(
        network=network,
        query=VALIDATOR_REGISTRATIONS_SYNC_BLOCK_QUERY,
        variables={},
    )
    meta = result["_meta"]

    return block_number <= BlockNumber(int(meta["block"]["number"]))
Exemple #4
0
def _get_avg_block_time(w3: Web3, sample_size: int) -> float:
    latest = w3.eth.getBlock('latest')

    constrained_sample_size = min(sample_size, latest['number'])
    if constrained_sample_size == 0:
        raise ValidationError('Constrained sample size is 0')

    oldest = w3.eth.getBlock(BlockNumber(latest['number'] - constrained_sample_size))
    return (latest['timestamp'] - oldest['timestamp']) / constrained_sample_size
Exemple #5
0
def _get_weighted_avg_block_time(w3: Web3, sample_size: int) -> float:
    latest_block_number = w3.eth.getBlock('latest')['number']
    constrained_sample_size = min(sample_size, latest_block_number)
    if constrained_sample_size == 0:
        raise ValidationError('Constrained sample size is 0')

    oldest_block = w3.eth.getBlock(BlockNumber(latest_block_number - constrained_sample_size))
    oldest_block_number = oldest_block['number']
    prev_timestamp = oldest_block['timestamp']
    weighted_sum = 0.0
    sum_of_weights = 0.0
    for i in range(oldest_block_number + 1, latest_block_number + 1):
        curr_timestamp = w3.eth.getBlock(BlockNumber(i))['timestamp']
        time = curr_timestamp - prev_timestamp
        weight = (i - oldest_block_number) / constrained_sample_size
        weighted_sum += (time * weight)
        sum_of_weights += weight
        prev_timestamp = curr_timestamp
    return weighted_sum / sum_of_weights
Exemple #6
0
async def get_latest_block(network: str) -> Block:
    """Gets the latest block number and its timestamp."""
    result: Dict = await execute_ethereum_gql_query(
        network=network,
        query=LATEST_BLOCK_QUERY,
        variables=dict(confirmation_blocks=CONFIRMATION_BLOCKS, ),
    )
    return Block(
        block_number=BlockNumber(int(result["blocks"][0]["id"])),
        timestamp=Timestamp(int(result["blocks"][0]["timestamp"])),
    )
Exemple #7
0
async def get_voting_parameters(network: str,
                                block_number: BlockNumber) -> VotingParameters:
    """Fetches rewards voting parameters."""
    result: Dict = await execute_sw_gql_query(
        network=network,
        query=VOTING_PARAMETERS_QUERY,
        variables=dict(block_number=block_number, ),
    )
    network = result["networks"][0]
    reward_token = result["rewardEthTokens"][0]

    try:
        distributor = result["merkleDistributors"][0]
    except IndexError:
        distributor = {
            "rewardsUpdatedAtBlock": 0,
            "updatedAtBlock": 0,
            "merkleRoot": None,
            "merkleProofs": None,
        }

    rewards = RewardsVotingParameters(
        rewards_nonce=int(network["oraclesRewardsNonce"]),
        total_rewards=Wei(int(reward_token["totalRewards"])),
        rewards_updated_at_timestamp=Timestamp(
            int(reward_token["updatedAtTimestamp"])),
    )
    distributor = DistributorVotingParameters(
        rewards_nonce=int(network["oraclesRewardsNonce"]),
        from_block=BlockNumber(int(distributor["rewardsUpdatedAtBlock"])),
        to_block=BlockNumber(int(reward_token["updatedAtBlock"])),
        last_updated_at_block=BlockNumber(int(distributor["updatedAtBlock"])),
        last_merkle_root=distributor["merkleRoot"],
        last_merkle_proofs=distributor["merkleProofs"],
        protocol_reward=Wei(int(reward_token["protocolPeriodReward"])),
        distributor_reward=Wei(int(reward_token["distributorPeriodReward"])),
    )
    return VotingParameters(rewards=rewards, distributor=distributor)
Exemple #8
0
        def _update_block_info_cache() -> None:
            avg_block_time = block_info.get(AVG_BLOCK_TIME_KEY,
                                            default_average_block_time)
            avg_block_sample_size = block_info.get(AVG_BLOCK_SAMPLE_SIZE_KEY,
                                                   0)
            avg_block_time_updated_at = block_info.get(
                AVG_BLOCK_TIME_UPDATED_AT_KEY, 0)

            # compute age as counted by number of blocks since the avg_block_time
            if avg_block_time == 0:
                avg_block_time_age_in_blocks: float = avg_block_sample_size
            else:
                avg_block_time_age_in_blocks = (
                    (time.time() - avg_block_time_updated_at) / avg_block_time)

            if avg_block_time_age_in_blocks >= avg_block_sample_size:
                # If the length of time since the average block time as
                # measured by blocks is greater than or equal to the number of
                # blocks sampled then we need to recompute the average block
                # time.
                latest_block = web3.eth.getBlock('latest')
                ancestor_block_number = BlockNumber(
                    max(
                        0,
                        latest_block['number'] -
                        average_block_time_sample_size,
                    ))
                ancestor_block = web3.eth.getBlock(ancestor_block_number)
                sample_size = latest_block['number'] - ancestor_block_number

                block_info[AVG_BLOCK_SAMPLE_SIZE_KEY] = sample_size
                if sample_size != 0:
                    block_info[AVG_BLOCK_TIME_KEY] = (
                        (latest_block['timestamp'] -
                         ancestor_block['timestamp']) / sample_size)
                else:
                    block_info[AVG_BLOCK_TIME_KEY] = avg_block_time
                block_info[AVG_BLOCK_TIME_UPDATED_AT_KEY] = time.time()

            if 'latest_block' in block_info:
                latest_block = block_info['latest_block']
                time_since_latest_block = time.time(
                ) - latest_block['timestamp']

                # latest block is too old so update cache
                if time_since_latest_block > avg_block_time:
                    block_info['latest_block'] = web3.eth.getBlock('latest')
            else:
                # latest block has not been fetched so we fetch it.
                block_info['latest_block'] = web3.eth.getBlock('latest')
Exemple #9
0
async def get_voting_parameters(network: str) -> ValidatorVotingParameters:
    """Fetches validator voting parameters."""
    result: Dict = await execute_sw_gql_query(
        network=network,
        query=VALIDATOR_VOTING_PARAMETERS_QUERY,
        variables={},
    )
    network = result["networks"][0]
    pool = result["pools"][0]
    meta = result["_meta"]
    return ValidatorVotingParameters(
        validators_nonce=int(network["oraclesValidatorsNonce"]),
        pool_balance=Wei(int(pool["balance"])),
        latest_block_number=BlockNumber(int(meta["block"]["number"])),
    )
Exemple #10
0
def create_latest_block_uri(w3: "Web3", from_blocks_ago: int = 3) -> URI:
    """
    Creates a block uri for the given w3 instance.
    Defaults to 3 blocks prior to the "latest" block to accommodate for block reorgs.
    If using a testnet with less than 3 mined blocks, adjust :from_blocks_ago:.
    """
    chain_id = to_hex(get_genesis_block_hash(w3))
    latest_block_tx_receipt = w3.eth.get_block("latest")
    target_block_number = BlockNumber(latest_block_tx_receipt["number"] - from_blocks_ago)
    if target_block_number < 0:
        raise Exception(
            f"Only {latest_block_tx_receipt['number']} blocks avaible on provided w3, "
            f"cannot create latest block uri for {from_blocks_ago} blocks ago."
        )
    recent_block = to_hex(w3.eth.get_block(target_block_number)["hash"])
    return create_block_uri(chain_id, recent_block)
Exemple #11
0
async def get_one_time_rewards(network: str, from_block: BlockNumber,
                               to_block: BlockNumber) -> Rewards:
    """Fetches one time rewards."""
    distributor_fallback_address = NETWORKS[network][
        "DISTRIBUTOR_FALLBACK_ADDRESS"]

    last_id = ""
    result: Dict = await execute_sw_gql_query(
        network=network,
        query=ONE_TIME_DISTRIBUTIONS_QUERY,
        variables=dict(from_block=from_block,
                       to_block=to_block,
                       last_id=last_id),
    )
    distributions_chunk = result.get("oneTimeDistributions", [])
    distributions = distributions_chunk

    # accumulate chunks of distributions
    while len(distributions_chunk) >= 1000:
        last_id = distributions_chunk[-1]["id"]
        result: Dict = await execute_sw_gql_query(
            network=network,
            query=ONE_TIME_DISTRIBUTIONS_QUERY,
            variables=dict(from_block=from_block,
                           to_block=to_block,
                           last_id=last_id),
        )
        distributions_chunk = result.get("oneTimeDistributions", [])
        distributions.extend(distributions_chunk)

    # process one time distributions
    final_rewards: Rewards = {}
    for distribution in distributions:
        distributed_at_block = BlockNumber(
            int(distribution["distributedAtBlock"]))
        if not (from_block < distributed_at_block <= to_block):
            continue

        total_amount = int(distribution["amount"])
        distributed_amount = 0
        token = Web3.toChecksumAddress(distribution["token"])
        rewards: Rewards = {}
        try:
            allocated_rewards = await get_one_time_rewards_allocations(
                distribution["rewardsLink"])
            for beneficiary, amount in allocated_rewards.items():
                if beneficiary == EMPTY_ADDR_HEX:
                    continue

                rewards.setdefault(beneficiary, {})[token] = amount
                distributed_amount += int(amount)

            if total_amount != distributed_amount:
                logger.warning(
                    f'[{network}] Failed to process one time distribution: {distribution["id"]}. Invalid rewards.'
                )
                rewards: Rewards = {
                    distributor_fallback_address: {
                        token: str(total_amount)
                    }
                }
        except Exception as e:
            logger.error(e)
            logger.warning(
                f'[{network}] Failed to process one time distribution: {distribution["id"]}. Exception occurred.'
            )
            rewards: Rewards = {
                distributor_fallback_address: {
                    token: str(total_amount)
                }
            }

        final_rewards = DistributorRewards.merge_rewards(
            final_rewards, rewards)

    return final_rewards
Exemple #12
0
async def get_operators_rewards(
    network: str,
    from_block: BlockNumber,
    to_block: BlockNumber,
    total_reward: Wei,
) -> Tuple[Rewards, Wei]:
    """Fetches operators rewards."""
    result: Dict = await execute_sw_gql_query(
        network=network,
        query=OPERATORS_REWARDS_QUERY,
        variables=dict(block_number=to_block, ),
    )
    operators = result["operators"]
    reward_token_address = NETWORKS[network]["REWARD_TOKEN_CONTRACT_ADDRESS"]

    # process operators
    points: Dict[ChecksumAddress, int] = {}
    total_points = 0
    total_validators = 0
    for operator in operators:
        account = Web3.toChecksumAddress(operator["id"])
        if account == EMPTY_ADDR_HEX:
            continue

        validators_count = int(operator["validatorsCount"])
        total_validators += validators_count

        revenue_share = int(operator["revenueShare"])
        prev_account_points = int(operator["distributorPoints"])
        updated_at_block = BlockNumber(int(operator["updatedAtBlock"]))
        if from_block > updated_at_block:
            updated_at_block = from_block
            prev_account_points = 0

        account_points = prev_account_points + (validators_count *
                                                revenue_share *
                                                (to_block - updated_at_block))
        if account_points <= 0:
            continue

        points[account] = points.get(account, 0) + account_points
        total_points += account_points

    if total_validators <= 0:
        return {}, total_reward

    operators_reward = Wei(
        (total_reward * total_points) // (total_validators * 10000 *
                                          (to_block - from_block)))
    if operators_reward <= 0:
        return {}, total_reward

    operators_reward = min(total_reward, operators_reward)
    rewards = calculate_points_based_rewards(
        total_reward=operators_reward,
        points=points,
        total_points=total_points,
        reward_token=reward_token_address,
    )

    return rewards, Wei(total_reward - operators_reward)