yearn-treasury 0.0.44__cp310-cp310-win32.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of yearn-treasury might be problematic. Click here for more details.

Files changed (128) hide show
  1. yearn_treasury/__init__.py +20 -0
  2. yearn_treasury/_db.cp310-win32.pyd +0 -0
  3. yearn_treasury/_db.py +41 -0
  4. yearn_treasury/_ens.cp310-win32.pyd +0 -0
  5. yearn_treasury/_ens.py +31 -0
  6. yearn_treasury/_logging.cp310-win32.pyd +0 -0
  7. yearn_treasury/_logging.py +43 -0
  8. yearn_treasury/address_labels.yaml +39 -0
  9. yearn_treasury/budget/__init__.cp310-win32.pyd +0 -0
  10. yearn_treasury/budget/__init__.py +6 -0
  11. yearn_treasury/budget/_request.cp310-win32.pyd +0 -0
  12. yearn_treasury/budget/_request.py +44 -0
  13. yearn_treasury/budget/_requests.cp310-win32.pyd +0 -0
  14. yearn_treasury/budget/_requests.py +96 -0
  15. yearn_treasury/constants.py +89 -0
  16. yearn_treasury/main.py +176 -0
  17. yearn_treasury/py.typed +1 -0
  18. yearn_treasury/rules/__init__.py +20 -0
  19. yearn_treasury/rules/constants.cp310-win32.pyd +0 -0
  20. yearn_treasury/rules/constants.py +17 -0
  21. yearn_treasury/rules/cost_of_revenue/__init__.py +1 -0
  22. yearn_treasury/rules/cost_of_revenue/gas.cp310-win32.pyd +0 -0
  23. yearn_treasury/rules/cost_of_revenue/gas.py +66 -0
  24. yearn_treasury/rules/cost_of_revenue/match_on_hash.yaml +12 -0
  25. yearn_treasury/rules/expense/__init__.cp310-win32.pyd +0 -0
  26. yearn_treasury/rules/expense/__init__.py +4 -0
  27. yearn_treasury/rules/expense/general.cp310-win32.pyd +0 -0
  28. yearn_treasury/rules/expense/general.py +13 -0
  29. yearn_treasury/rules/expense/infrastructure.cp310-win32.pyd +0 -0
  30. yearn_treasury/rules/expense/infrastructure.py +47 -0
  31. yearn_treasury/rules/expense/match_on_hash.yaml +48 -0
  32. yearn_treasury/rules/expense/match_on_to_address.yaml +7 -0
  33. yearn_treasury/rules/expense/people.cp310-win32.pyd +0 -0
  34. yearn_treasury/rules/expense/people.py +84 -0
  35. yearn_treasury/rules/expense/security.cp310-win32.pyd +0 -0
  36. yearn_treasury/rules/expense/security.py +147 -0
  37. yearn_treasury/rules/ignore/__init__.py +8 -0
  38. yearn_treasury/rules/ignore/general.cp310-win32.pyd +0 -0
  39. yearn_treasury/rules/ignore/general.py +11 -0
  40. yearn_treasury/rules/ignore/maker.py +92 -0
  41. yearn_treasury/rules/ignore/passthru.py +310 -0
  42. yearn_treasury/rules/ignore/staking.py +102 -0
  43. yearn_treasury/rules/ignore/swaps/__init__.py +25 -0
  44. yearn_treasury/rules/ignore/swaps/_skip_tokens.py +9 -0
  45. yearn_treasury/rules/ignore/swaps/aave.py +69 -0
  46. yearn_treasury/rules/ignore/swaps/auctions.cp310-win32.pyd +0 -0
  47. yearn_treasury/rules/ignore/swaps/auctions.py +31 -0
  48. yearn_treasury/rules/ignore/swaps/compound.py +58 -0
  49. yearn_treasury/rules/ignore/swaps/conversion_factory.cp310-win32.pyd +0 -0
  50. yearn_treasury/rules/ignore/swaps/conversion_factory.py +21 -0
  51. yearn_treasury/rules/ignore/swaps/cowswap.py +87 -0
  52. yearn_treasury/rules/ignore/swaps/curve.py +170 -0
  53. yearn_treasury/rules/ignore/swaps/gearbox.cp310-win32.pyd +0 -0
  54. yearn_treasury/rules/ignore/swaps/gearbox.py +37 -0
  55. yearn_treasury/rules/ignore/swaps/iearn.cp310-win32.pyd +0 -0
  56. yearn_treasury/rules/ignore/swaps/iearn.py +43 -0
  57. yearn_treasury/rules/ignore/swaps/otc.cp310-win32.pyd +0 -0
  58. yearn_treasury/rules/ignore/swaps/otc.py +58 -0
  59. yearn_treasury/rules/ignore/swaps/pooltogether.cp310-win32.pyd +0 -0
  60. yearn_treasury/rules/ignore/swaps/pooltogether.py +23 -0
  61. yearn_treasury/rules/ignore/swaps/synthetix.cp310-win32.pyd +0 -0
  62. yearn_treasury/rules/ignore/swaps/synthetix.py +10 -0
  63. yearn_treasury/rules/ignore/swaps/uniswap.py +294 -0
  64. yearn_treasury/rules/ignore/swaps/unwrapper.cp310-win32.pyd +0 -0
  65. yearn_treasury/rules/ignore/swaps/unwrapper.py +17 -0
  66. yearn_treasury/rules/ignore/swaps/vaults.cp310-win32.pyd +0 -0
  67. yearn_treasury/rules/ignore/swaps/vaults.py +263 -0
  68. yearn_treasury/rules/ignore/swaps/woofy.cp310-win32.pyd +0 -0
  69. yearn_treasury/rules/ignore/swaps/woofy.py +80 -0
  70. yearn_treasury/rules/ignore/swaps/yfi.cp310-win32.pyd +0 -0
  71. yearn_treasury/rules/ignore/swaps/yfi.py +111 -0
  72. yearn_treasury/rules/ignore/swaps/yla.cp310-win32.pyd +0 -0
  73. yearn_treasury/rules/ignore/swaps/yla.py +28 -0
  74. yearn_treasury/rules/ignore/unit.cp310-win32.pyd +0 -0
  75. yearn_treasury/rules/ignore/unit.py +40 -0
  76. yearn_treasury/rules/ignore/weth.cp310-win32.pyd +0 -0
  77. yearn_treasury/rules/ignore/weth.py +48 -0
  78. yearn_treasury/rules/ignore/ygov.cp310-win32.pyd +0 -0
  79. yearn_treasury/rules/ignore/ygov.py +16 -0
  80. yearn_treasury/rules/other_expense/__init__.cp310-win32.pyd +0 -0
  81. yearn_treasury/rules/other_expense/__init__.py +7 -0
  82. yearn_treasury/rules/other_expense/boost.cp310-win32.pyd +0 -0
  83. yearn_treasury/rules/other_expense/boost.py +49 -0
  84. yearn_treasury/rules/other_expense/bugs.cp310-win32.pyd +0 -0
  85. yearn_treasury/rules/other_expense/bugs.py +81 -0
  86. yearn_treasury/rules/other_expense/donations.cp310-win32.pyd +0 -0
  87. yearn_treasury/rules/other_expense/donations.py +43 -0
  88. yearn_treasury/rules/other_expense/dyfi.cp310-win32.pyd +0 -0
  89. yearn_treasury/rules/other_expense/dyfi.py +29 -0
  90. yearn_treasury/rules/other_expense/events.cp310-win32.pyd +0 -0
  91. yearn_treasury/rules/other_expense/events.py +21 -0
  92. yearn_treasury/rules/other_expense/match_on_hash.yaml +43 -0
  93. yearn_treasury/rules/other_expense/match_on_to_address.yaml +8 -0
  94. yearn_treasury/rules/other_expense/misc.cp310-win32.pyd +0 -0
  95. yearn_treasury/rules/other_expense/misc.py +49 -0
  96. yearn_treasury/rules/other_expense/revshare.cp310-win32.pyd +0 -0
  97. yearn_treasury/rules/other_expense/revshare.py +20 -0
  98. yearn_treasury/rules/other_income/__init__.cp310-win32.pyd +0 -0
  99. yearn_treasury/rules/other_income/__init__.py +2 -0
  100. yearn_treasury/rules/other_income/airdrops.cp310-win32.pyd +0 -0
  101. yearn_treasury/rules/other_income/airdrops.py +30 -0
  102. yearn_treasury/rules/other_income/match_on_hash.yaml +21 -0
  103. yearn_treasury/rules/other_income/misc.cp310-win32.pyd +0 -0
  104. yearn_treasury/rules/other_income/misc.py +80 -0
  105. yearn_treasury/rules/revenue/__init__.py +6 -0
  106. yearn_treasury/rules/revenue/bribes.cp310-win32.pyd +0 -0
  107. yearn_treasury/rules/revenue/bribes.py +26 -0
  108. yearn_treasury/rules/revenue/farming.cp310-win32.pyd +0 -0
  109. yearn_treasury/rules/revenue/farming.py +56 -0
  110. yearn_treasury/rules/revenue/keepcoins.cp310-win32.pyd +0 -0
  111. yearn_treasury/rules/revenue/keepcoins.py +63 -0
  112. yearn_treasury/rules/revenue/match_on_hash.yaml +4 -0
  113. yearn_treasury/rules/revenue/seasolver.cp310-win32.pyd +0 -0
  114. yearn_treasury/rules/revenue/seasolver.py +23 -0
  115. yearn_treasury/rules/revenue/vaults.py +168 -0
  116. yearn_treasury/rules/revenue/yteams.cp310-win32.pyd +0 -0
  117. yearn_treasury/rules/revenue/yteams.py +17 -0
  118. yearn_treasury/shitcoins.py +141 -0
  119. yearn_treasury/vaults.cp310-win32.pyd +0 -0
  120. yearn_treasury/vaults.py +50 -0
  121. yearn_treasury/wallets.yaml +54 -0
  122. yearn_treasury/yteams.py +208 -0
  123. yearn_treasury-0.0.44.dist-info/METADATA +86 -0
  124. yearn_treasury-0.0.44.dist-info/RECORD +128 -0
  125. yearn_treasury-0.0.44.dist-info/WHEEL +5 -0
  126. yearn_treasury-0.0.44.dist-info/entry_points.txt +2 -0
  127. yearn_treasury-0.0.44.dist-info/top_level.txt +2 -0
  128. yearn_treasury__mypyc.cp310-win32.pyd +0 -0
@@ -0,0 +1,294 @@
1
+ from typing import Final
2
+
3
+ from brownie import ZERO_ADDRESS, chain
4
+ from dao_treasury import TreasuryTx, TreasuryWallet
5
+ from y import Network, Contract
6
+ from y.constants import CHAINID, WRAPPED_GAS_COIN
7
+
8
+ from yearn_treasury.rules.ignore.swaps import swaps
9
+ from yearn_treasury.rules.ignore.swaps._skip_tokens import SKIP_TOKENS
10
+
11
+
12
+ uniswap: Final = swaps("Uniswap")
13
+
14
+
15
+ ROUTERS: Final = ("0xd9e1cE17f2641f24aE83637ab66a2cca9C378B9F",)
16
+
17
+
18
+ @uniswap("Add Liquidity")
19
+ async def is_uniswap_deposit(tx: TreasuryTx) -> bool:
20
+ if tx.to_address:
21
+ try:
22
+ events = tx.events
23
+ except KeyError as e:
24
+ if e.args[0] == "components":
25
+ return False
26
+ raise
27
+
28
+ if "Mint" in events and "Transfer" in events:
29
+ transfers = events["Transfer"]
30
+ for mint in events["Mint"]:
31
+ event_args = {"sender", "amount0", "amount1"}
32
+ if any(arg not in mint for arg in event_args):
33
+ continue
34
+
35
+ # LP token
36
+ if tx.from_address == ZERO_ADDRESS and (
37
+ tx.token == mint.address
38
+ or
39
+ # KP3R/WETH Uni v3 LP -- used while depositing to kLP-KP3R/WETH
40
+ mint.address == "0x11B7a6bc0259ed6Cf9DB8F499988F9eCc7167bf5"
41
+ ):
42
+ lp = tx.token.contract
43
+ tokens = [await lp.token0, await lp.token1]
44
+ if all(
45
+ any(
46
+ token == transfer.address
47
+ and tx.to_address == transfer[0]
48
+ and transfer[1] == mint.address
49
+ for transfer in events["Transfer"]
50
+ )
51
+ for token in tokens
52
+ ):
53
+ return True
54
+
55
+ # Maybe native asset was used instead of wrapped.
56
+ if tokens[0] == WRAPPED_GAS_COIN:
57
+ if any(
58
+ tokens[1] == transfer.address
59
+ and tx.to_address
60
+ == transfer.values()[:1] # type: ignore [index]
61
+ == [mint["sender"], mint.address]
62
+ for transfer in transfers
63
+ ):
64
+ for int_tx in chain.get_transaction(tx.hash).internal_transfers:
65
+ if (
66
+ tx.to_address == int_tx["from"] == mint["sender"]
67
+ and int_tx["to"] in ROUTERS
68
+ ):
69
+ for transfer in transfers:
70
+ if (
71
+ transfer[0] == WRAPPED_GAS_COIN == transfer.address
72
+ and tx.token == transfer[1]
73
+ and transfer[2] == int_tx["value"]
74
+ ):
75
+ return True
76
+
77
+ elif tokens[1] == WRAPPED_GAS_COIN:
78
+ if any(
79
+ tokens[0] == transfer.address
80
+ and tx.to_address
81
+ == transfer.values()[:1] # type: ignore [index]
82
+ == [mint["sender"], mint.address]
83
+ for transfer in transfers
84
+ ):
85
+ for int_tx in chain.get_transaction(tx.hash).internal_transfers:
86
+ if (
87
+ tx.to_address == int_tx["from"] == mint["sender"]
88
+ and int_tx["to"] in ROUTERS
89
+ ):
90
+ for transfer in transfers:
91
+ if (
92
+ transfer[0] == WRAPPED_GAS_COIN == transfer.address
93
+ and tx.token == transfer[1]
94
+ and transfer[2] == int_tx["value"]
95
+ ):
96
+ return True
97
+
98
+ else:
99
+ print(f"tokens: {tokens}")
100
+
101
+ # Component tokens
102
+ elif tx.to_address == mint.address:
103
+ return True
104
+
105
+ if CHAINID == Network.Mainnet:
106
+ return (
107
+ tx.hash == "0x3a000d3aa5d0d83a3ff359de261bfcecdc62cd13500b8ab517802742ac918627"
108
+ ) # uni v3
109
+ return False
110
+
111
+
112
+ @uniswap("Remove Liquidity")
113
+ async def is_uniswap_withdrawal(tx: TreasuryTx) -> bool:
114
+ if tx.to_address:
115
+ try:
116
+ events = tx.events
117
+ except KeyError as e:
118
+ if e.args[0] == "components":
119
+ return False
120
+ raise
121
+
122
+ if "Burn" in events and "Transfer" in events:
123
+ transfers = events["Transfer"]
124
+ for burn in events["Burn"]:
125
+ event_args = {"sender", "amount0", "amount1", "to"}
126
+ if any(arg not in burn for arg in event_args):
127
+ continue
128
+
129
+ # LP token
130
+ if (
131
+ TreasuryWallet._get_instance(tx.from_address.address) # type: ignore [union-attr, arg-type]
132
+ and tx.from_address == burn["to"]
133
+ and tx.token == tx.to_address == burn.address
134
+ ):
135
+ lp = tx.token.contract
136
+ tokens = [await lp.token0, await lp.token1]
137
+ if tx.token == tx.to_address and all(
138
+ any(
139
+ token == transfer.address
140
+ and tx.to_address == transfer[0]
141
+ and tx.from_address == transfer[1] == burn["to"]
142
+ for transfer in transfers
143
+ )
144
+ for token in tokens
145
+ ):
146
+ return True
147
+
148
+ # Maybe native asset was used instead of wrapped.
149
+ if tokens[0] == WRAPPED_GAS_COIN:
150
+ if any(
151
+ tokens[1] == transfer.address
152
+ and tx.token == tx.to_address == transfer[0]
153
+ and tx.from_address == transfer[1] == burn["to"]
154
+ for transfer in transfers
155
+ ):
156
+ for int_tx in chain.get_transaction(tx.hash).internal_transfers:
157
+ if int_tx["from"] in ROUTERS and tx.from_address == int_tx["to"]:
158
+ for transfer in transfers:
159
+ if (
160
+ tx.token == transfer[0]
161
+ and transfer[1] == transfer.address == WRAPPED_GAS_COIN
162
+ and transfer[2] == int_tx["value"]
163
+ ):
164
+ return True
165
+
166
+ elif tokens[1] == WRAPPED_GAS_COIN:
167
+ if any(
168
+ tokens[0] == transfer.address
169
+ and tx.token == tx.to_address == transfer[0]
170
+ and tx.from_address == transfer[1] == burn["to"]
171
+ for transfer in transfers
172
+ ):
173
+ for int_tx in chain.get_transaction(tx.hash).internal_transfers:
174
+ if int_tx["from"] in ROUTERS and tx.from_address == int_tx["to"]:
175
+ for transfer in transfers:
176
+ if (
177
+ transfer[0] == tx.token
178
+ and transfer[1] == transfer.address == WRAPPED_GAS_COIN
179
+ and transfer[2] == int_tx["value"]
180
+ ):
181
+ return True
182
+
183
+ else:
184
+ print(f"tokens: {tokens}")
185
+
186
+ # Component tokens
187
+ elif tx.from_address == burn.address:
188
+ return True
189
+
190
+ return CHAINID == Network.Mainnet and tx.hash in (
191
+ "0xf0723677162cdf8105c0f752a8c03c53803cb9dd9a6649f3b9bc5d26822d531f",
192
+ "0xaf1b7f138fb8bf3f5e13a680cb4a9b7983ec71a75836111c03dee6ae530db176", # v3
193
+ # these use ETH not WETH so they dont match
194
+ "0x5b05dfd3305c471df0ad944237edc2dbb14b268f7415252de566a5ab283002af",
195
+ "0x46ab9b383751f612ea0de8c0c6e9fa86e7324de04b032ecb48161989b7dbdbf7",
196
+ )
197
+
198
+
199
+ @uniswap("Swap")
200
+ async def is_uniswap_swap(tx: TreasuryTx) -> bool:
201
+ # The LP for dumping solidSEX is not verified :( devs blz do something
202
+ # Sell side
203
+ if (
204
+ TreasuryWallet._get_instance(tx.from_address.address) # type: ignore [union-attr, arg-type]
205
+ and tx.to_nickname == "Non-Verified Contract: 0xa66901D1965F5410dEeB4d0Bb43f7c1B628Cb20b"
206
+ and tx.symbol == "SOLIDsex"
207
+ ):
208
+ return True
209
+ # Buy side
210
+ elif (
211
+ tx.from_nickname == "Non-Verified Contract: 0xa66901D1965F5410dEeB4d0Bb43f7c1B628Cb20b"
212
+ and TreasuryWallet._get_instance(tx.to_address.address) # type: ignore [union-attr, arg-type]
213
+ and tx.symbol == "WFTM"
214
+ ):
215
+ return True
216
+
217
+ elif CHAINID == Network.Mainnet and tx.hash in (
218
+ # uni v3
219
+ "0x490245ef6e3c60127491415afdea23c13f4ca1a8c04de4fb3a498e7f7574b724",
220
+ "0xf2c6ff1863c60ca9924b611dad5548ffc4fecbab2fee34e2601dd16f0aa8e333",
221
+ ):
222
+ return True
223
+
224
+ # All other swaps
225
+ for swap in tx.get_events("Swap"):
226
+ # Sell side
227
+ if (
228
+ TreasuryWallet._get_instance(tx.from_address.address) # type: ignore [union-attr, arg-type]
229
+ and tx.to_address == swap.address
230
+ ):
231
+ pool = await Contract.coroutine(swap.address)
232
+ if not is_pool(pool): # type: ignore [arg-type]
233
+ continue
234
+
235
+ token0 = await pool.token0 # type: ignore [attr-defined]
236
+ token1 = await pool.token1 # type: ignore [attr-defined]
237
+ if token0 in SKIP_TOKENS or token1 in SKIP_TOKENS:
238
+ # This will be recorded elsewhere
239
+ continue
240
+
241
+ # The below code only works for v2 swaps, let's skip v3 swaps
242
+ if "sqrtPriceX96" in swap:
243
+ continue
244
+
245
+ if tx.token == token0:
246
+ # TODO: get rid of this rounding when we migrate to postgres
247
+ event_amount = round(tx.token.scale_value(swap["amount0In"]), 10)
248
+ if event_amount == round(tx.amount, 10):
249
+ return True
250
+ print(
251
+ f"Uniswap sell token0 amount does not match: {round(tx.amount, 10)} {event_amount}"
252
+ )
253
+ elif tx.token == token1:
254
+ # TODO: get rid of this rounding when we migrate to postgres
255
+ event_amount = round(tx.token.scale_value(swap["amount1In"]), 10)
256
+ if event_amount == round(tx.amount, 10):
257
+ return True
258
+ print(
259
+ f"Uniswap sell token1 amount does not match: {round(tx.amount, 10)} {event_amount}"
260
+ )
261
+
262
+ # Buy side
263
+ elif tx.from_address == swap.address and TreasuryWallet._get_instance(
264
+ tx.to_address.address # type: ignore [union-attr, arg-type]
265
+ ):
266
+ pool = await Contract.coroutine(swap.address)
267
+ if not is_pool(pool): # type: ignore [arg-type]
268
+ continue
269
+ token0 = await pool.token0 # type: ignore [attr-defined]
270
+ token1 = await pool.token1 # type: ignore [attr-defined]
271
+ if token0 in SKIP_TOKENS or token1 in SKIP_TOKENS:
272
+ # This will be recorded elsewhere
273
+ continue
274
+ if "amount0Out" in swap and tx.token == token0:
275
+ # TODO: get rid of this rounding when we migrate to postgres
276
+ event_amount = round(tx.token.scale_value(swap["amount0Out"]), 9)
277
+ if event_amount == round(tx.amount, 9):
278
+ return True
279
+ print(
280
+ f"Uniswap buy token0 amount does not match: {round(tx.amount, 9)} {event_amount}"
281
+ )
282
+ elif "amount1Out" in swap and tx.token == token1:
283
+ # TODO: get rid of this rounding when we migrate to postgres
284
+ event_amount = round(tx.token.scale_value(swap["amount1Out"]), 10)
285
+ if event_amount == round(tx.amount, 10):
286
+ return True
287
+ print(
288
+ f"Uniswap buy token1 amount does not match: {round(tx.amount, 10)} {event_amount}"
289
+ )
290
+ return False
291
+
292
+
293
+ def is_pool(pool: Contract) -> bool:
294
+ return hasattr(pool, "token0") and hasattr(pool, "token1")
@@ -0,0 +1,17 @@
1
+ """
2
+ Ignore rules for the Unwrapper contract.
3
+
4
+ This module defines matching logic for swaps involving the Unwrapper
5
+ contract, so those transactions can be ignored from analytics and
6
+ reporting.
7
+ """
8
+
9
+ from dao_treasury import TreasuryTx
10
+ from y import Network
11
+
12
+ from yearn_treasury.rules.ignore.swaps import swaps
13
+
14
+
15
+ @swaps("Unwrapper", Network.Mainnet)
16
+ def is_unwrapper(tx: TreasuryTx) -> bool:
17
+ return "Contract: Unwrapper" in [tx.from_nickname, tx.to_nickname]
@@ -0,0 +1,263 @@
1
+ from typing import Final
2
+
3
+ from dao_treasury import TreasuryTx, TreasuryWallet
4
+ from eth_typing import BlockNumber, ChecksumAddress
5
+ from faster_async_lru import alru_cache
6
+ from y import Contract, Network
7
+ from y.prices.yearn import YearnInspiredVault
8
+
9
+ from yearn_treasury.constants import CHAINID, TREASURY_WALLETS
10
+ from yearn_treasury.rules.ignore.swaps import swaps
11
+ from yearn_treasury.rules.constants import ZERO_ADDRESS
12
+ from yearn_treasury.vaults import v1, v2
13
+
14
+
15
+ vaults: Final = swaps("Vaults")
16
+
17
+ TREASURY_AND_ZERO: Final = {*TREASURY_WALLETS, ZERO_ADDRESS}
18
+
19
+ all_vaults: Final = tuple(v1.keys()) + tuple(v2.values())
20
+
21
+
22
+ @vaults("Deposit")
23
+ async def is_vault_deposit(tx: TreasuryTx) -> bool:
24
+ return (
25
+ await is_v1_or_v2_vault_deposit(tx)
26
+ or tx.hash
27
+ in {
28
+ # TODO these go thru zaps and do not get caught by the logic below. figure out how to capture these
29
+ Network.Mainnet: { # type: ignore [call-overload]
30
+ "0x39616fdfc8851e10e17d955f55beea5c3dd4eed7c066a8ecbed8e50b496012ff",
31
+ "0x248e896eb732dfe40a0fa49131717bb7d2c1721743a2945ab9680787abcf9c50",
32
+ "0x2ce0240a08c8cc8d35b018995862711eb660a24d294b1aa674fbc467af4e621b",
33
+ # this is not thru a zap, its a lp-yCRVv2 deposit I probably dont need to write hueristics for
34
+ "0x93cf055d82b7e82b3877ab506629de6359fc5385ffb6b8c2fbfe0d61947fab59",
35
+ }
36
+ }.get(CHAINID, set())
37
+ or is_v3_vault_deposit(tx)
38
+ )
39
+
40
+
41
+ async def is_v1_or_v2_vault_deposit(tx: TreasuryTx) -> bool:
42
+ """This code doesn't validate amounts but so far that's not been a problem."""
43
+ try:
44
+ if "Transfer" not in tx.events:
45
+ return False
46
+ except KeyError as e:
47
+ # This happens sometimes from a busted abi, shouldnt impact us
48
+ if str(e) == "'components'":
49
+ return False
50
+ raise
51
+
52
+ transfer_events = tx.events["Transfer"]
53
+
54
+ tx_token = tx.token.address.address
55
+
56
+ block: BlockNumber = tx.block # type: ignore [assignment]
57
+ sender: ChecksumAddress
58
+ receiver: ChecksumAddress
59
+ underlying_address: ChecksumAddress
60
+
61
+ # vault side
62
+ for vault in all_vaults:
63
+ if tx_token == vault.address:
64
+ for event in transfer_events:
65
+ if tx_token == event.address:
66
+ event_pos = event.pos
67
+ sender, receiver, value = event.values()
68
+ if sender == ZERO_ADDRESS and TreasuryWallet.check_membership(receiver, block):
69
+ tx_to_address = tx.to_address
70
+ underlying_address = await _get_underlying(vault)
71
+ for _event in transfer_events:
72
+ _sender, _receiver, _value = _event.values()
73
+ if (
74
+ _event.address == underlying_address
75
+ and tx_to_address == _sender
76
+ and tx_token == _receiver
77
+ ):
78
+ # v1
79
+ if _event.pos < event_pos:
80
+ return True
81
+ # v2
82
+ if event_pos < _event.pos:
83
+ return True
84
+
85
+ # token side
86
+ for vault in all_vaults:
87
+ if tx_token == await _get_underlying(vault):
88
+ for event in transfer_events:
89
+ if tx_token == event.address:
90
+ vault_address = vault.address
91
+ event_pos = event.pos
92
+ sender, receiver, value = event.values()
93
+ if TreasuryWallet.check_membership(sender, block) and receiver == vault_address:
94
+ for _event in transfer_events:
95
+ _sender, _receiver, _value = _event.values()
96
+ if (
97
+ _event.address == vault_address
98
+ and _sender == ZERO_ADDRESS
99
+ and TreasuryWallet.check_membership(_receiver, block)
100
+ ):
101
+ # v1?
102
+ if event_pos < _event.pos:
103
+ return True
104
+ # v2
105
+ if _event.pos < event_pos:
106
+ return True
107
+ return False
108
+
109
+
110
+ _v3_deposit_keys: Final = "sender", "owner", "assets", "shares"
111
+
112
+
113
+ def is_v3_vault_deposit(tx: TreasuryTx) -> bool:
114
+ try:
115
+ if "Deposit" not in tx.events:
116
+ return False
117
+ except KeyError as e:
118
+ # This happens sometimes due to a busted abi, shouldnt impact us
119
+ if str(e) == "'components'":
120
+ return False
121
+ raise
122
+
123
+ if deposits := [
124
+ event for event in tx.events["Deposit"] if all(key in event for key in _v3_deposit_keys)
125
+ ]:
126
+ token = tx.token
127
+ to_address = tx.to_address
128
+ amount = tx.amount
129
+
130
+ # Vault side
131
+ if tx.from_address == ZERO_ADDRESS:
132
+ token_address = token.address.address
133
+ if deposits := [d for d in deposits if token_address == d.address]:
134
+ for deposit in deposits:
135
+ if to_address != deposit["owner"]:
136
+ print("wrong owner")
137
+ continue
138
+ # TODO: once postgres is in, remove the `round`
139
+ # elif amount == (scaled := token.scale_value(deposit["shares"])):
140
+ # return True
141
+ amount = round(amount, 8)
142
+ scaled = round(token.scale_value(deposit["shares"]), 8)
143
+ if amount == scaled:
144
+ return True
145
+ print(f"wrong amount: tx={amount} event={scaled}")
146
+ print("no matching vault-side deposit found")
147
+
148
+ # Token side
149
+ elif deposits := [d for d in deposits if to_address == d.address]:
150
+ from_address = tx.from_address.address # type: ignore [union-attr]
151
+ for deposit in deposits:
152
+ if from_address != deposit["sender"]:
153
+ print("sender doesnt match")
154
+ continue
155
+ # TODO: once postgres is in, remove the `round`
156
+ amount = round(amount, 8)
157
+ scaled = round(token.scale_value(deposit["assets"]), 8)
158
+ if amount == scaled:
159
+ return True
160
+ print(f"wrong amount: tx={amount} event={scaled}")
161
+ print("no matching token-side deposit found")
162
+ return False
163
+
164
+
165
+ @alru_cache(maxsize=None)
166
+ async def _get_underlying(vault: Contract) -> ChecksumAddress:
167
+ underlying = await YearnInspiredVault(vault, asynchronous=True).underlying
168
+ return underlying.address # type: ignore [return-value]
169
+
170
+
171
+ @vaults("Withdrawal")
172
+ async def is_vault_withdrawal(tx: TreasuryTx) -> bool:
173
+ to_address = tx.to_address.address # type: ignore [union-attr]
174
+ if to_address not in TREASURY_AND_ZERO:
175
+ return False
176
+
177
+ try:
178
+ if "Transfer" not in tx.events:
179
+ return False
180
+ except KeyError as e:
181
+ if str(e) == "'components'":
182
+ return False
183
+ raise
184
+
185
+ transfer_events = tx.events["Transfer"]
186
+
187
+ token = tx.token
188
+ token_address: ChecksumAddress = token.address.address # type: ignore [assignment]
189
+ block: BlockNumber = tx.block # type: ignore [assignment]
190
+
191
+ underlying: ChecksumAddress
192
+
193
+ # vault side
194
+ if any(token_address == vault.address for vault in all_vaults):
195
+ for event in transfer_events:
196
+ if token_address == event.address:
197
+ sender, receiver, value = event.values()
198
+ if (
199
+ to_address == ZERO_ADDRESS == receiver
200
+ and TreasuryWallet.check_membership(sender, block)
201
+ and tx.from_address == sender
202
+ ):
203
+ underlying = await _get_underlying(token_address)
204
+ for _event in transfer_events:
205
+ _sender, _receiver, _value = _event.values()
206
+ if (
207
+ _event.address == underlying
208
+ and tx.from_address == _receiver
209
+ and event.pos < _event.pos
210
+ and token_address == _sender
211
+ ):
212
+ return True
213
+ # token side
214
+ for vault in all_vaults:
215
+ if token_address == await _get_underlying(vault):
216
+ vault_address = vault.address
217
+ for event in transfer_events:
218
+ if token_address == event.address:
219
+ sender, receiver, value = event.values()
220
+ if tx.from_address == vault_address == sender and to_address == receiver:
221
+ for _event in transfer_events:
222
+ _sender, _receiver, _value = _event.values()
223
+ if (
224
+ _event.address == vault_address
225
+ and _receiver == ZERO_ADDRESS
226
+ and TreasuryWallet.check_membership(_sender, block)
227
+ and to_address == _sender
228
+ and _event.pos < event.pos
229
+ ):
230
+ return True
231
+ return False
232
+
233
+
234
+ @vaults("DOLA Fed Withdrawal")
235
+ def is_dolla_fed_withdrawal(tx: TreasuryTx) -> bool:
236
+ if tx.from_nickname == "Token: Curve DOLA Pool yVault - Unlisted" and TreasuryWallet.check_membership(tx.to_address.address, tx.block) and tx.symbol == "DOLA3POOL3CRV-f": # type: ignore [union-attr, arg-type]
237
+ return True
238
+ elif TreasuryWallet.check_membership(tx.from_address.address, tx.block) and tx.to_address == ZERO_ADDRESS and tx.symbol == "yvCurve-DOLA-U": # type: ignore [union-attr, arg-type]
239
+ return True
240
+ return False
241
+
242
+
243
+ @vaults("DOLA FRAX Vault Withdrawal")
244
+ def is_dola_frax_withdrawal(tx: TreasuryTx) -> bool:
245
+ symbol = tx.symbol
246
+ from_nickname = tx.from_nickname
247
+ to_nickname = tx.to_nickname
248
+ if (
249
+ symbol == "yvCurve-DOLA-FRAXBP-U"
250
+ and from_nickname == "Yearn yChad Multisig"
251
+ and to_nickname == "Zero Address"
252
+ ):
253
+ return True
254
+ elif (
255
+ symbol == "DOLAFRAXBP3CRV-f"
256
+ and from_nickname == "Token: Curve DOLA-FRAXBP Pool yVault - Unlisted"
257
+ and to_nickname == "Yearn yChad Multisig"
258
+ ):
259
+ return True
260
+ return (
261
+ tx.hash == "0x59a3a3b9e724835958eab6d0956a3acf697191182c41403c96d39976047d7240"
262
+ and tx.log_index == 232
263
+ )
@@ -0,0 +1,80 @@
1
+ from decimal import Decimal
2
+ from typing import Final
3
+
4
+ from brownie import ZERO_ADDRESS
5
+ from dao_treasury import TreasuryTx
6
+ from y import Network
7
+
8
+ from yearn_treasury.constants import YFI
9
+ from yearn_treasury.rules.ignore.swaps import swaps
10
+
11
+
12
+ WOOFY: Final = "0xD0660cD418a64a1d44E9214ad8e459324D8157f1"
13
+
14
+ YFI_SCALE: Final = Decimal(10**18)
15
+ WOOFY_SCALE: Final = Decimal(10**12)
16
+
17
+
18
+ @swaps("WOOFY", Network.Mainnet)
19
+ def is_woofy(tx: TreasuryTx) -> bool:
20
+ """
21
+ Returns True if the tx involved wrapping or unwrapping WOOFY.
22
+
23
+ https://docs.yearn.fi/resources/deprecated/woofy
24
+ """
25
+
26
+ # Wrapping, YFI side
27
+ if tx.to_address == WOOFY and tx.symbol == "YFI":
28
+ # Check for WOOFY transfer
29
+ for transfer in tx.get_events("Transfer"):
30
+ if transfer.address != WOOFY:
31
+ continue
32
+ sender, receiver, amount = transfer.values()
33
+ if sender == ZERO_ADDRESS and tx.from_address == receiver:
34
+ scaled = Decimal(amount) / YFI_SCALE
35
+ if scaled == tx.amount:
36
+ return True
37
+ print(f"woofy wrapping woofy side amount no match: [{scaled}, {tx.amount}]")
38
+
39
+ # Wrapping, WOOFY side
40
+ elif tx.from_address == ZERO_ADDRESS and tx.symbol == "WOOFY":
41
+ # Check for YFI transfer
42
+ for transfer in tx.get_events("Transfer"):
43
+ if transfer.address != YFI:
44
+ continue
45
+ sender, receiver, amount = transfer.values()
46
+ if receiver == WOOFY and tx.to_address == sender:
47
+ scaled = Decimal(amount) / WOOFY_SCALE
48
+ if scaled == tx.amount:
49
+ return True
50
+ print(f"woofy wrapping yfi side amount no match: [{scaled}, {tx.amount}]")
51
+
52
+ # Unwrapping, YFI side
53
+ elif tx.from_address == WOOFY and tx.symbol == "YFI":
54
+ # Check for WOOFY transfer
55
+ for transfer in tx.get_events("Transfer"):
56
+ if transfer.address != WOOFY:
57
+ continue
58
+ sender, receiver, amount = transfer.values()
59
+ if tx.to_address == sender and receiver == ZERO_ADDRESS:
60
+ scaled = round(Decimal(amount) / YFI_SCALE, 12)
61
+ rounded = round(tx.amount, 12)
62
+ if scaled == rounded:
63
+ return True
64
+ print(f"woofy unwrapping yfi side amount no match: [{scaled}, {rounded}]")
65
+
66
+ # Unwrapping, WOOFY side
67
+ elif tx.to_address == ZERO_ADDRESS and tx.symbol == "WOOFY":
68
+ # Check for YFI transfer
69
+ for transfer in tx.get_events("Transfer"):
70
+ if transfer.address != YFI:
71
+ continue
72
+ sender, receiver, amount = transfer.values()
73
+ if sender == WOOFY and tx.from_address == receiver:
74
+ # TODO remove this rounding once sqlite is replaced with postgres
75
+ scaled = round(Decimal(amount) / WOOFY_SCALE, 7)
76
+ rounded = round(tx.amount, 7)
77
+ if scaled == rounded:
78
+ return True
79
+ print(f"woofy unwrapping woofy side amount no match: [{scaled}, {rounded}]")
80
+ return False