-
Notifications
You must be signed in to change notification settings - Fork 42
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add cached balances examples & minor tweaks
- Loading branch information
1 parent
636565a
commit bcfceb3
Showing
11 changed files
with
306 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import json | ||
from web3 import Web3 | ||
from utils.web3_utils import w3 | ||
|
||
PAGINATION_SIZE = 2000 | ||
ACTIVE_ENA_START_BLOCK_EXAMPLE = 21202656 | ||
ENA_ADDRESS = Web3.to_checksum_address("0x57e114B691Db790C35207b2e685D4A43181e6061") | ||
with open("abi/ERC20_abi.json") as f: | ||
ERC20_ABI = json.load(f) | ||
|
||
ENA_CONTRACT = w3.eth.contract( | ||
address=ENA_ADDRESS, | ||
abi=ERC20_ABI, | ||
) | ||
|
||
BEEFY_ARBITRUM_START_BLOCK_EXAMPLE = 219870802 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
112 changes: 112 additions & 0 deletions
112
integrations/beefy_cached_balance_example_integration.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
import logging | ||
|
||
from constants.example_integrations import ( | ||
BEEFY_ARBITRUM_START_BLOCK_EXAMPLE, | ||
) | ||
from integrations.cached_balances_integration import CachedBalancesIntegration | ||
from web3 import Web3 | ||
from constants.chains import Chain | ||
from typing import Dict, List, Set, cast | ||
from eth_typing import ChecksumAddress | ||
|
||
from constants.beefy import BEEFY_LRT_API_URL | ||
from constants.summary_columns import SummaryColumn | ||
from integrations.integration_ids import IntegrationID | ||
from utils.request_utils import requests_retry_session | ||
from utils.slack import slack_message | ||
|
||
CHAIN_TO_API_URL_PREFIX = { | ||
Chain.ARBITRUM: f"{BEEFY_LRT_API_URL}/partner/ethena/arbitrum", | ||
Chain.FRAXTAL: f"{BEEFY_LRT_API_URL}/partner/ethena/fraxtal", | ||
Chain.MANTLE: f"{BEEFY_LRT_API_URL}/partner/ethena/mantle", | ||
Chain.OPTIMISM: f"{BEEFY_LRT_API_URL}/partner/ethena/optimism", | ||
} | ||
|
||
|
||
class BeefyCachedBalanceIntegration(CachedBalancesIntegration): | ||
def __init__( | ||
self, | ||
integration_id: IntegrationID, | ||
start_block: int, | ||
chain: Chain, | ||
summary_cols: List[SummaryColumn], | ||
reward_multiplier: int = 1, | ||
): | ||
super().__init__( | ||
integration_id=integration_id, | ||
start_block=start_block, | ||
chain=chain, | ||
summary_cols=summary_cols, | ||
reward_multiplier=reward_multiplier, | ||
balance_multiplier=1, | ||
excluded_addresses=None, | ||
end_block=None, | ||
) | ||
|
||
def get_beefy_users(self) -> Set[ChecksumAddress]: | ||
""" | ||
Get all participants of the protocol, ever. | ||
""" | ||
logging.info("[Beefy integration] Getting participants...") | ||
try: | ||
base_url = CHAIN_TO_API_URL_PREFIX[self.chain] | ||
url = f"{base_url}/users" | ||
|
||
response = requests_retry_session().get(url) | ||
data = cast(List[str], response.json()) | ||
return set(Web3.to_checksum_address(user) for user in data) | ||
except Exception as e: | ||
msg = f"Error getting participants for beefy: {e}" | ||
logging.error(msg) | ||
slack_message(msg) | ||
return set() | ||
|
||
def get_data_for_block( | ||
self, block: int, users: Set[ChecksumAddress] | ||
) -> Dict[ChecksumAddress, float]: | ||
logging.info(f"Getting data for beefy at block {block}...") | ||
|
||
if block < self.start_block: | ||
return {} | ||
data: Dict[ChecksumAddress, float] = {} | ||
# Just get the first 10 users as a quick example | ||
for user in list(users)[:10]: | ||
try: | ||
base_url = CHAIN_TO_API_URL_PREFIX[self.chain] | ||
url = f"{base_url}/user/{user}/balance/{block}" | ||
response = requests_retry_session(retries=1, backoff_factor=0).get(url) | ||
user_data = response.json() | ||
|
||
if user_data is None or "effective_balance" not in user_data: | ||
data[user] = 0.0 | ||
data[user] = round(float(user_data["effective_balance"]), 4) | ||
except Exception as e: | ||
msg = f"Error getting beefy data for {user} at block {block}: {e}" | ||
logging.error(msg) | ||
slack_message(msg) | ||
data[user] = 0.0 | ||
return data | ||
|
||
def get_block_balances( | ||
self, cached_data: Dict[int, Dict[ChecksumAddress, float]], blocks: List[int] | ||
) -> Dict[int, Dict[ChecksumAddress, float]]: | ||
logging.info("Getting block data for beefy...") | ||
block_data: Dict[int, Dict[ChecksumAddress, float]] = {} | ||
beefy_users = self.get_beefy_users() | ||
for block in blocks: | ||
if block < self.start_block: | ||
block_data[block] = {} | ||
continue | ||
block_data[block] = self.get_data_for_block(block, beefy_users) | ||
return block_data | ||
|
||
|
||
if __name__ == "__main__": | ||
example_integration = BeefyCachedBalanceIntegration( | ||
integration_id=IntegrationID.BEEFY_CACHED_BALANCE_EXAMPLE, | ||
start_block=BEEFY_ARBITRUM_START_BLOCK_EXAMPLE, | ||
chain=Chain.ARBITRUM, | ||
summary_cols=[SummaryColumn.BEEFY_CACHED_BALANCE_EXAMPLE], | ||
) | ||
# Since this integration is based on API calls, we don't need to use the cached data | ||
print(example_integration.get_block_balances(cached_data={}, blocks=[276231389])) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
from copy import deepcopy | ||
import logging | ||
|
||
from typing import Dict, List, Optional | ||
|
||
from constants.summary_columns import SummaryColumn | ||
from eth_typing import ChecksumAddress | ||
|
||
from constants.example_integrations import ( | ||
ACTIVE_ENA_START_BLOCK_EXAMPLE, | ||
ENA_CONTRACT, | ||
PAGINATION_SIZE, | ||
) | ||
|
||
from constants.chains import Chain | ||
from integrations.integration_ids import IntegrationID | ||
from integrations.cached_balances_integration import CachedBalancesIntegration | ||
from utils.web3_utils import fetch_events_logs_with_retry | ||
|
||
|
||
class ClaimedEnaIntegration(CachedBalancesIntegration): | ||
def __init__( | ||
self, | ||
integration_id: IntegrationID, | ||
start_block: int, | ||
chain: Chain = Chain.ETHEREUM, | ||
summary_cols: Optional[List[SummaryColumn]] = None, | ||
reward_multiplier: int = 1, | ||
): | ||
super().__init__( | ||
integration_id, | ||
start_block, | ||
chain, | ||
summary_cols, | ||
reward_multiplier, | ||
) | ||
|
||
def get_block_balances( | ||
self, cached_data: Dict[int, Dict[ChecksumAddress, float]], blocks: List[int] | ||
) -> Dict[int, Dict[ChecksumAddress, float]]: | ||
logging.info("Getting block data for claimed ENA") | ||
new_block_data: Dict[int, Dict[ChecksumAddress, float]] = {} | ||
if not blocks: | ||
logging.error("No blocks provided to claimed ENA get_block_balances") | ||
return new_block_data | ||
sorted_blocks = sorted(blocks) | ||
cache_copy: Dict[int, Dict[ChecksumAddress, float]] = deepcopy(cached_data) | ||
for block in sorted_blocks: | ||
# find the closest prev block in the data | ||
# list keys parsed as ints and in descending order | ||
sorted_existing_blocks = sorted( | ||
cache_copy, | ||
reverse=True, | ||
) | ||
# loop through the sorted blocks and find the closest previous block | ||
prev_block = self.start_block | ||
start = prev_block | ||
bals = {} | ||
for existing_block in sorted_existing_blocks: | ||
if existing_block < block: | ||
prev_block = existing_block | ||
start = existing_block + 1 | ||
bals = deepcopy(cache_copy[prev_block]) | ||
break | ||
# parse transfer events since and update bals | ||
while start <= block: | ||
to_block = min(start + PAGINATION_SIZE, block) | ||
# print(f"Fetching transfers from {start} to {to_block}") | ||
transfers = fetch_events_logs_with_retry( | ||
"Token transfers claimed ENA", | ||
ENA_CONTRACT.events.Transfer(), | ||
start, | ||
to_block, | ||
) | ||
for transfer in transfers: | ||
recipient = transfer["args"]["to"] | ||
if recipient not in bals: | ||
bals[recipient] = 0 | ||
bals[recipient] += round(transfer["args"]["value"] / 10**18, 4) | ||
start = to_block + 1 | ||
new_block_data[block] = bals | ||
cache_copy[block] = bals | ||
return new_block_data | ||
|
||
|
||
if __name__ == "__main__": | ||
example_integration = ClaimedEnaIntegration( | ||
integration_id=IntegrationID.CLAIMED_ENA_EXAMPLE, | ||
start_block=ACTIVE_ENA_START_BLOCK_EXAMPLE, | ||
summary_cols=[SummaryColumn.CLAIMED_ENA_PTS_EXAMPLE], | ||
reward_multiplier=20, | ||
) | ||
|
||
# Without cached data | ||
without_cached_data_output = example_integration.get_block_balances( | ||
cached_data={}, blocks=[21209856, 21217056] | ||
) | ||
|
||
print("=" * 120) | ||
print("Run without cached data", without_cached_data_output) | ||
print("=" * 120, "\n" * 5) | ||
|
||
# With cached data, using the previous output so there is no need | ||
# to fetch the previous blocks again | ||
with_cached_data_output = example_integration.get_block_balances( | ||
cached_data=without_cached_data_output, blocks=[21224256] | ||
) | ||
print("Run with cached data", with_cached_data_output) | ||
print("=" * 120) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
import requests | ||
from requests.adapters import HTTPAdapter | ||
from urllib3.util.retry import Retry | ||
|
||
|
||
def requests_retry_session( | ||
retries=5, | ||
backoff_factor=0.3, | ||
status_forcelist=(400, 404, 500, 502, 504), | ||
session=None, | ||
): | ||
session = session or requests.Session() | ||
retry = Retry( | ||
total=retries, | ||
read=retries, | ||
connect=retries, | ||
backoff_factor=backoff_factor, | ||
status_forcelist=status_forcelist, | ||
) | ||
adapter = HTTPAdapter(max_retries=retry) | ||
session.mount("http://", adapter) | ||
session.mount("https://", adapter) | ||
return session |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters