Skip to content

Commit d1f6299

Browse files
authored
Karak job (HemeraProtocol#164)
karak partly support
1 parent 8a7dc3c commit d1f6299

File tree

13 files changed

+589
-0
lines changed

13 files changed

+589
-0
lines changed

enumeration/entity_type.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
ENSNameRenewD,
3030
ENSRegisterD,
3131
)
32+
from indexer.modules.custom.karak.karak_domain import KarakActionD, KarakAddressCurrentD, KarakVaultTokenD
3233
from indexer.modules.custom.opensea.domain.address_opensea_transactions import AddressOpenseaTransaction
3334
from indexer.modules.custom.opensea.domain.opensea_order import OpenseaOrder
3435
from indexer.modules.custom.uniswap_v3.domain.feature_uniswap_v3 import (
@@ -66,6 +67,8 @@ class EntityType(IntFlag):
6667

6768
ENS = 1 << 10
6869

70+
KARAK = 1 << 11
71+
6972
EIGEN_LAYER = 1 << 13
7073

7174
EXPLORER = EXPLORER_BASE | EXPLORER_TOKEN | EXPLORER_TRACE
@@ -186,6 +189,11 @@ def generate_output_types(entity_types):
186189
yield AddressOpenseaTransaction
187190
yield OpenseaOrder
188191

192+
if entity_types & EntityType.KARAK:
193+
yield KarakActionD
194+
yield KarakVaultTokenD
195+
yield KarakAddressCurrentD
196+
189197
if entity_types & EntityType.EIGEN_LAYER:
190198
yield EigenLayerActionD
191199
yield EigenLayerAddressCurrentD
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#!/usr/bin/env python
2+
# -*- coding: utf-8 -*-
3+
# @Time 2024/9/19 15:18
4+
# @Author will
5+
# @File __init__.py.py
6+
# @Brief
7+
"""Currently, this job only support Deposit, StartWithDraw, FinishWithDraw, more events coming soon"""
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#!/usr/bin/env python
2+
# -*- coding: utf-8 -*-
3+
# @Time 2024/9/19 15:18
4+
# @Author will
5+
# @File __init__.py.py
6+
# @Brief
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#!/usr/bin/env python
2+
# -*- coding: utf-8 -*-
3+
# @Time 2024/9/20 15:01
4+
# @Author will
5+
# @File routes.py
6+
# @Brief
Lines changed: 291 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,291 @@
1+
import logging
2+
from collections import defaultdict
3+
from typing import Any, Dict, List
4+
5+
from eth_abi import decode
6+
from eth_typing import Decodable
7+
from sqlalchemy import func
8+
9+
from common.utils.exception_control import FastShutdownError
10+
from indexer.domain.transaction import Transaction
11+
from indexer.executors.batch_work_executor import BatchWorkExecutor
12+
from indexer.jobs import FilterTransactionDataJob
13+
from indexer.modules.custom.karak.karak_abi import DEPOSIT_EVENT, FINISH_WITHDRAWAL_EVENT, START_WITHDRAWAL_EVENT
14+
from indexer.modules.custom.karak.karak_conf import CHAIN_CONTRACT
15+
from indexer.modules.custom.karak.karak_domain import (
16+
KarakActionD,
17+
KarakAddressCurrentD,
18+
KarakVaultTokenD,
19+
karak_address_current_factory,
20+
)
21+
from indexer.modules.custom.karak.models.af_karak_address_current import AfKarakAddressCurrent
22+
from indexer.modules.custom.karak.models.af_karak_vault_token import AfKarakVaultToken
23+
from indexer.specification.specification import TopicSpecification, TransactionFilterByLogs
24+
from indexer.utils.abi import bytes_to_hex_str, decode_log
25+
from indexer.utils.utils import extract_eth_address
26+
27+
logger = logging.getLogger(__name__)
28+
29+
30+
class ExportKarakJob(FilterTransactionDataJob):
31+
# transaction with its logs
32+
dependency_types = [Transaction]
33+
output_types = [KarakActionD, KarakVaultTokenD, KarakAddressCurrentD]
34+
able_to_reorg = False
35+
36+
def __init__(self, **kwargs):
37+
super().__init__(**kwargs)
38+
39+
self._batch_work_executor = BatchWorkExecutor(
40+
kwargs["batch_size"],
41+
kwargs["max_workers"],
42+
job_name=self.__class__.__name__,
43+
)
44+
45+
self._is_batch = kwargs["batch_size"] > 1
46+
self.db_service = kwargs["config"].get("db_service")
47+
self.chain_id = self._web3.eth.chain_id
48+
self.karak_conf = CHAIN_CONTRACT[self.chain_id]
49+
self.token_vault = dict()
50+
self.vault_token = dict()
51+
self.init_vaults()
52+
53+
def init_vaults(self):
54+
# fetch from database
55+
if not self.db_service:
56+
return
57+
58+
with self.db_service.get_service_session() as session:
59+
query = session.query(AfKarakVaultToken)
60+
result = query.all()
61+
62+
for r in result:
63+
self.token_vault[bytes_to_hex_str(r.token)] = bytes_to_hex_str(r.vault)
64+
self.vault_token[bytes_to_hex_str(r.vault)] = bytes_to_hex_str(r.token)
65+
logging.info(f"init vaults with {len(self.token_vault)} tokens")
66+
67+
def get_filter(self):
68+
# deposit, startWithdraw, finishWithdraw
69+
topics = []
70+
addresses = []
71+
for k, item in self.karak_conf.items():
72+
if isinstance(item, dict) and item.get("topic"):
73+
topics.append(item["topic"])
74+
if isinstance(item, dict) and item.get("address"):
75+
addresses.append(item["address"])
76+
for ad in self.vault_token:
77+
addresses.append(ad)
78+
return [
79+
TransactionFilterByLogs(topics_filters=[TopicSpecification(topics=topics, addresses=addresses)]),
80+
]
81+
82+
def discover_vaults(self, transactions: List[Transaction]):
83+
res = []
84+
for transaction in transactions:
85+
# deployVault
86+
if not transaction.input.startswith(self.karak_conf["NEW_VAULT"]["starts_with"]):
87+
continue
88+
logs = transaction.receipt.logs
89+
vault = None
90+
for log in logs:
91+
if (
92+
log.topic0 == self.karak_conf["NEW_VAULT"]["topic"]
93+
and log.address == self.karak_conf["NEW_VAULT"]["address"]
94+
):
95+
vault = extract_eth_address(log.topic1)
96+
break
97+
dd = self.decode_function(
98+
["address", "string", "string", "uint8"], bytes.fromhex(transaction.input[2:])[4:]
99+
)
100+
kvt = KarakVaultTokenD(
101+
vault=vault,
102+
token=dd[0],
103+
name=dd[1],
104+
symbol=dd[2],
105+
asset_type=dd[3],
106+
)
107+
self.token_vault[kvt.token] = kvt.vault
108+
self.vault_token[kvt.vault] = kvt.token
109+
res.append(kvt)
110+
return res
111+
112+
def _collect(self, **kwargs):
113+
transactions: List[Transaction] = self._data_buff.get(Transaction.type(), [])
114+
new_vaults = self.discover_vaults(transactions)
115+
if new_vaults:
116+
self._collect_items(KarakVaultTokenD.type(), new_vaults)
117+
res = []
118+
119+
for transaction in transactions:
120+
logs = transaction.receipt.logs
121+
for log in logs:
122+
if log.topic0 == self.karak_conf["DEPOSIT"]["topic"] and log.address in self.vault_token:
123+
dl = decode_log(DEPOSIT_EVENT, log)
124+
vault = log.address
125+
amount = dl.get("shares")
126+
by = dl.get("by")
127+
if not by:
128+
staker = transaction.from_address
129+
else:
130+
staker = by
131+
owner = dl.get("owner")
132+
133+
if not amount or not vault:
134+
raise FastShutdownError(f"karak job failed {transaction.hash}")
135+
kad = KarakActionD(
136+
transaction_hash=transaction.hash,
137+
log_index=log.log_index,
138+
transaction_index=transaction.transaction_index,
139+
block_number=log.block_number,
140+
block_timestamp=log.block_timestamp,
141+
method=transaction.get_method_id(),
142+
event_name=DEPOSIT_EVENT["name"],
143+
topic0=log.topic0,
144+
from_address=transaction.from_address,
145+
to_address=transaction.to_address,
146+
vault=vault,
147+
amount=amount,
148+
staker=staker,
149+
)
150+
res.append(kad)
151+
elif (
152+
log.topic0 == self.karak_conf["START_WITHDRAW"]["topic"]
153+
and log.address == self.karak_conf["START_WITHDRAW"]["address"]
154+
):
155+
dl = decode_log(START_WITHDRAWAL_EVENT, log)
156+
vault = dl.get("vault")
157+
staker = dl.get("staker")
158+
operator = dl.get("operator")
159+
withdrawer = dl.get("withdrawer")
160+
shares = dl.get("shares")
161+
kad = KarakActionD(
162+
transaction_hash=transaction.hash,
163+
log_index=log.log_index,
164+
transaction_index=transaction.transaction_index,
165+
block_number=log.block_number,
166+
block_timestamp=log.block_timestamp,
167+
method=transaction.get_method_id(),
168+
event_name=START_WITHDRAWAL_EVENT["name"],
169+
topic0=log.topic0,
170+
from_address=transaction.from_address,
171+
to_address=transaction.to_address,
172+
vault=vault,
173+
staker=staker,
174+
operator=operator,
175+
withdrawer=withdrawer,
176+
shares=shares,
177+
amount=shares,
178+
)
179+
res.append(kad)
180+
181+
elif (
182+
log.topic0 == self.karak_conf["FINISH_WITHDRAW"]["topic"]
183+
and log.address == self.karak_conf["FINISH_WITHDRAW"]["address"]
184+
):
185+
dl = decode_log(FINISH_WITHDRAWAL_EVENT, log)
186+
vault = dl.get("vault")
187+
staker = dl.get("staker")
188+
operator = dl.get("operator")
189+
withdrawer = dl.get("withdrawer")
190+
shares = dl.get("shares")
191+
withdrawroot = dl.get("withdrawRoot")
192+
kad = KarakActionD(
193+
transaction_hash=transaction.hash,
194+
log_index=log.log_index,
195+
transaction_index=transaction.transaction_index,
196+
block_number=log.block_number,
197+
block_timestamp=log.block_timestamp,
198+
method=transaction.get_method_id(),
199+
event_name=FINISH_WITHDRAWAL_EVENT["name"],
200+
topic0=log.topic0,
201+
from_address=transaction.from_address,
202+
to_address=transaction.to_address,
203+
vault=vault,
204+
staker=staker,
205+
operator=operator,
206+
withdrawer=withdrawer,
207+
shares=shares,
208+
withdrawroot=withdrawroot,
209+
amount=shares,
210+
)
211+
res.append(kad)
212+
self._collect_items(KarakActionD.type(), res)
213+
batch_result_dic = self.calculate_batch_result(res)
214+
exists_dic = self.get_existing_address_current(list(batch_result_dic.keys()))
215+
for address, outer_dic in batch_result_dic.items():
216+
for vault, kad in outer_dic.items():
217+
if address in exists_dic and vault in exists_dic[address]:
218+
exists_kad = exists_dic[address][vault]
219+
exists_kad.deposit_amount += kad.deposit_amount
220+
exists_kad.start_withdraw_amount += kad.start_withdraw_amount
221+
exists_kad.finish_withdraw_amount += kad.finish_withdraw_amount
222+
self._collect_item(kad.type(), exists_kad)
223+
else:
224+
self._collect_item(kad.type(), kad)
225+
226+
@staticmethod
227+
def decode_function(decode_types, output: Decodable) -> Any:
228+
try:
229+
return decode(decode_types, output)
230+
except Exception as e:
231+
logger.error(e)
232+
return [None] * len(decode_types)
233+
234+
def get_existing_address_current(self, addresses):
235+
if not self.db_service:
236+
return {}
237+
238+
addresses = [ad[2:] for ad in addresses if ad and ad.startswith("0x")]
239+
if not addresses:
240+
return {}
241+
with self.db_service.get_service_session() as session:
242+
query = session.query(AfKarakAddressCurrent).filter(
243+
func.encode(AfKarakAddressCurrent.address, "hex").in_(addresses)
244+
)
245+
result = query.all()
246+
lis = []
247+
for rr in result:
248+
lis.append(
249+
KarakAddressCurrentD(
250+
address=bytes_to_hex_str(rr.address),
251+
vault=bytes_to_hex_str(rr.vault),
252+
deposit_amount=rr.deposit_amount,
253+
start_withdraw_amount=rr.start_withdraw_amount,
254+
finish_withdraw_amount=rr.finish_withdraw_amount,
255+
)
256+
)
257+
258+
return create_nested_dict(lis)
259+
260+
def calculate_batch_result(self, karak_actions: List[KarakActionD]) -> Any:
261+
def nested_dict():
262+
return defaultdict(karak_address_current_factory)
263+
264+
res_d = defaultdict(nested_dict)
265+
for action in karak_actions:
266+
staker = action.staker
267+
vault = action.vault
268+
topic0 = action.topic0
269+
if topic0 == self.karak_conf["DEPOSIT"]["topic"]:
270+
res_d[staker][vault].address = staker
271+
res_d[staker][vault].vault = vault
272+
res_d[staker][vault].deposit_amount += action.amount
273+
elif topic0 == self.karak_conf["START_WITHDRAW"]["topic"]:
274+
res_d[staker][vault].address = staker
275+
res_d[staker][vault].vault = vault
276+
res_d[staker][vault].start_withdraw_amount += action.amount
277+
elif topic0 == self.karak_conf["FINISH_WITHDRAW"]["topic"]:
278+
res_d[staker][vault].address = staker
279+
res_d[staker][vault].vault = vault
280+
res_d[staker][vault].finish_withdraw_amount += action.amount
281+
return res_d
282+
283+
284+
def create_nested_dict(data_list: List[KarakAddressCurrentD]) -> Dict[str, Dict[str, KarakAddressCurrentD]]:
285+
result = {}
286+
for item in data_list:
287+
if item.address and item.vault:
288+
if item.address not in result:
289+
result[item.address] = {}
290+
result[item.address][item.vault] = item
291+
return result
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
#!/usr/bin/env python
2+
# -*- coding: utf-8 -*-
3+
# @Time 2024/9/20 11:07
4+
# @Author will
5+
# @File karak_abi.py
6+
# @Brief
7+
import json
8+
from typing import cast
9+
10+
from web3.types import ABIEvent
11+
12+
from indexer.utils.abi import event_log_abi_to_topic
13+
14+
DEPOSIT_EVENT = cast(
15+
ABIEvent,
16+
json.loads(
17+
"""{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"by","type":"address"},{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":false,"internalType":"uint256","name":"assets","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"shares","type":"uint256"}],"name":"Deposit","type":"event"}"""
18+
),
19+
)
20+
21+
START_WITHDRAWAL_EVENT = cast(
22+
ABIEvent,
23+
json.loads(
24+
"""{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"vault","type":"address"},{"indexed":true,"internalType":"address","name":"staker","type":"address"},{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":false,"internalType":"address","name":"withdrawer","type":"address"},{"indexed":false,"internalType":"uint256","name":"shares","type":"uint256"}],"name":"StartedWithdrawal","type":"event"}
25+
"""
26+
),
27+
)
28+
29+
FINISH_WITHDRAWAL_EVENT = cast(
30+
ABIEvent,
31+
json.loads(
32+
"""{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"vault","type":"address"},{"indexed":true,"internalType":"address","name":"staker","type":"address"},{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":false,"internalType":"address","name":"withdrawer","type":"address"},{"indexed":false,"internalType":"uint256","name":"shares","type":"uint256"},{"indexed":false,"internalType":"bytes32","name":"withdrawRoot","type":"bytes32"}],"name":"FinishedWithdrawal","type":"event"}
33+
"""
34+
),
35+
)
36+
37+
TRANSFER_EVENT = cast(
38+
ABIEvent,
39+
json.loads(
40+
"""{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Transfer","type":"event"}"""
41+
),
42+
)

0 commit comments

Comments
 (0)