yearn-treasury 0.1.7__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.
Files changed (128) hide show
  1. yearn_treasury/__init__.py +19 -0
  2. yearn_treasury/_db.cp310-win32.pyd +0 -0
  3. yearn_treasury/_db.py +50 -0
  4. yearn_treasury/_ens.cp310-win32.pyd +0 -0
  5. yearn_treasury/_ens.py +30 -0
  6. yearn_treasury/_logging.cp310-win32.pyd +0 -0
  7. yearn_treasury/_logging.py +41 -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 +43 -0
  13. yearn_treasury/budget/_requests.cp310-win32.pyd +0 -0
  14. yearn_treasury/budget/_requests.py +96 -0
  15. yearn_treasury/constants.py +91 -0
  16. yearn_treasury/main.py +175 -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 +15 -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 +64 -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 +46 -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 +83 -0
  35. yearn_treasury/rules/expense/security.cp310-win32.pyd +0 -0
  36. yearn_treasury/rules/expense/security.py +146 -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 +91 -0
  41. yearn_treasury/rules/ignore/passthru.py +309 -0
  42. yearn_treasury/rules/ignore/staking.py +101 -0
  43. yearn_treasury/rules/ignore/swaps/__init__.py +24 -0
  44. yearn_treasury/rules/ignore/swaps/_skip_tokens.py +8 -0
  45. yearn_treasury/rules/ignore/swaps/aave.py +68 -0
  46. yearn_treasury/rules/ignore/swaps/auctions.cp310-win32.pyd +0 -0
  47. yearn_treasury/rules/ignore/swaps/auctions.py +30 -0
  48. yearn_treasury/rules/ignore/swaps/compound.py +57 -0
  49. yearn_treasury/rules/ignore/swaps/conversion_factory.cp310-win32.pyd +0 -0
  50. yearn_treasury/rules/ignore/swaps/conversion_factory.py +20 -0
  51. yearn_treasury/rules/ignore/swaps/cowswap.py +86 -0
  52. yearn_treasury/rules/ignore/swaps/curve.py +174 -0
  53. yearn_treasury/rules/ignore/swaps/gearbox.cp310-win32.pyd +0 -0
  54. yearn_treasury/rules/ignore/swaps/gearbox.py +36 -0
  55. yearn_treasury/rules/ignore/swaps/iearn.cp310-win32.pyd +0 -0
  56. yearn_treasury/rules/ignore/swaps/iearn.py +41 -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 +293 -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 +79 -0
  70. yearn_treasury/rules/ignore/swaps/yfi.cp310-win32.pyd +0 -0
  71. yearn_treasury/rules/ignore/swaps/yfi.py +110 -0
  72. yearn_treasury/rules/ignore/swaps/yla.cp310-win32.pyd +0 -0
  73. yearn_treasury/rules/ignore/swaps/yla.py +27 -0
  74. yearn_treasury/rules/ignore/unit.cp310-win32.pyd +0 -0
  75. yearn_treasury/rules/ignore/unit.py +39 -0
  76. yearn_treasury/rules/ignore/weth.cp310-win32.pyd +0 -0
  77. yearn_treasury/rules/ignore/weth.py +47 -0
  78. yearn_treasury/rules/ignore/ygov.cp310-win32.pyd +0 -0
  79. yearn_treasury/rules/ignore/ygov.py +15 -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 +80 -0
  86. yearn_treasury/rules/other_expense/donations.cp310-win32.pyd +0 -0
  87. yearn_treasury/rules/other_expense/donations.py +42 -0
  88. yearn_treasury/rules/other_expense/dyfi.cp310-win32.pyd +0 -0
  89. yearn_treasury/rules/other_expense/dyfi.py +28 -0
  90. yearn_treasury/rules/other_expense/events.cp310-win32.pyd +0 -0
  91. yearn_treasury/rules/other_expense/events.py +20 -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 +48 -0
  96. yearn_treasury/rules/other_expense/revshare.cp310-win32.pyd +0 -0
  97. yearn_treasury/rules/other_expense/revshare.py +19 -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 +29 -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 +78 -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 +25 -0
  108. yearn_treasury/rules/revenue/farming.cp310-win32.pyd +0 -0
  109. yearn_treasury/rules/revenue/farming.py +55 -0
  110. yearn_treasury/rules/revenue/keepcoins.cp310-win32.pyd +0 -0
  111. yearn_treasury/rules/revenue/keepcoins.py +61 -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 +171 -0
  116. yearn_treasury/rules/revenue/yteams.cp310-win32.pyd +0 -0
  117. yearn_treasury/rules/revenue/yteams.py +16 -0
  118. yearn_treasury/shitcoins.py +143 -0
  119. yearn_treasury/vaults.cp310-win32.pyd +0 -0
  120. yearn_treasury/vaults.py +49 -0
  121. yearn_treasury/wallets.yaml +54 -0
  122. yearn_treasury/yteams.py +207 -0
  123. yearn_treasury-0.1.7.dist-info/METADATA +85 -0
  124. yearn_treasury-0.1.7.dist-info/RECORD +128 -0
  125. yearn_treasury-0.1.7.dist-info/WHEEL +5 -0
  126. yearn_treasury-0.1.7.dist-info/entry_points.txt +2 -0
  127. yearn_treasury-0.1.7.dist-info/top_level.txt +2 -0
  128. yearn_treasury__mypyc.cp310-win32.pyd +0 -0
@@ -0,0 +1,309 @@
1
+ from decimal import Decimal
2
+ from typing import Final
3
+
4
+ from dao_treasury import TreasuryTx, TreasuryWallet, ignore
5
+ from eth_typing import BlockNumber, ChecksumAddress
6
+ from y import Network
7
+
8
+ from yearn_treasury.constants import CHAINID, YSWAP_MULTISIG, ZERO_ADDRESS
9
+
10
+ passthru: Final = ignore("Pass-Thru")
11
+
12
+ cowswap_router: Final = "0x9008D19f58AAbD9eD0D60971565AA8510560ab41"
13
+ ycrv: Final = "0xFCc5c47bE19d06BF83eB04298b026F81069ff65b"
14
+
15
+
16
+ @passthru("Sent to dinobots to dump", Network.Mainnet)
17
+ def is_sent_to_dinoswap(tx: TreasuryTx) -> bool:
18
+ """These tokens are dumpped and the proceeds sent back to the origin strategy."""
19
+ return tx.from_nickname == "Contract: Strategy" and tx.to_nickname == "yMechs Multisig"
20
+
21
+
22
+ @passthru("Bribes for yCRV", Network.Mainnet)
23
+ def is_ycrv(tx: TreasuryTx) -> bool:
24
+ """These are routed thru cowswap with dai as the purchase token."""
25
+ ymechs = "0x2C01B4AD51a67E2d8F02208F54dF9aC4c0B778B6"
26
+
27
+ from_address = tx.from_address
28
+ symbol = tx.symbol
29
+ if (from_address == YSWAP_MULTISIG and symbol == "DAI") or (
30
+ from_address == ymechs and symbol == "3Crv"
31
+ ):
32
+ if tx.to_address == cowswap_router:
33
+ for trade in tx.get_events("Trade"):
34
+ (
35
+ owner,
36
+ sell_token,
37
+ buy_token,
38
+ sell_amount,
39
+ buy_amount,
40
+ fee_amount,
41
+ order_uid,
42
+ ) = trade.values()
43
+ if tx.from_address == owner and tx.token == sell_token and buy_token == ycrv:
44
+ scaled = Decimal(sell_amount) / 10**18
45
+ # TODO: remove this rounding when we implement postgres
46
+ if round(scaled, 11) == round(tx.amount, 11):
47
+ return True
48
+ print(f"bribes for ycrv amount no match: [{scaled}, {tx.amount}]")
49
+
50
+ elif tx.hash in {
51
+ # one off exception case to correct accounting mix-up
52
+ "0x1578f3b0d3158d305167c39dc29ada08914e1ddb67ef9698e1b0421432f9aed6",
53
+ # A few donations from ySwap
54
+ "0xb2e335500b33b42edd8a97f57db35e0561df9a3a811d0cd73dce9767c23da0c4",
55
+ "0xc02aab3a84b3bbfbc18f0ee6aa742f233d97511f653b4a40e7cd8f822851e10a",
56
+ "0x8a2dba62eac44fdfc7ff189016ac601c9da664f5dea42d647f2e552319db2f7d",
57
+ "0xd2c0a137d03811c5e4c27be19c7893f7fdd5851bdd6f825ee7301f3634033035",
58
+ }:
59
+ return True
60
+ return is_dola_bribe(tx)
61
+
62
+
63
+ def is_dola_bribe(tx: TreasuryTx) -> bool:
64
+ return (
65
+ tx.from_nickname == "ySwap Multisig"
66
+ and tx.to_nickname == "Contract: GPv2Settlement"
67
+ and tx.symbol == "DOLA"
68
+ )
69
+
70
+
71
+ passthru("BAL Rewards", Network.Mainnet).match(
72
+ hash="0xf4677cce1a08ecd54272cdc1b23bc64693450f8bb5d6de59b8e58e288ec3b2a7",
73
+ symbol="BAL",
74
+ )
75
+
76
+
77
+ @passthru("StrategyAuraUSDClonable", Network.Mainnet)
78
+ def is_aura(tx: TreasuryTx) -> bool:
79
+ txhash = tx.hash
80
+ if (
81
+ txhash == "0x1621ba5c9b57930c97cc43d5d6d401ee9c69fed435b0b458ee031544a10bfa75"
82
+ and tx.symbol in ["BAL", "AURA"]
83
+ ):
84
+ return True
85
+ return (
86
+ txhash == "0x996b5911a48319133f50f72904e70ed905c08c81e2c03770e0ccc896be873bd4"
87
+ and tx.symbol == "AURA"
88
+ )
89
+
90
+
91
+ @passthru("yPrisma Strategy Migration", Network.Mainnet)
92
+ def is_yprisma_migration(tx: TreasuryTx) -> bool:
93
+ # strategies were changed a few times
94
+ txhash = tx.hash
95
+ if txhash in (
96
+ "0x4c19259ff9e23c2f23653b7560526c2dbd5adef2d53c297b63d8c1fa6f4906f1",
97
+ "0xed39b66c01e25b053117778c80e544c985d962522233b49ce6f7fe136b1a4474",
98
+ ):
99
+ return True
100
+ return (
101
+ txhash == "0x45bb5d7c25393c5bb8ad9647ae60ff39ddc39d695f0e427eb45f91b04f42c636"
102
+ and tx.symbol == "yPRISMA"
103
+ )
104
+
105
+
106
+ @passthru("rKP3R", Network.Mainnet)
107
+ def is_rkp3r(tx: TreasuryTx) -> bool:
108
+ if tx.symbol == "rKP3R":
109
+ from_nickname = tx.from_nickname
110
+ to_nickname = tx.to_nickname
111
+ if (
112
+ from_nickname == "Contract: StrategyConvexFixedForexClonable"
113
+ and to_nickname == "Yearn yChad Multisig"
114
+ ):
115
+ return True
116
+ elif (
117
+ from_nickname == "Yearn yChad Multisig"
118
+ and to_nickname == "Contract: StrategyConvexFixedForexClonable"
119
+ ):
120
+ return True
121
+ return False
122
+
123
+
124
+ @passthru("Inverse-earned YearnFed Fees", Network.Mainnet)
125
+ def is_inverse_fees_from_yearn_fed(tx: TreasuryTx) -> bool:
126
+ return tx.symbol == "yvDOLA-U" and tx.to_nickname == "Contract: YearnFed"
127
+
128
+
129
+ @passthru("stkAAVE", Network.Mainnet)
130
+ def is_stkaave(tx: TreasuryTx) -> bool:
131
+ """stkAAVE is sent from a strategy to ychad, then to sms for unwrapping."""
132
+ if tx.symbol == "stkAAVE":
133
+ from_nickname = tx.from_nickname
134
+ to_nickname = tx.to_nickname
135
+ if "Strategy" in from_nickname and to_nickname == "Yearn yChad Multisig":
136
+ return True
137
+ elif from_nickname == "Yearn yChad Multisig" and to_nickname == "Yearn Strategist Multisig":
138
+ return True
139
+ return False
140
+
141
+
142
+ @passthru("StrategyIdle", Network.Mainnet)
143
+ def is_idle(tx: TreasuryTx) -> bool:
144
+ return tx.hash in {
145
+ "0x59595773ee4304ba4e7e06d2c02541781d93867f74c6c83056e7295b684036c7",
146
+ "0x4c7685aa3dfa9f375c612a2773951b9edbe059102b505423ed28a97d2692e75a",
147
+ "0xb17317686b57229aeb7f06103097b47dc2eafa34489c40af70d2ac57bcf8f455",
148
+ "0xfd9e6fd303fdbb358207bf3ba069b7f6a21f82f6b082605056d54948127e81e8",
149
+ "0x41c8428fd361c54bb80cdac752e31622915ac626dd1e9270f02af1dc2c84d1f9",
150
+ "0x9c0d169c7362a7fe436ae852c1aee58a5905d10569abbd50261f65cb0574dc3a",
151
+ "0x55d89a5890cfe80da06f6831fdfa3a366c0ed9cf9b7f1b4d53f5007bb9698fa0",
152
+ "0x6c6fc43dca1841af82b517bc5fc53ea8607e3f95512e4dd3009c0dbb425669f7",
153
+ }
154
+
155
+
156
+ _factory_strat_to_yield_tokens = {
157
+ "Contract: StrategyCurveBoostedFactoryClonable": ("CRV", "LDO"),
158
+ "Contract: StrategyConvexFactoryClonable": ("CRV", "CVX"),
159
+ "Contract: StrategyConvexFraxFactoryClonable": ("CRV", "CVX", "FXS"),
160
+ }
161
+
162
+
163
+ @passthru("Factory Vault Yield", Network.Mainnet)
164
+ def is_factory_vault_yield(tx: TreasuryTx) -> bool:
165
+ return tx.to_nickname == "yMechs Multisig" and tx.symbol in _factory_strat_to_yield_tokens.get(
166
+ tx.from_nickname, ()
167
+ )
168
+
169
+
170
+ @passthru("CowSwap Migration", Network.Mainnet)
171
+ def is_cowswap_migration(tx: TreasuryTx) -> bool:
172
+ """A one-time tx that transferred tokens from an old contract to its replacement."""
173
+ return tx.hash == "0xb50341d3db2ff4a39b9bfa21753893035554ae44abb7d104ab650753db1c4855"
174
+
175
+
176
+ def is_curve_bribe(tx: TreasuryTx) -> bool:
177
+ """All present and future curve bribes are committed to yveCRV holders."""
178
+ from_nickname = tx.from_nickname
179
+ if from_nickname == "Contract: CurveYCRVVoter" and tx.hash not in [
180
+ # took place before bribes were committed to yveCRV
181
+ "0x6824345c8a0c1f0b801d8050bb6f587032c4a9fa153faa113d723a2068d844f4",
182
+ # was a whitehat hack of the v1 bribe contract, necessary to safeguard user funds
183
+ "0xfcef3911809770fe351b2b526e4ee0274589a3f7d6ef9408a8f5643fa006b771",
184
+ ]:
185
+ return True
186
+
187
+ # Bribe V3
188
+ elif from_nickname == "Contract: yBribe" and tx.to_nickname in [
189
+ "Yearn Treasury",
190
+ "ySwap Multisig",
191
+ ]:
192
+ return True
193
+
194
+ # NOTE: I added this one-off to capture tokens sent to BribeSplitter 0x527e80008D212E2891C737Ba8a2768a7337D7Fd2
195
+ return tx.hash == "0xce45da7e3a7616ed0c0d356d6dfa8a784606c9a8034bae9faa40abf7b52be114"
196
+
197
+
198
+ _pass_thru_hashes: tuple[str, ...] = {
199
+ Network.Mainnet: ("0xf662c68817c56a64b801181a3175c8a7e7a5add45f8242990c695d418651e50d",),
200
+ Network.Fantom: (
201
+ "0x411d0aff42c3862d06a0b04b5ffd91f4593a9a8b2685d554fe1fbe5dc7e4fc04",
202
+ "0xa347da365286cc912e4590fc71e97a5bcba9e258c98a301f85918826538aa021",
203
+ ),
204
+ }.get(
205
+ CHAINID, ()
206
+ ) # type: ignore [call-overload]
207
+
208
+
209
+ @passthru("Misc.", Network.Mainnet)
210
+ def is_misc_passthru_mainnet(tx: TreasuryTx) -> bool:
211
+ # not sure if we still need this, TODO figure it out
212
+ # NOTE skipped the hashmatcher to do strange things ... there is probably a better way to do this
213
+ if tx.hash in _pass_thru_hashes and str(tx.log_index).lower() != "nan":
214
+ return True
215
+
216
+ txhash = tx.hash
217
+ if txhash in {
218
+ "0xae6797ad466de75731117df46ccea5c263265dd6258d596b9d6d8cf3a7b1e3c2",
219
+ "0x2a6191ba8426d3ae77e2a6c91de10a6e76d1abdb2d0f831c6c5aad52be3d6246",
220
+ # https://github.com/yearn/chief-multisig-officer/pull/924
221
+ "0x25b54e113e58a3a4bbffc011cdfcb8c07a0424f33b0dbda921803d82b88f1429",
222
+ "0xcb000dd2b623f9924fe0234831800950a3269b2d412ce9eeabb0ec65cd737059",
223
+ }:
224
+ return True
225
+ # do these need hueristics? build real sort logic if these keep reoccurring
226
+ elif txhash == "0x14faeac8ee0734875611e68ce0614eaf39db94a5ffb5bc6f9739da6daf58282a" and (
227
+ tx.symbol in ("CRV", "CVX", "yPRISMA") or tx.log_index == 254
228
+ ):
229
+ return True
230
+ return False
231
+
232
+
233
+ @passthru("Misc.", Network.Fantom)
234
+ def is_misc_passthru_fantom(tx: TreasuryTx) -> bool:
235
+ # not sure if we still need this, TODO figure it out
236
+ # NOTE skipped the hashmatcher to do strange things ... there is probably a better way to do this
237
+ if tx.hash in _pass_thru_hashes and str(tx.log_index).lower() != "nan":
238
+ return True
239
+
240
+ if tx.hash == "0x14faeac8ee0734875611e68ce0614eaf39db94a5ffb5bc6f9739da6daf58282a":
241
+ return True
242
+ # Passing thru to yvWFTM
243
+ if (
244
+ tx.symbol == "WFTM"
245
+ and TreasuryWallet.check_membership(tx.from_address.address, tx.block) # type: ignore [arg-type, union-attr]
246
+ and tx.to_address == "0x0DEC85e74A92c52b7F708c4B10207D9560CEFaf0"
247
+ ):
248
+ # dont want to accidenally sort a vault deposit here
249
+ is_deposit = False
250
+ for event in tx.get_events("Transfer"):
251
+ sender, receiver, _ = event.values()
252
+ if (
253
+ tx.to_address == event.address
254
+ and sender == ZERO_ADDRESS
255
+ and tx.from_address == receiver
256
+ ):
257
+ is_deposit = True
258
+ if not is_deposit:
259
+ return True
260
+ return False
261
+
262
+
263
+ @passthru("yvBoost INCOMPLETE", Network.Mainnet)
264
+ def is_buying_yvboost(tx: TreasuryTx) -> bool:
265
+ """Bought back yvBoost is unwrapped and sent back to vault holders."""
266
+ symbol = tx.symbol
267
+ block: BlockNumber = tx.block # type: ignore [assignment]
268
+ from_address: ChecksumAddress = tx.from_address.address # type: ignore [union-attr, assignment]
269
+ to_address: ChecksumAddress = tx.to_address.address # type: ignore [union-attr, assignment]
270
+ if (
271
+ symbol == "SPELL"
272
+ and TreasuryWallet.check_membership(from_address, block)
273
+ and to_address == cowswap_router
274
+ ):
275
+ return True
276
+
277
+ elif (
278
+ symbol == "yveCRV-DAO"
279
+ and TreasuryWallet.check_membership(from_address, block)
280
+ and to_address
281
+ in (
282
+ "0xd7240B32d24B814fE52946cD44d94a2e3532E63d",
283
+ "0x7fe508eE30316e3261079e2C81f4451E0445103b",
284
+ )
285
+ ):
286
+ return True
287
+
288
+ elif (
289
+ symbol == "3Crv"
290
+ and from_address == "0xd7240B32d24B814fE52946cD44d94a2e3532E63d"
291
+ and TreasuryWallet.check_membership(to_address, block)
292
+ ):
293
+ return True
294
+
295
+ # SPELL bribe handling
296
+ elif symbol == "SPELL":
297
+ if tx.to_nickname in ("Abracadabra Treasury", "Contract: BribeSplitter"):
298
+ return True
299
+
300
+ return tx in (
301
+ "0x9eabdf110efbfb44aab7a50eb4fe187f68deae7c8f28d78753c355029f2658d3",
302
+ "0x5a80f5ff90fc6f4f4597290b2432adbb62ab4154ead68b515accdf19b01c1086",
303
+ "0x848b4d629e137ad8d8eefe5db40eab895c9959b9c210d0ae0fef16a04bfaaee1",
304
+ "0x896663aa9e2633b5d152028bdf84d7f4b1137dd27a8e61daca3863db16bebc4f",
305
+ "0xd8aa1e5d093a89515530b7267a9fd216b97fddb6478b3027b2f5c1d53070cd5f",
306
+ "0x169aab84b408fce76e0b776ebf412c796240300c5610f0263d5c09d0d3f1b062",
307
+ "0xe6fefbf061f4489cd967cdff6aa8aca616f0c709e08c3696f12b0027e9e166c9",
308
+ "0x10be8a3345660f3c51b695e8716f758b1a91628bd612093784f0516a604f79c1",
309
+ )
@@ -0,0 +1,101 @@
1
+ from typing import Final
2
+
3
+ from dao_treasury import TreasuryTx, TreasuryWallet, ignore
4
+ from y import Network
5
+
6
+ from yearn_treasury.rules.constants import ZERO_ADDRESS
7
+
8
+ staking: Final = ignore("Staking")
9
+
10
+
11
+ @staking("Curve Gauges", Network.Mainnet)
12
+ def is_curve_gauge(tx: TreasuryTx) -> bool:
13
+ """Ignore DAO staking into Curve gauge contracts on Mainnet.
14
+
15
+ These deposit transactions into Curve gauges are infrequent and are excluded from automatic categorization.
16
+ """
17
+ return tx.hash in (
18
+ "0xfb9fbe6e6c1d6e3dbeae81f80f0ff7729c556b08afb6ce1fa8ab04d3ecb56788",
19
+ "0x832eb508906baf2c00dfec7a2d3f7b856fdee683921a5fff206cf6b0c997cb32",
20
+ )
21
+
22
+
23
+ @staking("Solidex", Network.Fantom)
24
+ async def is_solidex_staking(tx: TreasuryTx) -> bool:
25
+ """Ignore Solidex staking and unstaking lifecycle transactions on Fantom.
26
+
27
+ This rule matches each stage of the Solidex LP flow:
28
+ - Stake deposits: DAO wallet deposits tokens into the LP depositor contract (`Deposited` event).
29
+ - Reward claims: Claim tokens minted to DAO (`Deposited` event with ZERO_ADDRESS as sender).
30
+ - Claim token burns: DAO wallet burns claim tokens to redeem rewards (`Withdrawn` event).
31
+ - Unstake withdrawals: DAO wallet receives original LP tokens back (`Withdrawn` event).
32
+ """
33
+ # Solidex Finance: LP Depositor
34
+ lp_depositor = "0x26E1A0d851CF28E697870e1b7F053B605C8b060F"
35
+
36
+ # STAKING
37
+ # Step 1: Stake your tokens
38
+ if (
39
+ TreasuryWallet._get_instance(tx.from_address.address) # type: ignore [union-attr, arg-type]
40
+ and tx.to_address == lp_depositor
41
+ ):
42
+ for event in tx.get_events("Deposited"):
43
+ if (
44
+ event.address == lp_depositor
45
+ and "user" in event
46
+ and "pool" in event
47
+ and tx.from_address == event["user"]
48
+ and tx.token == event["pool"]
49
+ ):
50
+ return True
51
+
52
+ # CLAIMING
53
+ # Step 2: Get your claim tokens
54
+ elif tx.from_address == ZERO_ADDRESS and TreasuryWallet._get_instance(
55
+ tx.to_address.address # type: ignore [union-attr, arg-type]
56
+ ):
57
+ for event in tx.get_events("Deposited"):
58
+ pool = await tx.token.contract.pool
59
+ if (
60
+ event.address == lp_depositor
61
+ and "user" in event
62
+ and "pool" in event
63
+ and tx.to_address == event["user"]
64
+ and event["pool"] == pool
65
+ ):
66
+ return True
67
+
68
+ # UNSTAKING
69
+ # Step 3: Burn your claim tokens
70
+ elif (
71
+ TreasuryWallet._get_instance(tx.from_address.address) # type: ignore [union-attr, arg-type]
72
+ and tx.to_address == ZERO_ADDRESS
73
+ ):
74
+ token = tx.token.contract
75
+ if hasattr(token, "pool"):
76
+ pool = await token.pool
77
+ for event in tx.get_events("Withdrawn"):
78
+ if (
79
+ event.address == lp_depositor
80
+ and "user" in event
81
+ and "pool" in event
82
+ and tx.from_address == event["user"]
83
+ and event["pool"] == pool
84
+ ):
85
+ return True
86
+
87
+ # Step 4: Unstake your tokens
88
+ elif tx.from_address == lp_depositor and TreasuryWallet._get_instance(
89
+ tx.to_address.address # type: ignore [union-attr, arg-type]
90
+ ):
91
+ for event in tx.get_events("Withdrawn"):
92
+ if (
93
+ event.address == lp_depositor
94
+ and "user" in event
95
+ and "pool" in event
96
+ and tx.to_address == event["user"]
97
+ and tx.token == event["pool"]
98
+ ):
99
+ return True
100
+
101
+ return False
@@ -0,0 +1,24 @@
1
+ from typing import Final
2
+
3
+ from dao_treasury import IgnoreSortRule, SortRuleFactory, ignore
4
+
5
+ swaps: Final[SortRuleFactory[IgnoreSortRule]] = ignore("Swaps")
6
+
7
+
8
+ from yearn_treasury.rules.ignore.swaps.aave import *
9
+ from yearn_treasury.rules.ignore.swaps.auctions import *
10
+ from yearn_treasury.rules.ignore.swaps.compound import *
11
+ from yearn_treasury.rules.ignore.swaps.conversion_factory import *
12
+ from yearn_treasury.rules.ignore.swaps.cowswap import *
13
+ from yearn_treasury.rules.ignore.swaps.curve import *
14
+ from yearn_treasury.rules.ignore.swaps.gearbox import *
15
+ from yearn_treasury.rules.ignore.swaps.iearn import *
16
+ from yearn_treasury.rules.ignore.swaps.otc import *
17
+ from yearn_treasury.rules.ignore.swaps.pooltogether import *
18
+ from yearn_treasury.rules.ignore.swaps.synthetix import *
19
+ from yearn_treasury.rules.ignore.swaps.uniswap import *
20
+ from yearn_treasury.rules.ignore.swaps.unwrapper import *
21
+ from yearn_treasury.rules.ignore.swaps.vaults import *
22
+ from yearn_treasury.rules.ignore.swaps.woofy import *
23
+ from yearn_treasury.rules.ignore.swaps.yfi import *
24
+ from yearn_treasury.rules.ignore.swaps.yla import *
@@ -0,0 +1,8 @@
1
+ from typing import Final
2
+
3
+ from ...constants import YFI
4
+
5
+ SKIP_TOKENS: Final = (
6
+ YFI,
7
+ "0x9d409a0A012CFbA9B15F6D4B36Ac57A46966Ab9a", # yvboost
8
+ )
@@ -0,0 +1,68 @@
1
+ """
2
+ Ignore rules for Aave-related transactions.
3
+
4
+ This module defines rules for identifying and ignoring Aave-related
5
+ transactions in the Yearn Treasury system. It provides matching logic
6
+ for deposit and withdrawal events, so these transactions can be
7
+ ignored in analytics and reporting.
8
+ """
9
+
10
+ from typing import Final
11
+
12
+ from dao_treasury import TreasuryTx, TreasuryWallet
13
+ from eth_typing import ChecksumAddress
14
+
15
+ from yearn_treasury.rules.constants import ZERO_ADDRESS
16
+ from yearn_treasury.rules.ignore.swaps import swaps
17
+
18
+ aave: Final = swaps("Aave")
19
+
20
+
21
+ @aave("Deposit")
22
+ def is_aave_deposit(tx: TreasuryTx) -> bool:
23
+ # Atoken side
24
+
25
+ # Underlying side
26
+ # TODO we didnt need this historically??
27
+ return False
28
+
29
+
30
+ @aave("Withdrawal")
31
+ async def is_aave_withdrawal(tx: TreasuryTx) -> bool:
32
+ from_address: ChecksumAddress = tx.from_address.address # type: ignore [union-attr, assignment]
33
+ to_address: ChecksumAddress = tx.to_address.address # type: ignore [union-attr, assignment]
34
+ # Atoken side
35
+ if (
36
+ TreasuryWallet.check_membership(from_address, tx.block) # type: ignore [union-attr, arg-type]
37
+ and to_address == ZERO_ADDRESS
38
+ ):
39
+ token = tx.token
40
+ if hasattr(token.contract, "underlyingAssetAddress"):
41
+ for event in await tx.get_events("RedeemUnderlying", sync=False):
42
+ if (
43
+ from_address == event["_user"]
44
+ and await token.contract.underlyingAssetAddress == event["_reserve"]
45
+ ):
46
+ # TODO get rid of this rounding when we migrate the db to postgres
47
+ event_amount = round(token.scale_value(event["_amount"]), 11)
48
+ if event_amount == round(tx.amount, 11):
49
+ return True
50
+ print(
51
+ f"Aave Withdrawal atoken side does not match: {round(tx.amount, 14)} {event_amount}"
52
+ )
53
+
54
+ # Underlying side
55
+ if TreasuryWallet.check_membership(tx.to_address.address, tx.block): # type: ignore [union-attr, arg-type]
56
+ token = tx.token
57
+ for event in await tx.get_events("RedeemUnderlying", sync=False):
58
+ if token == event["_reserve"] and to_address == event["_user"]:
59
+ # TODO get rid of this rounding when we migrate the db to postgres
60
+ event_amount = round(token.scale_value(event["_amount"]), 11)
61
+ if event_amount == round(tx.amount, 11):
62
+ return True
63
+ print(
64
+ f"Aave Withdrawal underlying side does not match: {round(tx.amount, 14)} {event_amount}"
65
+ )
66
+
67
+ # TODO: If these end up becoming more frequent, figure out sorting hueristics.
68
+ return tx.hash == "0x36ee5631859a15f57b44e41b8590023cf6f0c7b12d28ea760e9d8f8003f4fc50"
@@ -0,0 +1,30 @@
1
+ from typing import Final
2
+
3
+ from dao_treasury import TreasuryTx
4
+ from y import Network
5
+
6
+ from yearn_treasury.rules.ignore.swaps import swaps
7
+
8
+ auctions: Final = swaps("Auctions")
9
+
10
+ YEARNFI_DUTCH_AUCTIONS: Final = "0x861fE45742f70054917B65bE18904662bD0dBd30"
11
+
12
+
13
+ @auctions("Auction Proceeds", Network.Mainnet)
14
+ async def is_auction_proceeds(tx: TreasuryTx) -> bool:
15
+ # NOTE: the other side of these swaps is currently recorded under
16
+ # 'Ignore:Internal Transfer' when it goes to the Generic bucket contract
17
+ if tx.from_nickname != "Contract: GPv2Settlement":
18
+ return False
19
+
20
+ for trade in await tx.get_events("Trade", sync=False):
21
+ if trade["owner"] != YEARNFI_DUTCH_AUCTIONS or tx.token != trade["buyToken"]:
22
+ continue
23
+ buy_amount = tx.token.scale_value(trade["buyAmount"])
24
+ if round(buy_amount, 14) == round(tx.amount, 14):
25
+ return True
26
+ print(
27
+ f"auction proceeds amount does not match: {round(buy_amount, 14)} {round(tx.amount, 14)}"
28
+ )
29
+
30
+ return False
@@ -0,0 +1,57 @@
1
+ from typing import Final
2
+
3
+ from dao_treasury import TreasuryTx
4
+
5
+ from yearn_treasury.rules.ignore.swaps import swaps
6
+
7
+ compound: Final = swaps("Compound")
8
+
9
+
10
+ @compound("Deposit")
11
+ async def is_compound_deposit(tx: TreasuryTx) -> bool:
12
+ for event in await tx.get_events("Mint", sync=False):
13
+ if all(arg in event for arg in ("minter", "mintTokens", "mintAmount")):
14
+ minter = event["minter"]
15
+ minted = tx.token.scale_value(event["mintTokens"])
16
+ # cToken side
17
+ if tx.token == tx.from_address == event.address and tx.to_address == minter:
18
+ # TODO: get rid of this rounding when we migrate to postgres
19
+ if round(minted, 14) == round(tx.amount, 14):
20
+ return True
21
+ print(
22
+ f"Compound deposit ctoken side does not match: {round(minted, 14)} {round(tx.amount, 14)}"
23
+ )
24
+ # underlying side
25
+ elif tx.to_address == event.address and tx.from_address == minter:
26
+ # TODO: get rid of this rounding when we migrate to postgres
27
+ if round(minted, 14) == round(tx.amount, 14):
28
+ return True
29
+ print(
30
+ f"Compound deposit underlying side does not match: {round(minted, 14)} {round(tx.amount, 14)}"
31
+ )
32
+ return False
33
+
34
+
35
+ @compound("Withdrawal")
36
+ async def is_compound_withdrawal(tx: TreasuryTx) -> bool:
37
+ for event in await tx.get_events("Redeem", sync=False):
38
+ if all(arg in event for arg in ("redeemer", "redeemTokens", "redeemAmount")):
39
+ redeemer = event["redeemer"]
40
+ redeemed = tx.token.scale_value(event["redeemTokens"])
41
+ # cToken side
42
+ if tx.token == event.address and tx.from_address == redeemer:
43
+ # TODO: get rid of this rounding when we migrate to postgres
44
+ if round(redeemed, 7) == round(tx.amount, 7):
45
+ return True
46
+ print(
47
+ f"Compound withdrawal ctoken side does not match: {round(redeemed, 7)} {round(tx.amount, 7)}"
48
+ )
49
+ # underlying side
50
+ elif tx.to_address == redeemer and tx.from_address == event.address:
51
+ # TODO: get rid of this rounding when we migrate to postgres
52
+ if round(redeemed, 14) == round(tx.amount, 14):
53
+ return True
54
+ print(
55
+ f"Compound withdrawal underlying side does not match: {round(redeemed, 14)} {round(tx.amount, 14)}"
56
+ )
57
+ return False
@@ -0,0 +1,20 @@
1
+ from typing import Final
2
+
3
+ from dao_treasury import TreasuryTx
4
+
5
+ from yearn_treasury.rules.ignore.swaps import swaps
6
+
7
+ CONVERSION_FACTORY: Final = "0x8E6A115bd8e24d2D86A1AacCC56221e5Bd4577ba"
8
+ ROBOTREASURY: Final = "0xEf77cc176c748d291EfB6CdC982c5744fC7211c8"
9
+ GENERIC_BUCKET: Final = "0x278374fFb10B7D16E7633444c13e6E565EA57c28"
10
+ SOME_RELATED_NON_VERIFIED_CONTRACT: Final = "0x5CECc042b2A320937c04980148Fc2a4b66Da0fbF"
11
+
12
+
13
+ @swaps("Conversion Factory")
14
+ def is_conversion_factory(tx: TreasuryTx) -> bool:
15
+ # TODO: track the balances that are held by the conversion factory but not yet dumped
16
+ from_address = tx.from_address.address # type: ignore [union-attr]
17
+ to_address = tx.to_address.address # type: ignore [union-attr]
18
+ return (from_address == GENERIC_BUCKET and to_address == CONVERSION_FACTORY) or (
19
+ from_address == SOME_RELATED_NON_VERIFIED_CONTRACT and to_address == ROBOTREASURY
20
+ )