Skip to content

Commit

Permalink
Aave v2 event reader (#209)
Browse files Browse the repository at this point in the history
  • Loading branch information
hieuh25 authored Apr 19, 2024
1 parent 0e5d8bb commit ba46943
Show file tree
Hide file tree
Showing 133 changed files with 31,489 additions and 22 deletions.
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,6 @@
[submodule "contracts/velvet"]
path = contracts/velvet
url = https://github.com/Velvet-Capital/protocol-v2-public.git
[submodule "contracts/aave-v2"]
path = contracts/aave-v2
url = https://github.com/aave/protocol-v2.git
9 changes: 5 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
# 0.25.6

- Add Aave v2 event reader support


# 0.25.5

- Handle HTTP 410 retryable, as returned by dRPC
-
# 0.25.4

- TODO

# 0.25.4

Expand Down
5 changes: 5 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,11 @@ aavev3:
@mkdir -p eth_defi/abi/aave_v3
@find contracts/aave-v3-deploy/artifacts/@aave -iname "*.json" -not -iname "*.dbg.json" -exec cp {} eth_defi/abi/aave_v3 \;

aavev2:
@(cd contracts/aave-v2 && npm ci && npm run compile) > /dev/null
@mkdir -p eth_defi/abi/aave_v2
@find contracts/aave-v2/artifacts/contracts -iname "*.json" -not -iname "*.dbg.json" -exec cp {} eth_defi/abi/aave_v2 \;

# Compile and copy Enzyme contract ABIs from their Github repository
# Needs pnpm: curl -fsSL https://get.pnpm.io/install.sh | sh -
#
Expand Down
1 change: 1 addition & 0 deletions contracts/aave-v2
Submodule aave-v2 added at ce53c4
15 changes: 15 additions & 0 deletions docs/source/api/aave_v2/index.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
Aave v2 API
-----------

This is Python documentation for high-level `Aave lending protocol <https://tradingstrategy.ai/glossary/aave>`_ APIs.

Functionality includes:

- Reading current and historical Aave data and metrics

.. autosummary::
:toctree: _autosummary_aave_v2
:recursive:

eth_defi.aave_v2.constants
eth_defi.aave_v2.events
1 change: 1 addition & 0 deletions docs/source/api/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ API documentation
usdc/index
uniswap_v2/index
uniswap_v3/index
aave_v2/index
aave_v3/index
one_delta/index
enzyme/index
Expand Down
58 changes: 58 additions & 0 deletions eth_defi/aave_v2/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
"""Aave v2 constants."""

from typing import NamedTuple

from eth_defi.aave_v3.constants import ( # noqa: passthrough imports, don't remove
MAX_AMOUNT,
AaveVersion,
)


class AaveV2Network(NamedTuple):
# Network name
name: str

# Aave v2 lending pool address
pool_address: str

# Block number when the pool was created
pool_created_at_block: int


# https://docs.aave.com/developers/v/2.0/deployed-contracts/deployed-contracts
AAVE_V2_NETWORK_CHAINS: dict[int, str] = {
1: "ethereum",
137: "polygon",
43114: "avalanche",
}

AAVE_V2_NETWORKS: dict[str, AaveV2Network] = {
# Ethereum Mainnet
"ethereum": AaveV2Network(
name="Ethereum",
pool_address="0x7d2768de32b0b80b7a3454c06bdac94a69ddc7a9",
# https://etherscan.io/tx/0x7d77cc7523a491fa670bfefa0a386ab036b6511d6d9fa6c2cf5c07b349dc9d3a
pool_created_at_block=11362579,
),
# Polygon Mainnet
"polygon": AaveV2Network(
name="Polygon",
pool_address="0x8dFf5E27EA6b7AC08EbFdf9eB090F32ee9a30fcf",
# https://polygonscan.com/tx/0xb5a63fed49e97a58135b012fa14d83e680a0f3cd3aefeb551228d6e3640dbec9
pool_created_at_block=12687245,
),
# Avalanche C-Chain
"avalanche": AaveV2Network(
name="Avalanche",
pool_address="0x4F01AeD16D97E3aB5ab2B501154DC9bb0F1A5A2C",
# https://snowtrace.io/tx/0x5db8b8c3026d4a433ca67cbc120540ab6f8897b3aff37e78ba014ac505d167bc?chainId=43114
pool_created_at_block=4607005,
),
}


def get_aave_v2_network_by_chain_id(chain_id: int) -> AaveV2Network:
if chain_id not in AAVE_V2_NETWORK_CHAINS:
raise ValueError(f"Unsupported chain id: {chain_id}")
network_slug = AAVE_V2_NETWORK_CHAINS[chain_id]
return AAVE_V2_NETWORKS[network_slug]
73 changes: 73 additions & 0 deletions eth_defi/aave_v2/events.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
"""Aave v2 event reader.
Efficiently read Aave v2 from a blockchain.
Currently we are tracking these events:
- ReserveDataUpdated
"""

import logging
from typing import Callable

from eth_defi.aave_v3.constants import AaveVersion
from eth_defi.aave_v3.events import _fetch_aave_events_to_csv
from eth_defi.event_reader.reorganisation_monitor import ReorganisationMonitor
from eth_defi.event_reader.state import ScanState

logger = logging.getLogger(__name__)


def aave_v2_fetch_events_to_csv(
json_rpc_url: str,
state: ScanState,
aave_network_name: str,
start_block: int,
end_block: int,
output_folder: str = "/tmp",
max_workers: int = 16,
log_info: Callable = print,
reorg_monitor: ReorganisationMonitor | None = None,
):
"""Fetch all tracked Aave v2 events to CSV files for notebook analysis.
Creates a CSV file with the event data:
- `/tmp/aave-v2-{aave_network_name.lower()}-reservedataupdated.csv`
A progress bar and estimation on the completion is rendered for console / Jupyter notebook using `tqdm`.
The scan be resumed using `state` storage to retrieve the last scanned block number from the previous round.
However, the mechanism here is no perfect and only good for notebook use - for advanced
persistent usage like database backed scans, please write your own scan loop using proper transaction management.
.. note ::
Any Ethereum address is lowercased in the resulting dataset and is not checksummed.
:param json_rpc_url: JSON-RPC URL
:param start_block: First block to process (inclusive), default is block xxx (when Aave v2 xxx was created on mainnet)
:param end_block: Last block to process (inclusive), default is block xxx (1000 block after default start block)
:param aave_network_name: Network name, e.g. 'Polygon'
:param state: Store the current scan state, so we can resume
:param output_folder: Folder to contain output CSV files, default is /tmp folder
:param max_workers:
How many threads to allocate for JSON-RPC IO.
You can increase your EVM node output a bit by making a lot of parallel requests,
until you exhaust your nodes IO capacity. Experiement with different values
and see how your node performs.
:param log_info: Which function to use to output info messages about the progress
"""

return _fetch_aave_events_to_csv(
json_rpc_url=json_rpc_url,
state=state,
aave_network_name=aave_network_name,
start_block=start_block,
end_block=end_block,
output_folder=output_folder,
max_workers=max_workers,
log_info=log_info,
reorg_monitor=reorg_monitor,
aave_version=AaveVersion.V2,
)
6 changes: 6 additions & 0 deletions eth_defi/aave_v3/constants.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Aave v3 constants."""

import enum
import json
import os
Expand Down Expand Up @@ -218,6 +219,11 @@ class AaveV3InterestRateMode(enum.IntEnum):
VARIABLE = 2


class AaveVersion(enum.Enum):
V2 = "v2"
V3 = "v3"


# Max amount user can withdraw or repay
# This is type(uint256).max in solidity
# https://github.com/aave/aave-v3-core/blob/e0bfed13240adeb7f05cb6cbe5e7ce78657f0621/contracts/protocol/libraries/logic/SupplyLogic.sol#L123
Expand Down
72 changes: 62 additions & 10 deletions eth_defi/aave_v3/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,35 +6,38 @@
- ReserveDataUpdated
"""

import csv
import datetime
import logging
from pathlib import Path
from typing import Callable

from requests.adapters import HTTPAdapter
from tqdm.auto import tqdm
from web3 import Web3

from eth_defi.aave_v2.constants import AAVE_V2_NETWORKS
from eth_defi.aave_v3.constants import (
AAVE_V3_NETWORKS,
AaveVersion,
aave_v3_get_token_name_by_deposit_address,
)
from eth_defi.abi import get_contract
from eth_defi.event_reader.conversion import (
convert_int256_bytes_to_int,
convert_jsonrpc_value_to_int,
convert_uint256_string_to_address,
decode_data,
convert_jsonrpc_value_to_int,
)
from eth_defi.event_reader.logresult import LogContext
from eth_defi.event_reader.reader import LogResult, read_events_concurrent
from eth_defi.event_reader.reorganisation_monitor import ReorganisationMonitor
from eth_defi.event_reader.state import ScanState
from eth_defi.event_reader.web3factory import TunedWeb3Factory
from eth_defi.event_reader.web3worker import create_thread_pool_executor
from eth_defi.token import TokenDetails, fetch_erc20_details

# from eth_defi.token import TokenDetails, fetch_erc20_details

logger = logging.getLogger(__name__)


Expand Down Expand Up @@ -96,8 +99,19 @@ def _decode_base(log: LogResult) -> dict:
}


def decode_reserve_data_updated(aave_network_name: str, log: LogResult) -> dict:
"""Process a ReserveDataUpdated event. The event signature is:
def decode_reserve_data_updated(
aave_network_name: str,
log: LogResult,
aave_version: AaveVersion,
) -> dict:
"""Process a ReserveDataUpdated event.
.. note ::
Both Aave v2 and v3 have this same event, so we use the lending pool smart
contract to filter out the correct events.
The event signature is:
.. code-block::
Expand All @@ -112,7 +126,11 @@ def decode_reserve_data_updated(aave_network_name: str, log: LogResult) -> dict:
);
"""
# Ensure the event comes from the correct smart contract
if log["address"].lower() != AAVE_V3_NETWORKS[aave_network_name.lower()].pool_address.lower():
if aave_version == AaveVersion.V2:
pool_address = AAVE_V2_NETWORKS[aave_network_name.lower()].pool_address.lower()
else:
pool_address = AAVE_V3_NETWORKS[aave_network_name.lower()].pool_address.lower()
if log["address"].lower() != pool_address:
return None

# Do additional lookup for the token data
Expand Down Expand Up @@ -151,7 +169,10 @@ def decode_reserve_data_updated(aave_network_name: str, log: LogResult) -> dict:
)

# Detect token name from reserve address (None if not found)
result["token"] = aave_v3_get_token_name_by_deposit_address(deposit_address)
if aave_version == AaveVersion.V3:
result["token"] = aave_v3_get_token_name_by_deposit_address(deposit_address)
else:
result["token"] = None

logger.debug(f'EVENT: block={log["blockNumber"]} tx={log["transactionHash"]} token={result["token"]} reserve={deposit_address} liquidity_rate={liquidity_rate} stable_borrow_rate={stable_borrow_rate} variable_borrow_rate={variable_borrow_rate} liquidity_index={liquidity_index} variable_borrow_index={variable_borrow_rate}')

Expand All @@ -166,7 +187,8 @@ def aave_v3_fetch_events_to_csv(
end_block: int,
output_folder: str = "/tmp",
max_workers: int = 16,
log_info=print,
log_info: Callable = print,
reorg_monitor: ReorganisationMonitor | None = None,
):
"""Fetch all tracked Aave v3 events to CSV files for notebook analysis.
Expand Down Expand Up @@ -197,6 +219,34 @@ def aave_v3_fetch_events_to_csv(
and see how your node performs.
:param log_info: Which function to use to output info messages about the progress
"""

return _fetch_aave_events_to_csv(
json_rpc_url=json_rpc_url,
state=state,
aave_network_name=aave_network_name,
start_block=start_block,
end_block=end_block,
output_folder=output_folder,
max_workers=max_workers,
log_info=log_info,
reorg_monitor=reorg_monitor,
aave_version=AaveVersion.V3,
)


def _fetch_aave_events_to_csv(
json_rpc_url: str,
state: ScanState,
aave_network_name: str,
start_block: int,
end_block: int,
output_folder: str = "/tmp",
max_workers: int = 16,
log_info=print,
reorg_monitor: ReorganisationMonitor | None = None,
aave_version: AaveVersion = AaveVersion.V3,
):
"""Fetch all tracked Aave (v2 or v3) events to CSV file."""
token_cache = TokenCache()
http_adapter = HTTPAdapter(pool_connections=max_workers, pool_maxsize=max_workers)
web3_factory = TunedWeb3Factory(json_rpc_url, http_adapter)
Expand All @@ -222,7 +272,7 @@ def aave_v3_fetch_events_to_csv(
buffers = {}

for event_name, mapping in event_mapping.items():
file_path = f"{output_folder}/aave-v3-{aave_network_name.lower()}-{event_name.lower()}.csv"
file_path = f"{output_folder}/aave-{aave_version.value}-{aave_network_name.lower()}-{event_name.lower()}.csv"
exists_already = Path(file_path).exists()
file_handler = open(file_path, "a")
csv_writer = csv.DictWriter(file_handler, fieldnames=mapping["field_names"])
Expand Down Expand Up @@ -288,13 +338,15 @@ def update_progress(
notify=update_progress,
chunk_size=100,
context=token_cache,
reorg_mon=reorg_monitor,
extract_timestamps=None,
):
try:
# write to correct buffer
event_name = log_result["event"].event_name
buffer = buffers[event_name]["buffer"]
decode_function = event_mapping[event_name]["decode_function"]
decoded_result = decode_function(aave_network_name, log_result)
decoded_result = decode_function(aave_network_name, log_result, aave_version)
# Note: decoded_result is None if the event is e.g. from Aave v2 contract
if decoded_result:
logger.debug(f"Adding event to buffer: {event_name}")
Expand Down
Loading

0 comments on commit ba46943

Please sign in to comment.