yearn-treasury 0.0.15__cp311-cp311-win_amd64.whl → 0.1.3__cp311-cp311-win_amd64.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 (108) hide show
  1. yearn_treasury/__init__.py +12 -0
  2. yearn_treasury/_db.cp311-win_amd64.pyd +0 -0
  3. yearn_treasury/_db.py +39 -10
  4. yearn_treasury/_ens.cp311-win_amd64.pyd +0 -0
  5. yearn_treasury/_ens.py +14 -0
  6. yearn_treasury/_logging.cp311-win_amd64.pyd +0 -0
  7. yearn_treasury/_logging.py +43 -0
  8. yearn_treasury/{addresses.yaml → address_labels.yaml} +10 -0
  9. yearn_treasury/budget/__init__.cp311-win_amd64.pyd +0 -0
  10. yearn_treasury/budget/__init__.py +2 -2
  11. yearn_treasury/budget/_request.cp311-win_amd64.pyd +0 -0
  12. yearn_treasury/budget/_request.py +8 -0
  13. yearn_treasury/budget/_requests.cp311-win_amd64.pyd +0 -0
  14. yearn_treasury/budget/_requests.py +45 -14
  15. yearn_treasury/constants.py +27 -7
  16. yearn_treasury/main.py +71 -29
  17. yearn_treasury/rules/__init__.py +14 -0
  18. yearn_treasury/rules/constants.cp311-win_amd64.pyd +0 -0
  19. yearn_treasury/rules/cost_of_revenue/gas.cp311-win_amd64.pyd +0 -0
  20. yearn_treasury/rules/cost_of_revenue/gas.py +9 -1
  21. yearn_treasury/rules/expense/__init__.cp311-win_amd64.pyd +0 -0
  22. yearn_treasury/rules/expense/general.cp311-win_amd64.pyd +0 -0
  23. yearn_treasury/rules/expense/infrastructure.cp311-win_amd64.pyd +0 -0
  24. yearn_treasury/rules/expense/infrastructure.py +7 -0
  25. yearn_treasury/rules/expense/people.cp311-win_amd64.pyd +0 -0
  26. yearn_treasury/rules/expense/people.py +43 -0
  27. yearn_treasury/rules/expense/security.cp311-win_amd64.pyd +0 -0
  28. yearn_treasury/rules/expense/security.py +17 -0
  29. yearn_treasury/rules/ignore/__init__.py +2 -0
  30. yearn_treasury/rules/ignore/general.cp311-win_amd64.pyd +0 -0
  31. yearn_treasury/rules/ignore/general.py +3 -2
  32. yearn_treasury/rules/ignore/maker.py +40 -28
  33. yearn_treasury/rules/ignore/passthru.py +310 -0
  34. yearn_treasury/rules/ignore/swaps/__init__.py +14 -0
  35. yearn_treasury/rules/ignore/swaps/aave.py +39 -18
  36. yearn_treasury/rules/ignore/swaps/auctions.cp311-win_amd64.pyd +0 -0
  37. yearn_treasury/rules/ignore/swaps/auctions.py +31 -0
  38. yearn_treasury/rules/ignore/swaps/compound.py +32 -22
  39. yearn_treasury/rules/ignore/swaps/conversion_factory.cp311-win_amd64.pyd +0 -0
  40. yearn_treasury/rules/ignore/swaps/conversion_factory.py +21 -0
  41. yearn_treasury/rules/ignore/swaps/cowswap.py +87 -0
  42. yearn_treasury/rules/ignore/swaps/curve.py +170 -0
  43. yearn_treasury/rules/ignore/swaps/gearbox.cp311-win_amd64.pyd +0 -0
  44. yearn_treasury/rules/ignore/swaps/gearbox.py +37 -0
  45. yearn_treasury/rules/ignore/swaps/iearn.cp311-win_amd64.pyd +0 -0
  46. yearn_treasury/rules/ignore/swaps/iearn.py +43 -0
  47. yearn_treasury/rules/ignore/swaps/otc.cp311-win_amd64.pyd +0 -0
  48. yearn_treasury/rules/ignore/swaps/otc.py +58 -0
  49. yearn_treasury/rules/ignore/swaps/pooltogether.cp311-win_amd64.pyd +0 -0
  50. yearn_treasury/rules/ignore/swaps/pooltogether.py +23 -0
  51. yearn_treasury/rules/ignore/swaps/synthetix.cp311-win_amd64.pyd +0 -0
  52. yearn_treasury/rules/ignore/swaps/synthetix.py +10 -0
  53. yearn_treasury/rules/ignore/swaps/uniswap.py +29 -6
  54. yearn_treasury/rules/ignore/swaps/unwrapper.cp311-win_amd64.pyd +0 -0
  55. yearn_treasury/rules/ignore/swaps/unwrapper.py +17 -0
  56. yearn_treasury/rules/ignore/swaps/vaults.cp311-win_amd64.pyd +0 -0
  57. yearn_treasury/rules/ignore/swaps/vaults.py +264 -0
  58. yearn_treasury/rules/ignore/swaps/woofy.cp311-win_amd64.pyd +0 -0
  59. yearn_treasury/rules/ignore/swaps/woofy.py +80 -0
  60. yearn_treasury/rules/ignore/swaps/yfi.cp311-win_amd64.pyd +0 -0
  61. yearn_treasury/rules/ignore/swaps/yfi.py +111 -0
  62. yearn_treasury/rules/ignore/swaps/yla.cp311-win_amd64.pyd +0 -0
  63. yearn_treasury/rules/ignore/swaps/yla.py +28 -0
  64. yearn_treasury/rules/ignore/unit.cp311-win_amd64.pyd +0 -0
  65. yearn_treasury/rules/ignore/unit.py +40 -0
  66. yearn_treasury/rules/ignore/weth.cp311-win_amd64.pyd +0 -0
  67. yearn_treasury/rules/ignore/weth.py +12 -4
  68. yearn_treasury/rules/ignore/ygov.cp311-win_amd64.pyd +0 -0
  69. yearn_treasury/rules/other_expense/__init__.cp311-win_amd64.pyd +0 -0
  70. yearn_treasury/rules/other_expense/__init__.py +1 -0
  71. yearn_treasury/rules/other_expense/boost.cp311-win_amd64.pyd +0 -0
  72. yearn_treasury/rules/other_expense/bugs.cp311-win_amd64.pyd +0 -0
  73. yearn_treasury/rules/other_expense/donations.cp311-win_amd64.pyd +0 -0
  74. yearn_treasury/rules/other_expense/donations.py +43 -0
  75. yearn_treasury/rules/other_expense/dyfi.cp311-win_amd64.pyd +0 -0
  76. yearn_treasury/rules/other_expense/events.cp311-win_amd64.pyd +0 -0
  77. yearn_treasury/rules/other_expense/events.py +6 -0
  78. yearn_treasury/rules/other_expense/match_on_to_address.yaml +3 -2
  79. yearn_treasury/rules/other_expense/misc.cp311-win_amd64.pyd +0 -0
  80. yearn_treasury/rules/other_expense/misc.py +22 -0
  81. yearn_treasury/rules/other_expense/revshare.cp311-win_amd64.pyd +0 -0
  82. yearn_treasury/rules/other_income/__init__.cp311-win_amd64.pyd +0 -0
  83. yearn_treasury/rules/other_income/__init__.py +2 -77
  84. yearn_treasury/rules/other_income/airdrops.cp311-win_amd64.pyd +0 -0
  85. yearn_treasury/rules/other_income/airdrops.py +30 -0
  86. yearn_treasury/rules/other_income/match_on_hash.yaml +2 -0
  87. yearn_treasury/rules/other_income/misc.cp311-win_amd64.pyd +0 -0
  88. yearn_treasury/rules/other_income/misc.py +80 -0
  89. yearn_treasury/rules/revenue/bribes.cp311-win_amd64.pyd +0 -0
  90. yearn_treasury/rules/revenue/farming.cp311-win_amd64.pyd +0 -0
  91. yearn_treasury/rules/revenue/keepcoins.cp311-win_amd64.pyd +0 -0
  92. yearn_treasury/rules/revenue/seasolver.cp311-win_amd64.pyd +0 -0
  93. yearn_treasury/rules/revenue/vaults.py +25 -22
  94. yearn_treasury/rules/revenue/yteams.cp311-win_amd64.pyd +0 -0
  95. yearn_treasury/shitcoins.py +93 -2
  96. yearn_treasury/vaults.cp311-win_amd64.pyd +0 -0
  97. yearn_treasury/vaults.py +17 -4
  98. yearn_treasury/wallets.yaml +54 -0
  99. yearn_treasury/yteams.py +208 -0
  100. {yearn_treasury-0.0.15.dist-info → yearn_treasury-0.1.3.dist-info}/METADATA +21 -8
  101. yearn_treasury-0.1.3.dist-info/RECORD +128 -0
  102. yearn_treasury-0.1.3.dist-info/top_level.txt +2 -0
  103. yearn_treasury__mypyc.cp311-win_amd64.pyd +0 -0
  104. 3ddd27aee2f79379b6d9__mypyc.cp311-win_amd64.pyd +0 -0
  105. yearn_treasury-0.0.15.dist-info/RECORD +0 -85
  106. yearn_treasury-0.0.15.dist-info/top_level.txt +0 -2
  107. {yearn_treasury-0.0.15.dist-info → yearn_treasury-0.1.3.dist-info}/WHEEL +0 -0
  108. {yearn_treasury-0.0.15.dist-info → yearn_treasury-0.1.3.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,264 @@
1
+ from typing import Final, cast
2
+
3
+ from dao_treasury import TreasuryTx, TreasuryWallet
4
+ from dao_treasury.db import Address
5
+ from eth_typing import BlockNumber, ChecksumAddress
6
+ from faster_async_lru import alru_cache
7
+ from y import Contract, Network
8
+ from y.prices.yearn import YearnInspiredVault
9
+
10
+ from yearn_treasury.constants import CHAINID, TREASURY_WALLETS
11
+ from yearn_treasury.rules.ignore.swaps import swaps
12
+ from yearn_treasury.rules.constants import ZERO_ADDRESS
13
+ from yearn_treasury.vaults import v1, v2
14
+
15
+
16
+ vaults: Final = swaps("Vaults")
17
+
18
+ TREASURY_AND_ZERO: Final = {*TREASURY_WALLETS, ZERO_ADDRESS}
19
+
20
+ all_vaults: Final = tuple(v1.keys()) + tuple(v2.values())
21
+
22
+
23
+ @vaults("Deposit")
24
+ async def is_vault_deposit(tx: TreasuryTx) -> bool:
25
+ return (
26
+ await is_v1_or_v2_vault_deposit(tx)
27
+ or tx.hash
28
+ in {
29
+ # TODO these go thru zaps and do not get caught by the logic below. figure out how to capture these
30
+ Network.Mainnet: { # type: ignore [call-overload]
31
+ "0x39616fdfc8851e10e17d955f55beea5c3dd4eed7c066a8ecbed8e50b496012ff",
32
+ "0x248e896eb732dfe40a0fa49131717bb7d2c1721743a2945ab9680787abcf9c50",
33
+ "0x2ce0240a08c8cc8d35b018995862711eb660a24d294b1aa674fbc467af4e621b",
34
+ # this is not thru a zap, its a lp-yCRVv2 deposit I probably dont need to write hueristics for
35
+ "0x93cf055d82b7e82b3877ab506629de6359fc5385ffb6b8c2fbfe0d61947fab59",
36
+ }
37
+ }.get(CHAINID, set())
38
+ or is_v3_vault_deposit(tx)
39
+ )
40
+
41
+
42
+ async def is_v1_or_v2_vault_deposit(tx: TreasuryTx) -> bool:
43
+ """This code doesn't validate amounts but so far that's not been a problem."""
44
+ try:
45
+ if "Transfer" not in tx.events:
46
+ return False
47
+ except KeyError as e:
48
+ # This happens sometimes from a busted abi, shouldnt impact us
49
+ if str(e) == "'components'":
50
+ return False
51
+ raise
52
+
53
+ transfer_events = tx.events["Transfer"]
54
+
55
+ tx_token = tx.token_address
56
+
57
+ block = BlockNumber(tx.block)
58
+ sender: ChecksumAddress
59
+ receiver: ChecksumAddress
60
+ underlying_address: ChecksumAddress
61
+
62
+ # vault side
63
+ for vault in all_vaults:
64
+ if tx_token == vault.address:
65
+ for event in transfer_events:
66
+ if tx_token == event.address:
67
+ event_pos = event.pos
68
+ sender, receiver, value = event.values()
69
+ if sender == ZERO_ADDRESS and TreasuryWallet.check_membership(receiver, block):
70
+ tx_to_address = tx.to_address
71
+ underlying_address = await _get_underlying(vault)
72
+ for _event in transfer_events:
73
+ _sender, _receiver, _value = _event.values()
74
+ if (
75
+ _event.address == underlying_address
76
+ and tx_to_address == _sender
77
+ and tx_token == _receiver
78
+ ):
79
+ # v1
80
+ if _event.pos < event_pos:
81
+ return True
82
+ # v2
83
+ if event_pos < _event.pos:
84
+ return True
85
+
86
+ # token side
87
+ for vault in all_vaults:
88
+ if tx_token == await _get_underlying(vault):
89
+ for event in transfer_events:
90
+ if tx_token == event.address:
91
+ vault_address = vault.address
92
+ event_pos = event.pos
93
+ sender, receiver, value = event.values()
94
+ if TreasuryWallet.check_membership(sender, block) and receiver == vault_address:
95
+ for _event in transfer_events:
96
+ _sender, _receiver, _value = _event.values()
97
+ if (
98
+ _event.address == vault_address
99
+ and _sender == ZERO_ADDRESS
100
+ and TreasuryWallet.check_membership(_receiver, block)
101
+ ):
102
+ # v1?
103
+ if event_pos < _event.pos:
104
+ return True
105
+ # v2
106
+ if _event.pos < event_pos:
107
+ return True
108
+ return False
109
+
110
+
111
+ _v3_deposit_keys: Final = "sender", "owner", "assets", "shares"
112
+
113
+
114
+ def is_v3_vault_deposit(tx: TreasuryTx) -> bool:
115
+ try:
116
+ if "Deposit" not in tx.events:
117
+ return False
118
+ except KeyError as e:
119
+ # This happens sometimes due to a busted abi, shouldnt impact us
120
+ if str(e) == "'components'":
121
+ return False
122
+ raise
123
+
124
+ if deposits := [
125
+ event for event in tx.events["Deposit"] if all(key in event for key in _v3_deposit_keys)
126
+ ]:
127
+ token = tx.token
128
+ to_address = tx.to_address
129
+ amount = tx.amount
130
+
131
+ # Vault side
132
+ if tx.from_address == ZERO_ADDRESS:
133
+ token_address = token.address.address
134
+ if deposits := [d for d in deposits if token_address == d.address]:
135
+ for deposit in deposits:
136
+ if to_address != deposit["owner"]:
137
+ print("wrong owner")
138
+ continue
139
+ # TODO: once postgres is in, remove the `round`
140
+ # elif amount == (scaled := token.scale_value(deposit["shares"])):
141
+ # return True
142
+ amount = round(amount, 8)
143
+ scaled = round(token.scale_value(deposit["shares"]), 8)
144
+ if amount == scaled:
145
+ return True
146
+ print(f"wrong amount: tx={amount} event={scaled}")
147
+ print("no matching vault-side deposit found")
148
+
149
+ # Token side
150
+ elif deposits := [d for d in deposits if to_address == d.address]:
151
+ from_address = cast(Address, tx.from_address).address
152
+ for deposit in deposits:
153
+ if from_address != deposit["sender"]:
154
+ print("sender doesnt match")
155
+ continue
156
+ # TODO: once postgres is in, remove the `round`
157
+ amount = round(amount, 8)
158
+ scaled = round(token.scale_value(deposit["assets"]), 8)
159
+ if amount == scaled:
160
+ return True
161
+ print(f"wrong amount: tx={amount} event={scaled}")
162
+ print("no matching token-side deposit found")
163
+ return False
164
+
165
+
166
+ @alru_cache(maxsize=None)
167
+ async def _get_underlying(vault: Contract) -> ChecksumAddress:
168
+ underlying = await YearnInspiredVault(vault, asynchronous=True).underlying
169
+ return underlying.address # type: ignore [return-value]
170
+
171
+
172
+ @vaults("Withdrawal")
173
+ async def is_vault_withdrawal(tx: TreasuryTx) -> bool:
174
+ to_address = cast(Address, tx.to_address).address
175
+ if to_address not in TREASURY_AND_ZERO:
176
+ return False
177
+
178
+ try:
179
+ if "Transfer" not in tx.events:
180
+ return False
181
+ except KeyError as e:
182
+ if str(e) == "'components'":
183
+ return False
184
+ raise
185
+
186
+ transfer_events = tx.events["Transfer"]
187
+
188
+ token = tx.token
189
+ token_address = cast(ChecksumAddress, token.address.address)
190
+ block = BlockNumber(tx.block)
191
+
192
+ underlying: ChecksumAddress
193
+
194
+ # vault side
195
+ if any(token_address == vault.address for vault in all_vaults):
196
+ for event in transfer_events:
197
+ if token_address == event.address:
198
+ sender, receiver, value = event.values()
199
+ if (
200
+ to_address == ZERO_ADDRESS == receiver
201
+ and TreasuryWallet.check_membership(sender, block)
202
+ and tx.from_address == sender
203
+ ):
204
+ underlying = await _get_underlying(token_address)
205
+ for _event in transfer_events:
206
+ _sender, _receiver, _value = _event.values()
207
+ if (
208
+ _event.address == underlying
209
+ and tx.from_address == _receiver
210
+ and event.pos < _event.pos
211
+ and token_address == _sender
212
+ ):
213
+ return True
214
+ # token side
215
+ for vault in all_vaults:
216
+ if token_address == await _get_underlying(vault):
217
+ vault_address = vault.address
218
+ for event in transfer_events:
219
+ if token_address == event.address:
220
+ sender, receiver, value = event.values()
221
+ if tx.from_address == vault_address == sender and to_address == receiver:
222
+ for _event in transfer_events:
223
+ _sender, _receiver, _value = _event.values()
224
+ if (
225
+ _event.address == vault_address
226
+ and _receiver == ZERO_ADDRESS
227
+ and TreasuryWallet.check_membership(_sender, block)
228
+ and to_address == _sender
229
+ and _event.pos < event.pos
230
+ ):
231
+ return True
232
+ return False
233
+
234
+
235
+ @vaults("DOLA Fed Withdrawal")
236
+ def is_dolla_fed_withdrawal(tx: TreasuryTx) -> bool:
237
+ 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]
238
+ return True
239
+ 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]
240
+ return True
241
+ return False
242
+
243
+
244
+ @vaults("DOLA FRAX Vault Withdrawal")
245
+ def is_dola_frax_withdrawal(tx: TreasuryTx) -> bool:
246
+ symbol = tx.symbol
247
+ from_nickname = tx.from_nickname
248
+ to_nickname = tx.to_nickname
249
+ if (
250
+ symbol == "yvCurve-DOLA-FRAXBP-U"
251
+ and from_nickname == "Yearn yChad Multisig"
252
+ and to_nickname == "Zero Address"
253
+ ):
254
+ return True
255
+ elif (
256
+ symbol == "DOLAFRAXBP3CRV-f"
257
+ and from_nickname == "Token: Curve DOLA-FRAXBP Pool yVault - Unlisted"
258
+ and to_nickname == "Yearn yChad Multisig"
259
+ ):
260
+ return True
261
+ return (
262
+ tx.hash == "0x59a3a3b9e724835958eab6d0956a3acf697191182c41403c96d39976047d7240"
263
+ and tx.log_index == 232
264
+ )
@@ -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
@@ -0,0 +1,111 @@
1
+ import decimal
2
+ from typing import Final
3
+
4
+ from brownie.exceptions import EventLookupError
5
+ from dao_treasury import TreasuryTx
6
+ from y import WRAPPED_GAS_COIN, Network
7
+
8
+ from yearn_treasury.constants import YCHAD_MULTISIG
9
+ from yearn_treasury.rules.ignore.swaps import swaps
10
+
11
+
12
+ buying_yfi: Final = swaps("Buying YFI")
13
+
14
+ VYPER_BUYERS: Final = (
15
+ "0xdf5e4E54d212F7a01cf94B3986f40933fcfF589F", # buys YFI for DAI at the current chainlink price
16
+ "0x6903223578806940bd3ff0C51f87aa43968424c8", # buys YFI for DAI at the current chainlink price. Can be funded via llamapay stream.
17
+ )
18
+ """These contracts, now retired, previously were used to purchase YFI for DAI at the current chainlink market price."""
19
+
20
+
21
+ YFI_BUYBACK_AUCTIONS: Final = "0x4349ed200029e6Cf38F1455B9dA88981F1806df3"
22
+
23
+
24
+ Decimal: Final = decimal.Decimal
25
+
26
+
27
+ @buying_yfi("Top-up Buyer Contract", Network.Mainnet)
28
+ def is_buyer_top_up(tx: TreasuryTx) -> bool:
29
+ """
30
+ The sell side of these transactions is in :func:`is_buying_with_buyer`.
31
+ The buyer is topped up with DAI regularly and buys YFI at the current chainlink market price.
32
+
33
+ # TODO: amortize this into a daily expense
34
+ """
35
+ return tx.symbol == "DAI" and tx.to_address.address in VYPER_BUYERS # type: ignore [union-attr]
36
+
37
+
38
+ @buying_yfi("Buyer Contract", Network.Mainnet)
39
+ def is_buying_with_buyer(tx: TreasuryTx) -> bool:
40
+ """
41
+ The buy side of these transactions is in :func:`is_buyer_top_up`.
42
+ The buyer is topped up with DAI regularly and buys YFI at the current chainlink market price
43
+ """
44
+ if tx.symbol == "YFI" and tx.to_address.address == YCHAD_MULTISIG: # type: ignore [union-attr]
45
+ try:
46
+ events = tx.events
47
+ except KeyError as e:
48
+ if "components" in str(e):
49
+ print(f"cannot parse events of possible YFI buyback {tx}")
50
+ return False
51
+ raise
52
+
53
+ if "Buyback" in events:
54
+ buyback_events = events["Buyback"]
55
+ if len(buyback_events) > 1:
56
+ print(f"Must code handler for multiple Buyback events in one tx: {tx}")
57
+ return False
58
+ buyback_event = buyback_events[0]
59
+ if buyback_event.address in VYPER_BUYERS and all( # type: ignore [attr-defined]
60
+ arg in buyback_event for arg in ("buyer", "yfi", "dai")
61
+ ):
62
+ # TODO get rid of this rounding once we've swapped out sqlite for postgres
63
+ buyback_amount = Decimal(buyback_event["yfi"]) / 10**18 # type: ignore [call-overload]
64
+ if round(tx.amount, 14) == round(buyback_amount, 14):
65
+ return True
66
+ print(
67
+ f"from node: {buyback_amount} "
68
+ f"from db: {tx.amount} "
69
+ f"diff: {buyback_amount - tx.amount}"
70
+ )
71
+ else:
72
+ print("unhandled Buyback event: buyback_event")
73
+ return False
74
+
75
+
76
+ @buying_yfi("Buyback Auction", Network.Mainnet)
77
+ def is_buying_with_auction(tx: TreasuryTx) -> bool:
78
+ try:
79
+ if tx.symbol != "YFI" or tx.to_address != YCHAD_MULTISIG or "AuctionTaken" not in tx.events:
80
+ return False
81
+ except EventLookupError:
82
+ return False
83
+ except KeyError as e:
84
+ # TODO: diagnose and fix this, pretty sure it's in eth-event
85
+ if "components" not in str(e):
86
+ raise
87
+ return False
88
+
89
+ auctions_taken = tx.get_events("AuctionTaken")
90
+ if len(auctions_taken) == 0:
91
+ return False
92
+ if len(auctions_taken) > 1:
93
+ raise NotImplementedError("we need new code to handle this case")
94
+ event = auctions_taken[0]
95
+ if event.address != YFI_BUYBACK_AUCTIONS: # type: ignore [attr-defined]
96
+ raise ValueError(event.address, event) # type: ignore [attr-defined]
97
+ # did the auction contract send weth to tx.sender?
98
+ for transfer in tx.get_events("Transfer"):
99
+ if transfer.address == WRAPPED_GAS_COIN:
100
+ sender, receiver, amount = transfer.values()
101
+ if sender != YFI_BUYBACK_AUCTIONS:
102
+ print(f"Transfer sender is not YFI_BUYBACK_AUCTIONS: sender={sender} YFI_BUYBACK_AUCTIONS={YFI_BUYBACK_AUCTIONS}") # type: ignore [union-attr]
103
+ continue
104
+ if tx.from_address != receiver:
105
+ print(f"Transfer does not match auction taker: taker={tx.from_address.address} transfer={receiver}") # type: ignore [union-attr]
106
+ continue
107
+ # TODO get rid of this rounding once we've swapped out sqlite for postgres
108
+ if round(amount, 14) == round(event["taken"], 14): # type: ignore [call-overload]
109
+ return True
110
+ print(f"AuctionTaken: {event} amount does not match Transfer: {transfer}")
111
+ return False
@@ -0,0 +1,28 @@
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
+
9
+ yla: Final = swaps("Yearn Lazy Ape")
10
+
11
+
12
+ @yla("Deposit", Network.Mainnet)
13
+ def is_yla_deposit(tx: TreasuryTx) -> bool:
14
+ return tx.hash == "0x1d4e974db2d60ebd994410fcd793c5db771af9a14660015faf94cbdaec285009" and (
15
+ tx.symbol == "YLA" or tx.to_address.address == "0x9ba60bA98413A60dB4C651D4afE5C937bbD8044B" # type: ignore [union-attr]
16
+ )
17
+
18
+
19
+ @yla("Withdrawal", Network.Mainnet)
20
+ def is_yla_withdrawal(tx: TreasuryTx) -> bool:
21
+ return (
22
+ "0x85c6D6b0cd1383Cc85e8e36C09D0815dAf36b9E9"
23
+ in (
24
+ tx.from_address.address, # type: ignore [union-attr]
25
+ tx.to_address.address, # type: ignore [union-attr]
26
+ )
27
+ or tx.hash == "0xb1ed399c268dfaf9917e20270cb720ab95986630b6cd4cabd7f02bb55ad5f7c6"
28
+ )
@@ -0,0 +1,40 @@
1
+ """
2
+ Ignore rules for Unit.xyz protocol.
3
+
4
+ This module defines matching logic for Unit.xyz protocol transactions,
5
+ so they can be ignored in analytics and reporting.
6
+ """
7
+
8
+ from typing import Final
9
+
10
+ from dao_treasury import TreasuryTx, ignore
11
+ from y import Network
12
+
13
+ from yearn_treasury.constants import ZERO_ADDRESS
14
+
15
+
16
+ UNIT_XYZ_VAULT: Final = "0xb1cFF81b9305166ff1EFc49A129ad2AfCd7BCf19"
17
+
18
+ unit: Final = ignore("Unit.xyz")
19
+ collateral: Final = unit("Collateral")
20
+ usdp: Final = unit("USDP")
21
+
22
+
23
+ @collateral("YFI Deposit", Network.Mainnet)
24
+ def is_unit_yfi_deposit(tx: TreasuryTx) -> bool:
25
+ return tx.symbol == "YFI" and tx.to_address.address == UNIT_XYZ_VAULT # type: ignore [union-attr]
26
+
27
+
28
+ @collateral("YFI Withdrawal", Network.Mainnet)
29
+ def is_unit_yfi_withdrawal(tx: TreasuryTx) -> bool:
30
+ return tx.symbol == "YFI" and tx.from_address.address == UNIT_XYZ_VAULT # type: ignore [union-attr]
31
+
32
+
33
+ @usdp("Minting", Network.Mainnet)
34
+ def is_minting_usdp(tx: TreasuryTx) -> bool:
35
+ return tx.symbol == "USDP" and tx.from_address.address == ZERO_ADDRESS # type: ignore [union-attr]
36
+
37
+
38
+ @usdp("Burning", Network.Mainnet)
39
+ def is_burning_usdp(tx: TreasuryTx) -> bool:
40
+ return tx.symbol == "USDP" and tx.to_address.address == ZERO_ADDRESS # type: ignore [union-attr]
@@ -1,3 +1,11 @@
1
+ """
2
+ Ignore rules for WETH minting and burning.
3
+
4
+ This module defines matching logic for WETH minting and burning
5
+ transactions, so those transactions can be ignored in analytics
6
+ and reporting.
7
+ """
8
+
1
9
  from typing import Final
2
10
 
3
11
  import y
@@ -14,12 +22,12 @@ weth: Final[ChecksumAddress] = y.weth.address # type: ignore [assignment]
14
22
  def is_weth_mint(tx: TreasuryTx) -> bool:
15
23
  if (
16
24
  tx.from_address == ZERO_ADDRESS
17
- and TreasuryWallet._get_instance(tx.to_address.address) # type: ignore [union-attr, arg-type]
25
+ and TreasuryWallet.check_membership(tx.to_address.address, tx.block) # type: ignore [union-attr, arg-type]
18
26
  and tx.token == weth
19
27
  ):
20
28
  return True
21
29
  return bool(
22
- TreasuryWallet._get_instance(tx.from_address.address) # type: ignore [union-attr, arg-type]
30
+ TreasuryWallet.check_membership(tx.from_address.address, tx.block) # type: ignore [union-attr, arg-type]
23
31
  and tx.to_address == weth
24
32
  and tx.token == EEE_ADDRESS
25
33
  )
@@ -28,13 +36,13 @@ def is_weth_mint(tx: TreasuryTx) -> bool:
28
36
  @ignore("WETH:Burning")
29
37
  def is_weth(tx: TreasuryTx) -> bool:
30
38
  if (
31
- TreasuryWallet._get_instance(tx.from_address.address) # type: ignore [union-attr, arg-type]
39
+ TreasuryWallet.check_membership(tx.from_address.address, tx.block) # type: ignore [union-attr, arg-type]
32
40
  and tx.to_address == ZERO_ADDRESS
33
41
  and tx.token == weth
34
42
  ):
35
43
  return True
36
44
  return bool(
37
45
  tx.from_address == weth
38
- and TreasuryWallet._get_instance(tx.to_address.address) # type: ignore [union-attr, arg-type]
46
+ and TreasuryWallet.check_membership(tx.to_address.address, tx.block) # type: ignore [union-attr, arg-type]
39
47
  and tx.token == EEE_ADDRESS
40
48
  )
@@ -1,5 +1,6 @@
1
1
  from yearn_treasury.rules.other_expense.boost import *
2
2
  from yearn_treasury.rules.other_expense.bugs import *
3
+ from yearn_treasury.rules.other_expense.donations import *
3
4
  from yearn_treasury.rules.other_expense.dyfi import *
4
5
  from yearn_treasury.rules.other_expense.events import *
5
6
  from yearn_treasury.rules.other_expense.misc import *
@@ -0,0 +1,43 @@
1
+ """
2
+ Other expense rules for donations in Yearn Treasury.
3
+
4
+ This module defines matching logic for donation transactions,
5
+ including Gitcoin matching rounds, 4626 Alliance, Vyper Compiler
6
+ Audit Contest, Warroom Games, and more.
7
+ """
8
+
9
+ from typing import Final
10
+
11
+ from dao_treasury import TreasuryTx, other_expense
12
+ from y import Network
13
+
14
+
15
+ donations: Final = other_expense("Donations")
16
+
17
+ gitcoin: Final = "0xde21F729137C5Af1b01d73aF1dC21eFfa2B8a0d6"
18
+
19
+
20
+ @donations("Gitcoin Matching Round", Network.Mainnet)
21
+ def is_gitcoin_matching_donation(tx: TreasuryTx) -> bool:
22
+ return tx.symbol in ["DAI", "USDC"] and tx.to_address == gitcoin
23
+
24
+
25
+ donations("4626 Alliance", Network.Mainnet).match(
26
+ hash="0xca61496c32806ba34f0deb331c32969eda11c947fdd6235173e6fa13d9a1c288",
27
+ log_index=150,
28
+ )
29
+
30
+
31
+ donations("Vyper Compiler Audit Contest", Network.Mainnet).match(
32
+ # Grant for a vyper compiler audit context, vyper-context.eth
33
+ hash="0xb8bb3728fdfb49d7c86c08dba8e3586e3761f13d2c88fa6fab80227b6a3f4519",
34
+ log_index=202,
35
+ )
36
+
37
+
38
+ @donations("Warroom Games 2023 Prizes", Network.Mainnet)
39
+ def is_warroom_games(tx: TreasuryTx) -> bool:
40
+ return (
41
+ tx.hash == "0x8f17ead9cea87166cf99ed2cdbc46dfdf98c04c261de5b5167caddce5f704cb2"
42
+ and tx.log_index in [429, 430, 431]
43
+ )
@@ -7,6 +7,12 @@ from y import Network
7
7
  events: Final = other_expense("Events")
8
8
 
9
9
 
10
+ events("Devcon").match(
11
+ hash="0x57bc99f6007989606bdd9d1adf91c99d198de51f61d29689ee13ccf440b244df",
12
+ log_index=83,
13
+ )
14
+
15
+
10
16
  @events("EthDenver", Network.Mainnet)
11
17
  def is_eth_denver(tx: TreasuryTx) -> bool:
12
18
  return (
@@ -3,5 +3,6 @@
3
3
  - "0x93C6c14C134C4fF52cbB6BC2f50F19d84874cDD1"
4
4
  Aztek Gas Subsidy:
5
5
  - "0xABc30E831B5Cc173A9Ed5941714A7845c909e7fA"
6
- Vyper Donation:
7
- - "0x70CCBE10F980d80b7eBaab7D2E3A73e87D67B775"
6
+ Donations:
7
+ Vyper:
8
+ - "0x70CCBE10F980d80b7eBaab7D2E3A73e87D67B775"