diff --git a/.dockerignore b/.dockerignore index 88103c7..c57c507 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,7 +1,8 @@ /.git /.idea /.github -venv +/venv +/dist .mypy_cache Dockerfile .dockerignore diff --git a/.gitignore b/.gitignore index 70ce279..2bef9dd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ __pycache__/ venv +dist .mypy_cache .idea local.env diff --git a/main.py b/main.py index d35ff47..2ca8ecb 100644 --- a/main.py +++ b/main.py @@ -5,13 +5,13 @@ import aiohttp -from src.distributor.controller import DistributorController -from src.eth1 import check_oracle_account, get_finalized_block, get_voting_parameters -from src.ipfs import check_or_create_ipns_keys -from src.rewards.controller import RewardsController -from src.rewards.eth2 import get_finality_checkpoints, get_genesis -from src.settings import LOG_LEVEL, PROCESS_INTERVAL -from src.validators.controller import ValidatorsController +from oracle.distributor.controller import DistributorController +from oracle.eth1 import check_oracle_account, get_finalized_block, get_voting_parameters +from oracle.ipfs import check_or_create_ipns_keys +from oracle.rewards.controller import RewardsController +from oracle.rewards.eth2 import get_finality_checkpoints, get_genesis +from oracle.settings import LOG_LEVEL, PROCESS_INTERVAL +from oracle.validators.controller import ValidatorsController logging.basicConfig( format="%(asctime)s %(name)-12s %(levelname)-8s %(message)s", @@ -42,12 +42,12 @@ def exit_gracefully(self, signum: int, frame: Any) -> None: async def main() -> None: - # aiohttp session - session = aiohttp.ClientSession() - # check stakewise graphql connection await get_finalized_block() + # aiohttp session + session = aiohttp.ClientSession() + # check ETH2 API connection await get_finality_checkpoints(session) diff --git a/src/__init__.py b/oracle/__init__.py similarity index 100% rename from src/__init__.py rename to oracle/__init__.py diff --git a/oracle/clients.py b/oracle/clients.py new file mode 100644 index 0000000..6eefa04 --- /dev/null +++ b/oracle/clients.py @@ -0,0 +1,27 @@ +import logging +from typing import Dict + +import backoff +from gql import Client +from gql.transport.aiohttp import AIOHTTPTransport +from graphql import DocumentNode + +from oracle.settings import STAKEWISE_SUBGRAPH_URL, UNISWAP_V3_SUBGRAPH_URL + +logger = logging.getLogger(__name__) + + +@backoff.on_exception(backoff.expo, Exception, max_time=300) +async def execute_sw_gql_query(query: DocumentNode, variables: Dict) -> Dict: + """Executes GraphQL query.""" + transport = AIOHTTPTransport(url=STAKEWISE_SUBGRAPH_URL) + async with Client(transport=transport, fetch_schema_from_transport=True) as session: + return await session.execute(query, variable_values=variables) + + +@backoff.on_exception(backoff.expo, Exception, max_time=300) +async def execute_uniswap_v3_gql_query(query: DocumentNode, variables: Dict) -> Dict: + """Executes GraphQL query.""" + transport = AIOHTTPTransport(url=UNISWAP_V3_SUBGRAPH_URL) + async with Client(transport=transport, fetch_schema_from_transport=True) as session: + return await session.execute(query, variable_values=variables) diff --git a/src/distributor/__init__.py b/oracle/distributor/__init__.py similarity index 100% rename from src/distributor/__init__.py rename to oracle/distributor/__init__.py diff --git a/src/distributor/controller.py b/oracle/distributor/controller.py similarity index 98% rename from src/distributor/controller.py rename to oracle/distributor/controller.py index 5101360..b48d9f4 100644 --- a/src/distributor/controller.py +++ b/oracle/distributor/controller.py @@ -3,8 +3,11 @@ from web3 import Web3 -from src.ipfs import submit_ipns_vote -from src.settings import REWARD_ETH_TOKEN_CONTRACT_ADDRESS, SWISE_TOKEN_CONTRACT_ADDRESS +from oracle.ipfs import submit_ipns_vote +from oracle.settings import ( + REWARD_ETH_TOKEN_CONTRACT_ADDRESS, + SWISE_TOKEN_CONTRACT_ADDRESS, +) from .eth1 import ( get_active_tokens_allocations, diff --git a/src/distributor/eth1.py b/oracle/distributor/eth1.py similarity index 85% rename from src/distributor/eth1.py rename to oracle/distributor/eth1.py index fa34d9e..14ddba5 100644 --- a/src/distributor/eth1.py +++ b/oracle/distributor/eth1.py @@ -6,14 +6,14 @@ from web3 import Web3 from web3.types import BlockNumber, Wei -from src.clients import execute_graphql_query, sw_gql_client -from src.graphql_queries import ( +from oracle.clients import execute_sw_gql_query +from oracle.graphql_queries import ( ACTIVE_TOKEN_DISTRIBUTIONS_QUERY, DISABLED_STAKER_ACCOUNTS_QUERY, DISTRIBUTOR_CLAIMED_ACCOUNTS_QUERY, SWISE_HOLDERS_QUERY, ) -from src.settings import ( +from oracle.settings import ( REWARD_ETH_TOKEN_CONTRACT_ADDRESS, STAKED_ETH_TOKEN_CONTRACT_ADDRESS, SWISE_TOKEN_CONTRACT_ADDRESS, @@ -36,8 +36,7 @@ async def get_active_tokens_allocations( ) -> TokenAllocations: """Fetches active token allocations.""" last_id = "" - result: Dict = await execute_graphql_query( - client=sw_gql_client, + result: Dict = await execute_sw_gql_query( query=ACTIVE_TOKEN_DISTRIBUTIONS_QUERY, variables=dict(from_block=from_block, to_block=to_block, last_id=last_id), ) @@ -50,8 +49,7 @@ async def get_active_tokens_allocations( if not last_id: break - result: Dict = await execute_graphql_query( - client=sw_gql_client, + result: Dict = await execute_sw_gql_query( query=ACTIVE_TOKEN_DISTRIBUTIONS_QUERY, variables=dict(from_block=from_block, to_block=to_block, last_id=last_id), ) @@ -89,8 +87,7 @@ async def get_disabled_stakers_reward_eth_distributions( return [] last_id = "" - result: Dict = await execute_graphql_query( - client=sw_gql_client, + result: Dict = await execute_sw_gql_query( query=DISABLED_STAKER_ACCOUNTS_QUERY, variables=dict(block_number=to_block, last_id=last_id), ) @@ -103,8 +100,7 @@ async def get_disabled_stakers_reward_eth_distributions( if not last_id: break - result: Dict = await execute_graphql_query( - client=sw_gql_client, + result: Dict = await execute_sw_gql_query( query=DISABLED_STAKER_ACCOUNTS_QUERY, variables=dict(block_number=to_block, last_id=last_id), ) @@ -128,7 +124,7 @@ async def get_disabled_stakers_reward_eth_distributions( continue principals[staker_address] = staker_principal - distributor_principal += staker_principal + distributor_principal += Wei(staker_principal) if distributor_principal <= 0: return [] @@ -154,7 +150,7 @@ async def get_disabled_stakers_reward_eth_distributions( reward=reward, ) distributions.append(distribution) - distributed += reward + distributed += Wei(reward) return distributions @@ -163,8 +159,7 @@ async def get_disabled_stakers_reward_eth_distributions( async def get_distributor_claimed_accounts(merkle_root: HexStr) -> ClaimedAccounts: """Fetches addresses that have claimed their tokens from the `MerkleDistributor` contract.""" last_id = "" - result: Dict = await execute_graphql_query( - client=sw_gql_client, + result: Dict = await execute_sw_gql_query( query=DISTRIBUTOR_CLAIMED_ACCOUNTS_QUERY, variables=dict(merkle_root=merkle_root, last_id=last_id), ) @@ -177,8 +172,7 @@ async def get_distributor_claimed_accounts(merkle_root: HexStr) -> ClaimedAccoun if not last_id: break - result: Dict = await execute_graphql_query( - client=sw_gql_client, + result: Dict = await execute_sw_gql_query( query=DISTRIBUTOR_CLAIMED_ACCOUNTS_QUERY, variables=dict(merkle_root=merkle_root, last_id=last_id), ) @@ -194,8 +188,7 @@ async def get_swise_holders( ) -> Balances: """Fetches SWISE holding points.""" last_id = "" - result: Dict = await execute_graphql_query( - client=sw_gql_client, + result: Dict = await execute_sw_gql_query( query=SWISE_HOLDERS_QUERY, variables=dict(block_number=to_block, last_id=last_id), ) @@ -208,8 +201,7 @@ async def get_swise_holders( if not last_id: break - result: Dict = await execute_graphql_query( - client=sw_gql_client, + result: Dict = await execute_sw_gql_query( query=SWISE_HOLDERS_QUERY, variables=dict(block_number=to_block, last_id=last_id), ) @@ -241,18 +233,25 @@ async def get_swise_holders( total_points += account_holding_points # process unclaimed SWISE - for account, rewards in unclaimed_rewards.items(): - balance = int(rewards.get(SWISE_TOKEN_CONTRACT_ADDRESS, "0")) - if balance <= 0: + for account in unclaimed_rewards: + origins = unclaimed_rewards.get(account, {}).get( + SWISE_TOKEN_CONTRACT_ADDRESS, {} + ) + if not origins: continue - account_holding_points = balance * (to_block - from_block) - if account_holding_points <= 0: - continue + for origin, balance in origins.items(): + balance = int(balance) + if balance <= 0: + continue - holding_points[account] = ( - holding_points.setdefault(account, 0) + account_holding_points - ) - total_points += account_holding_points + account_holding_points = balance * (to_block - from_block) + if account_holding_points <= 0: + continue + + holding_points[account] = ( + holding_points.setdefault(account, 0) + account_holding_points + ) + total_points += account_holding_points return Balances(total_supply=total_points, balances=holding_points) diff --git a/src/distributor/ipfs.py b/oracle/distributor/ipfs.py similarity index 61% rename from src/distributor/ipfs.py rename to oracle/distributor/ipfs.py index d3c4a4a..e526d0f 100644 --- a/src/distributor/ipfs.py +++ b/oracle/distributor/ipfs.py @@ -1,10 +1,9 @@ import logging -from typing import Dict import backoff import ipfshttpclient -from src.settings import IPFS_ENDPOINT +from oracle.settings import IPFS_ENDPOINT from .types import ClaimedAccounts, Claims, Rewards @@ -19,20 +18,23 @@ def get_unclaimed_balances( merkle_proofs = merkle_proofs.replace("ipfs://", "").replace("/ipfs/", "") with ipfshttpclient.connect(IPFS_ENDPOINT) as client: - prev_claims: Dict = client.get_json(merkle_proofs) + prev_claims: Claims = client.get_json(merkle_proofs) - unclaimed_rewards: Rewards = Rewards({}) + unclaimed_rewards: Rewards = {} for account, claim in prev_claims.items(): if account in claimed_accounts: continue - # TODO: remove after first v2 merkle root update - key = "reward_tokens" if "reward_tokens" in claim else "tokens" - for token, reward in zip(claim[key], claim["values"]): - prev_unclaimed = unclaimed_rewards.setdefault(account, {}).setdefault( - token, "0" - ) - unclaimed_rewards[account][token] = str(int(prev_unclaimed) + int(reward)) + for i, reward_token in enumerate(claim["reward_tokens"]): + for origin, value in zip(claim["origins"][i], claim["values"][i]): + prev_unclaimed = ( + unclaimed_rewards.setdefault(account, {}) + .setdefault(reward_token, {}) + .setdefault(origin, "0") + ) + unclaimed_rewards[account][reward_token][origin] = str( + int(prev_unclaimed) + int(value) + ) return unclaimed_rewards @@ -40,6 +42,7 @@ def get_unclaimed_balances( @backoff.on_exception(backoff.expo, Exception, max_time=900) def upload_claims(claims: Claims) -> str: """Submits claims to the IPFS and pins the file.""" + # TODO: split claims into files up to 1000 entries with ipfshttpclient.connect(IPFS_ENDPOINT) as client: ipfs_id = client.add_json(claims) client.pin.add(ipfs_id) diff --git a/src/distributor/merkle_tree.py b/oracle/distributor/merkle_tree.py similarity index 79% rename from src/distributor/merkle_tree.py rename to oracle/distributor/merkle_tree.py index 10517ab..dbf2d94 100644 --- a/src/distributor/merkle_tree.py +++ b/oracle/distributor/merkle_tree.py @@ -101,7 +101,7 @@ def get_merkle_node( index: int, tokens: List[ChecksumAddress], account: ChecksumAddress, - values: List[str], + values: List[int], ) -> bytes: """Generates node for merkle tree.""" encoded_data: bytes = w3.codec.encode_abi( @@ -117,16 +117,31 @@ def calculate_merkle_root(rewards: Rewards) -> Tuple[HexStr, Claims]: accounts: List[ChecksumAddress] = sorted(rewards.keys()) claims: Claims = OrderedDict() for i, account in enumerate(accounts): - tokens: List[ChecksumAddress] = sorted(rewards[account].keys()) - values: List[str] = [rewards[account][token] for token in tokens] - claim: Claim = OrderedDict(index=i, tokens=tokens, values=values) + reward_tokens: List[ChecksumAddress] = sorted(rewards[account].keys()) + claim: Claim = OrderedDict(index=i, reward_tokens=reward_tokens) + + reward_token_amounts: Dict[ChecksumAddress, int] = {} + for reward_token in reward_tokens: + origins: List[ChecksumAddress] = sorted( + rewards[account][reward_token].keys() + ) + values: List[str] = [] + for origin in origins: + value: str = rewards[account][reward_token][origin] + values.append(value) + prev_value = reward_token_amounts.setdefault(reward_token, 0) + reward_token_amounts[reward_token] = prev_value + int(value) + + claim.setdefault("origins", []).append(origins) + claim.setdefault("values", []).append(values) + claims[account] = claim - merkle_element = get_merkle_node( + merkle_element: bytes = get_merkle_node( index=i, account=account, - tokens=tokens, - values=values, + tokens=reward_tokens, + values=[reward_token_amounts[token] for token in reward_tokens], ) merkle_elements.append(merkle_element) diff --git a/src/distributor/rewards.py b/oracle/distributor/rewards.py similarity index 89% rename from src/distributor/rewards.py rename to oracle/distributor/rewards.py index 60729f2..1e24988 100644 --- a/src/distributor/rewards.py +++ b/oracle/distributor/rewards.py @@ -4,7 +4,7 @@ from eth_typing import BlockNumber, ChecksumAddress -from src.settings import ( +from oracle.settings import ( DISTRIBUTOR_FALLBACK_ADDRESS, REWARD_ETH_TOKEN_CONTRACT_ADDRESS, STAKED_ETH_TOKEN_CONTRACT_ADDRESS, @@ -51,25 +51,32 @@ def is_supported_contract(self, contract_address: ChecksumAddress) -> bool: def add_value( rewards: Rewards, to: ChecksumAddress, + origin: ChecksumAddress, reward_token: ChecksumAddress, amount: int, ) -> None: """Adds reward token to the beneficiary address.""" - prev_amount = rewards.setdefault(to, {}).setdefault(reward_token, "0") - rewards[to][reward_token] = str(int(prev_amount) + amount) + prev_amount = ( + rewards.setdefault(to, {}) + .setdefault(reward_token, {}) + .setdefault(origin, "0") + ) + rewards[to][reward_token][origin] = str(int(prev_amount) + amount) @staticmethod def merge_rewards(rewards1: Rewards, rewards2: Rewards) -> Rewards: """Merges two dictionaries into one.""" merged_rewards: Rewards = copy.deepcopy(rewards1) for account, account_rewards in rewards2.items(): - for token, amount in account_rewards.items(): - DistributorRewards.add_value( - rewards=merged_rewards, - to=account, - reward_token=token, - amount=int(amount), - ) + for reward_token, rewards in account_rewards.items(): + for origin, value in rewards.items(): + DistributorRewards.add_value( + rewards=merged_rewards, + to=account, + origin=origin, + reward_token=reward_token, + amount=int(value), + ) return merged_rewards @@ -89,6 +96,7 @@ async def get_rewards( self.add_value( rewards=rewards, to=DISTRIBUTOR_FALLBACK_ADDRESS, + origin=contract_address, reward_token=self.reward_token, amount=reward, ) @@ -159,6 +167,7 @@ async def _get_rewards( self.add_value( rewards=rewards, to=DISTRIBUTOR_FALLBACK_ADDRESS, + origin=contract_address, reward_token=self.reward_token, amount=total_reward, ) @@ -185,6 +194,7 @@ async def _get_rewards( self.add_value( rewards=rewards, to=DISTRIBUTOR_FALLBACK_ADDRESS, + origin=contract_address, reward_token=self.reward_token, amount=account_reward, ) @@ -200,6 +210,7 @@ async def _get_rewards( self.add_value( rewards=rewards, to=account, + origin=contract_address, reward_token=self.reward_token, amount=account_reward, ) diff --git a/src/distributor/types.py b/oracle/distributor/types.py similarity index 86% rename from src/distributor/types.py rename to oracle/distributor/types.py index 7fa1d2d..1cc113a 100644 --- a/src/distributor/types.py +++ b/oracle/distributor/types.py @@ -39,8 +39,9 @@ class Balances(TypedDict): class Claim(TypedDict): index: int - tokens: List[ChecksumAddress] - values: List[str] + reward_tokens: List[ChecksumAddress] + origins: List[List[ChecksumAddress]] + values: List[List[str]] proof: List[HexStr] @@ -60,5 +61,6 @@ class DistributorVote(TypedDict): TokenAllocations = Dict[ChecksumAddress, List[TokenAllocation]] Distributions = List[Distribution] ClaimedAccounts = Set[ChecksumAddress] -Rewards = Dict[ChecksumAddress, Dict[ChecksumAddress, str]] +# account -> reward token -> origin -> amount +Rewards = Dict[ChecksumAddress, Dict[ChecksumAddress, Dict[ChecksumAddress, str]]] Claims = Dict[ChecksumAddress, Claim] diff --git a/src/distributor/uniswap_v3.py b/oracle/distributor/uniswap_v3.py similarity index 88% rename from src/distributor/uniswap_v3.py rename to oracle/distributor/uniswap_v3.py index 9910e87..b59c36b 100644 --- a/src/distributor/uniswap_v3.py +++ b/oracle/distributor/uniswap_v3.py @@ -7,14 +7,14 @@ from web3 import Web3 from web3.types import Wei -from src.clients import execute_graphql_query, uniswap_v3_gql_client -from src.graphql_queries import ( +from oracle.clients import execute_uniswap_v3_gql_query +from oracle.graphql_queries import ( UNISWAP_V3_CURRENT_TICK_POSITIONS_QUERY, UNISWAP_V3_POOL_QUERY, UNISWAP_V3_POOLS_QUERY, UNISWAP_V3_POSITIONS_QUERY, ) -from src.settings import ( +from oracle.settings import ( REWARD_ETH_TOKEN_CONTRACT_ADDRESS, STAKED_ETH_TOKEN_CONTRACT_ADDRESS, SWISE_TOKEN_CONTRACT_ADDRESS, @@ -29,7 +29,7 @@ ) # NB! Changing BLOCKS_INTERVAL while distributions are still active can lead to invalid allocations -BLOCKS_INTERVAL: int = 300 +BLOCKS_INTERVAL: BlockNumber = BlockNumber(300) MIN_TICK: int = -887272 MAX_TICK: int = -MIN_TICK MAX_UINT_256 = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF @@ -41,8 +41,7 @@ async def get_uniswap_v3_pools(block_number: BlockNumber) -> UniswapV3Pools: """Fetches Uniswap V3 pools.""" last_id = "" - result: Dict = await execute_graphql_query( - client=uniswap_v3_gql_client, + result: Dict = await execute_uniswap_v3_gql_query( query=UNISWAP_V3_POOLS_QUERY, variables=dict(block_number=block_number, last_id=last_id), ) @@ -55,8 +54,7 @@ async def get_uniswap_v3_pools(block_number: BlockNumber) -> UniswapV3Pools: if not last_id: break - result: Dict = await execute_graphql_query( - client=uniswap_v3_gql_client, + result: Dict = await execute_uniswap_v3_gql_query( query=UNISWAP_V3_POOLS_QUERY, variables=dict(block_number=block_number, last_id=last_id), ) @@ -80,7 +78,7 @@ async def get_uniswap_v3_pools(block_number: BlockNumber) -> UniswapV3Pools: elif pool_token == SWISE_TOKEN_CONTRACT_ADDRESS: uni_v3_pools["swise_pools"].add(pool_address) - return UniswapV3Pools(**uni_v3_pools) + return uni_v3_pools @backoff.on_exception(backoff.expo, Exception, max_time=900) @@ -125,7 +123,7 @@ async def get_uniswap_v3_distributions( reward: Wei = Wei(reward_per_block * interval) if end == alloc_to_block: # collect left overs - reward += total_reward - (reward_per_block * total_blocks) + reward += Wei(total_reward - (reward_per_block * total_blocks)) if reward > 0: distribution = Distribution( @@ -158,8 +156,7 @@ async def get_uniswap_v3_liquidity_points( ) -> Balances: """Fetches users' liquidity points of the Uniswap V3 pool in the current tick.""" lowered_pool_address = pool_address.lower() - result: Dict = await execute_graphql_query( - client=uniswap_v3_gql_client, + result: Dict = await execute_uniswap_v3_gql_query( query=UNISWAP_V3_POOL_QUERY, variables=dict(block_number=block_number, pool_address=lowered_pool_address), ) @@ -174,8 +171,7 @@ async def get_uniswap_v3_liquidity_points( return Balances(total_supply=0, balances={}) last_id = "" - result: Dict = await execute_graphql_query( - client=uniswap_v3_gql_client, + result: Dict = await execute_uniswap_v3_gql_query( query=UNISWAP_V3_CURRENT_TICK_POSITIONS_QUERY, variables=dict( block_number=block_number, @@ -193,8 +189,7 @@ async def get_uniswap_v3_liquidity_points( if not last_id: break - result: Dict = await execute_graphql_query( - client=uniswap_v3_gql_client, + result: Dict = await execute_uniswap_v3_gql_query( query=UNISWAP_V3_CURRENT_TICK_POSITIONS_QUERY, variables=dict( block_number=block_number, @@ -231,8 +226,7 @@ async def get_uniswap_v3_single_token_balances( ) -> Balances: """Fetches users' single token balances of the Uniswap V3 pair across all the ticks.""" lowered_pool_address = pool_address.lower() - result: Dict = await execute_graphql_query( - client=uniswap_v3_gql_client, + result: Dict = await execute_uniswap_v3_gql_query( query=UNISWAP_V3_POOL_QUERY, variables=dict(block_number=block_number, pool_address=lowered_pool_address), ) @@ -255,8 +249,7 @@ async def get_uniswap_v3_single_token_balances( token1_address: ChecksumAddress = Web3.toChecksumAddress(pool["token1"]) last_id = "" - result: Dict = await execute_graphql_query( - client=uniswap_v3_gql_client, + result: Dict = await execute_uniswap_v3_gql_query( query=UNISWAP_V3_POSITIONS_QUERY, variables=dict( block_number=block_number, @@ -275,8 +268,7 @@ async def get_uniswap_v3_single_token_balances( if not last_id: break - result: Dict = await execute_graphql_query( - client=uniswap_v3_gql_client, + result: Dict = await execute_uniswap_v3_gql_query( query=UNISWAP_V3_POSITIONS_QUERY, variables=dict( block_number=block_number, @@ -288,8 +280,8 @@ async def get_uniswap_v3_single_token_balances( positions.extend(positions_chunk) # process positions - balances: Dict[ChecksumAddress, Wei] = {} - total_supply: Wei = Wei(0) + balances: Dict[ChecksumAddress, int] = {} + total_supply = 0 for position in positions: account = Web3.toChecksumAddress(position["owner"]) if account == EMPTY_ADDR_HEX: @@ -306,32 +298,25 @@ async def get_uniswap_v3_single_token_balances( continue if token0_address == token: - token0_amount: Wei = Wei( - get_amount0( - tick_current=tick_current, - sqrt_ratio_x96=sqrt_price, - tick_lower=tick_lower, - tick_upper=tick_upper, - liquidity=liquidity, - ) - ) - balances[account] = Wei( - balances.setdefault(account, Wei(0)) + token0_amount + token0_amount = get_amount0( + tick_current=tick_current, + sqrt_ratio_x96=sqrt_price, + tick_lower=tick_lower, + tick_upper=tick_upper, + liquidity=liquidity, ) + balances[account] = balances.setdefault(account, 0) + token0_amount total_supply += token0_amount elif token1_address == token: - token1_amount: Wei = Wei( - get_amount1( - tick_current=tick_current, - sqrt_ratio_x96=sqrt_price, - tick_lower=tick_lower, - tick_upper=tick_upper, - liquidity=liquidity, - ) - ) - balances[account] = Wei( - balances.setdefault(account, Wei(0)) + token1_amount + token1_amount = get_amount1( + tick_current=tick_current, + sqrt_ratio_x96=sqrt_price, + tick_lower=tick_lower, + tick_upper=tick_upper, + liquidity=liquidity, ) + + balances[account] = balances.setdefault(account, 0) + token1_amount total_supply += token1_amount return Balances(total_supply=total_supply, balances=balances) diff --git a/src/eth1.py b/oracle/eth1.py similarity index 94% rename from src/eth1.py rename to oracle/eth1.py index 594366d..9fa15e1 100644 --- a/src/eth1.py +++ b/oracle/eth1.py @@ -7,7 +7,7 @@ from web3 import Web3 from web3.types import BlockNumber, Timestamp -from .clients import execute_graphql_query, sw_gql_client +from .clients import execute_sw_gql_query from .distributor.types import DistributorVotingParameters from .graphql_queries import ( FINALIZED_BLOCK_QUERY, @@ -41,8 +41,7 @@ class VotingParameters(TypedDict): @backoff.on_exception(backoff.expo, Exception, max_time=900) async def get_finalized_block() -> FinalizedBlock: """Gets the finalized block number and its timestamp.""" - result: Dict = await execute_graphql_query( - client=sw_gql_client, + result: Dict = await execute_sw_gql_query( query=FINALIZED_BLOCK_QUERY, variables=dict( confirmation_blocks=ETH1_CONFIRMATION_BLOCKS, @@ -57,8 +56,7 @@ async def get_finalized_block() -> FinalizedBlock: @backoff.on_exception(backoff.expo, Exception, max_time=900) async def get_voting_parameters(block_number: BlockNumber) -> VotingParameters: """Fetches rewards voting parameters.""" - result: Dict = await execute_graphql_query( - client=sw_gql_client, + result: Dict = await execute_sw_gql_query( query=VOTING_PARAMETERS_QUERY, variables=dict( block_number=block_number, @@ -119,8 +117,7 @@ async def check_oracle_account() -> None: """Checks whether oracle is part of the oracles set.""" oracle_lowered_address = oracle.address.lower() result: List = ( - await execute_graphql_query( - client=sw_gql_client, + await execute_sw_gql_query( query=ORACLE_QUERY, variables=dict( oracle_address=oracle_lowered_address, diff --git a/src/graphql_queries.py b/oracle/graphql_queries.py similarity index 97% rename from src/graphql_queries.py rename to oracle/graphql_queries.py index 1e07a80..6570058 100644 --- a/src/graphql_queries.py +++ b/oracle/graphql_queries.py @@ -72,7 +72,7 @@ ORACLE_QUERY = gql( """ query getOracles($oracle_address: ID) { - oracles(first: 1, where: {id: $oracle_address) { + oracles(first: 1, where: {id: $oracle_address}) { id } } @@ -264,7 +264,7 @@ VALIDATOR_REGISTRATIONS_QUERY = gql( """ - query getValidatorRegistrations($block_number: Int, public_key: Bytes) { + query getValidatorRegistrations($block_number: Int, $public_key: Bytes) { validatorRegistrations( block: { number: $block_number } where: { id: $public_key } diff --git a/src/ipfs.py b/oracle/ipfs.py similarity index 96% rename from src/ipfs.py rename to oracle/ipfs.py index e698f92..6d33c18 100644 --- a/src/ipfs.py +++ b/oracle/ipfs.py @@ -120,12 +120,7 @@ def check_or_create_ipns_keys() -> IPNSKeys: def submit_ipns_vote( encoded_data: bytes, vote: Union[RewardsVote, DistributorVote, ValidatorVote], - key_id: Union[ - IPNS_REWARDS_KEY_NAME, - IPNS_DISTRIBUTOR_KEY_NAME, - IPNS_VALIDATOR_INITIALIZE_KEY_NAME, - IPNS_VALIDATOR_FINALIZE_KEY_NAME, - ], + key_id: str, ) -> IPNSRecord: """Submits vote to the IPFS and publishes to the IPNS.""" # generate candidate ID diff --git a/src/rewards/__init__.py b/oracle/merkle_distributor/utils.py similarity index 100% rename from src/rewards/__init__.py rename to oracle/merkle_distributor/utils.py diff --git a/src/validators/__init__.py b/oracle/rewards/__init__.py similarity index 100% rename from src/validators/__init__.py rename to oracle/rewards/__init__.py diff --git a/src/rewards/controller.py b/oracle/rewards/controller.py similarity index 99% rename from src/rewards/controller.py rename to oracle/rewards/controller.py index 92905cb..2838dcd 100644 --- a/src/rewards/controller.py +++ b/oracle/rewards/controller.py @@ -8,7 +8,7 @@ from web3 import Web3 from web3.types import Timestamp, Wei -from src.ipfs import submit_ipns_vote +from oracle.ipfs import submit_ipns_vote from .eth1 import SYNC_PERIOD, get_finalized_validators_public_keys from .eth2 import ( diff --git a/src/rewards/eth1.py b/oracle/rewards/eth1.py similarity index 79% rename from src/rewards/eth1.py rename to oracle/rewards/eth1.py index b839f8b..068af47 100644 --- a/src/rewards/eth1.py +++ b/oracle/rewards/eth1.py @@ -4,8 +4,8 @@ import backoff from web3.types import BlockNumber -from src.clients import execute_graphql_query, sw_gql_client -from src.graphql_queries import FINALIZED_VALIDATORS_QUERY +from oracle.clients import execute_sw_gql_query +from oracle.graphql_queries import FINALIZED_VALIDATORS_QUERY from .types import FinalizedValidatorsPublicKeys @@ -18,8 +18,7 @@ async def get_finalized_validators_public_keys( ) -> FinalizedValidatorsPublicKeys: """Fetches pool validators public keys.""" last_id = "" - result: Dict = await execute_graphql_query( - client=sw_gql_client, + result: Dict = await execute_sw_gql_query( query=FINALIZED_VALIDATORS_QUERY, variables=dict(block_number=block_number, last_id=last_id), ) @@ -32,8 +31,7 @@ async def get_finalized_validators_public_keys( if not last_id: break - result: Dict = await execute_graphql_query( - client=sw_gql_client, + result: Dict = await execute_sw_gql_query( query=FINALIZED_VALIDATORS_QUERY, variables=dict(block_number=block_number, last_id=last_id), ) diff --git a/src/rewards/eth2.py b/oracle/rewards/eth2.py similarity index 98% rename from src/rewards/eth2.py rename to oracle/rewards/eth2.py index 7b27ff5..afbc3a1 100644 --- a/src/rewards/eth2.py +++ b/oracle/rewards/eth2.py @@ -5,7 +5,7 @@ from aiohttp import ClientResponseError, ClientSession from eth_typing import HexStr -from src.settings import ETH2_ENDPOINT +from oracle.settings import ETH2_ENDPOINT class ValidatorStatus(Enum): diff --git a/src/rewards/types.py b/oracle/rewards/types.py similarity index 100% rename from src/rewards/types.py rename to oracle/rewards/types.py diff --git a/src/settings.py b/oracle/settings.py similarity index 100% rename from src/settings.py rename to oracle/settings.py diff --git a/src/staking_rewards/rewards.py b/oracle/staking_rewards/rewards.py similarity index 100% rename from src/staking_rewards/rewards.py rename to oracle/staking_rewards/rewards.py diff --git a/oracle/validators/__init__.py b/oracle/validators/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/validators/controller.py b/oracle/validators/controller.py similarity index 99% rename from src/validators/controller.py rename to oracle/validators/controller.py index fb7c790..688b7ed 100644 --- a/src/validators/controller.py +++ b/oracle/validators/controller.py @@ -4,7 +4,7 @@ from web3 import Web3 from web3.types import Wei -from src.ipfs import submit_ipns_vote +from oracle.ipfs import submit_ipns_vote from .eth1 import can_finalize_validator, select_validator from .ipfs import get_last_vote_public_key diff --git a/src/validators/eth1.py b/oracle/validators/eth1.py similarity index 82% rename from src/validators/eth1.py rename to oracle/validators/eth1.py index 7c5d680..e70001e 100644 --- a/src/validators/eth1.py +++ b/oracle/validators/eth1.py @@ -5,9 +5,9 @@ from web3 import Web3 from web3.types import BlockNumber -from src.clients import execute_graphql_query, sw_gql_client -from src.graphql_queries import OPERATORS_QUERY, VALIDATOR_REGISTRATIONS_QUERY -from src.settings import WITHDRAWAL_CREDENTIALS +from oracle.clients import execute_sw_gql_query +from oracle.graphql_queries import OPERATORS_QUERY, VALIDATOR_REGISTRATIONS_QUERY +from oracle.settings import WITHDRAWAL_CREDENTIALS from .ipfs import get_validator_deposit_data_public_key from .types import Validator @@ -16,8 +16,7 @@ @backoff.on_exception(backoff.expo, Exception, max_time=900) async def select_validator(block_number: BlockNumber) -> Union[None, Validator]: """Selects operator to initiate validator registration for.""" - result: Dict = await execute_graphql_query( - client=sw_gql_client, + result: Dict = await execute_sw_gql_query( query=OPERATORS_QUERY, variables=dict( block_number=block_number, @@ -43,8 +42,7 @@ async def select_validator(block_number: BlockNumber) -> Union[None, Validator]: @backoff.on_exception(backoff.expo, Exception, max_time=900) async def can_finalize_validator(block_number: BlockNumber, public_key: HexStr) -> bool: """Checks whether it's safe to finalize the validator registration.""" - result: Dict = await execute_graphql_query( - client=sw_gql_client, + result: Dict = await execute_sw_gql_query( query=VALIDATOR_REGISTRATIONS_QUERY, variables=dict(block_number=block_number, public_key=public_key), ) diff --git a/src/validators/ipfs.py b/oracle/validators/ipfs.py similarity index 96% rename from src/validators/ipfs.py rename to oracle/validators/ipfs.py index 9332520..1624232 100644 --- a/src/validators/ipfs.py +++ b/oracle/validators/ipfs.py @@ -6,7 +6,7 @@ from eth_typing import HexStr from ipfshttpclient.exceptions import ErrorResponse -from src.settings import IPFS_ENDPOINT +from oracle.settings import IPFS_ENDPOINT from .types import ValidatorVote diff --git a/src/validators/types.py b/oracle/validators/types.py similarity index 100% rename from src/validators/types.py rename to oracle/validators/types.py diff --git a/pyproject.toml b/pyproject.toml index f6b98b7..d824463 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,6 +3,7 @@ name = "oracle" version = "2.0.0" description = "StakeWise Oracles are responsible for submitting off-chain data." authors = ["Dmitri Tsumak "] +license = "AGPL-3.0-only" readme = "README.md" [tool.poetry.dependencies] diff --git a/src/clients.py b/src/clients.py deleted file mode 100644 index ac6e68d..0000000 --- a/src/clients.py +++ /dev/null @@ -1,29 +0,0 @@ -import logging -from typing import Dict - -import backoff -from gql import Client -from gql.transport.aiohttp import AIOHTTPTransport -from graphql import DocumentNode - -from src.settings import STAKEWISE_SUBGRAPH_URL, UNISWAP_V3_SUBGRAPH_URL - -logger = logging.getLogger(__name__) - -sw_gql_client = Client( - transport=AIOHTTPTransport(url=STAKEWISE_SUBGRAPH_URL), - fetch_schema_from_transport=True, -) - -uniswap_v3_gql_client = Client( - transport=AIOHTTPTransport(url=UNISWAP_V3_SUBGRAPH_URL), - fetch_schema_from_transport=True, -) - - -@backoff.on_exception(backoff.expo, Exception, max_time=300) -async def execute_graphql_query( - client: Client, query: DocumentNode, variables: Dict -) -> Dict: - """Executes GraphQL query.""" - return await client.execute_async(query, variable_values=variables)