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.
- yearn_treasury/__init__.py +12 -0
- yearn_treasury/_db.cp311-win_amd64.pyd +0 -0
- yearn_treasury/_db.py +39 -10
- yearn_treasury/_ens.cp311-win_amd64.pyd +0 -0
- yearn_treasury/_ens.py +14 -0
- yearn_treasury/_logging.cp311-win_amd64.pyd +0 -0
- yearn_treasury/_logging.py +43 -0
- yearn_treasury/{addresses.yaml → address_labels.yaml} +10 -0
- yearn_treasury/budget/__init__.cp311-win_amd64.pyd +0 -0
- yearn_treasury/budget/__init__.py +2 -2
- yearn_treasury/budget/_request.cp311-win_amd64.pyd +0 -0
- yearn_treasury/budget/_request.py +8 -0
- yearn_treasury/budget/_requests.cp311-win_amd64.pyd +0 -0
- yearn_treasury/budget/_requests.py +45 -14
- yearn_treasury/constants.py +27 -7
- yearn_treasury/main.py +71 -29
- yearn_treasury/rules/__init__.py +14 -0
- yearn_treasury/rules/constants.cp311-win_amd64.pyd +0 -0
- yearn_treasury/rules/cost_of_revenue/gas.cp311-win_amd64.pyd +0 -0
- yearn_treasury/rules/cost_of_revenue/gas.py +9 -1
- yearn_treasury/rules/expense/__init__.cp311-win_amd64.pyd +0 -0
- yearn_treasury/rules/expense/general.cp311-win_amd64.pyd +0 -0
- yearn_treasury/rules/expense/infrastructure.cp311-win_amd64.pyd +0 -0
- yearn_treasury/rules/expense/infrastructure.py +7 -0
- yearn_treasury/rules/expense/people.cp311-win_amd64.pyd +0 -0
- yearn_treasury/rules/expense/people.py +43 -0
- yearn_treasury/rules/expense/security.cp311-win_amd64.pyd +0 -0
- yearn_treasury/rules/expense/security.py +17 -0
- yearn_treasury/rules/ignore/__init__.py +2 -0
- yearn_treasury/rules/ignore/general.cp311-win_amd64.pyd +0 -0
- yearn_treasury/rules/ignore/general.py +3 -2
- yearn_treasury/rules/ignore/maker.py +40 -28
- yearn_treasury/rules/ignore/passthru.py +310 -0
- yearn_treasury/rules/ignore/swaps/__init__.py +14 -0
- yearn_treasury/rules/ignore/swaps/aave.py +39 -18
- yearn_treasury/rules/ignore/swaps/auctions.cp311-win_amd64.pyd +0 -0
- yearn_treasury/rules/ignore/swaps/auctions.py +31 -0
- yearn_treasury/rules/ignore/swaps/compound.py +32 -22
- yearn_treasury/rules/ignore/swaps/conversion_factory.cp311-win_amd64.pyd +0 -0
- yearn_treasury/rules/ignore/swaps/conversion_factory.py +21 -0
- yearn_treasury/rules/ignore/swaps/cowswap.py +87 -0
- yearn_treasury/rules/ignore/swaps/curve.py +170 -0
- yearn_treasury/rules/ignore/swaps/gearbox.cp311-win_amd64.pyd +0 -0
- yearn_treasury/rules/ignore/swaps/gearbox.py +37 -0
- yearn_treasury/rules/ignore/swaps/iearn.cp311-win_amd64.pyd +0 -0
- yearn_treasury/rules/ignore/swaps/iearn.py +43 -0
- yearn_treasury/rules/ignore/swaps/otc.cp311-win_amd64.pyd +0 -0
- yearn_treasury/rules/ignore/swaps/otc.py +58 -0
- yearn_treasury/rules/ignore/swaps/pooltogether.cp311-win_amd64.pyd +0 -0
- yearn_treasury/rules/ignore/swaps/pooltogether.py +23 -0
- yearn_treasury/rules/ignore/swaps/synthetix.cp311-win_amd64.pyd +0 -0
- yearn_treasury/rules/ignore/swaps/synthetix.py +10 -0
- yearn_treasury/rules/ignore/swaps/uniswap.py +29 -6
- yearn_treasury/rules/ignore/swaps/unwrapper.cp311-win_amd64.pyd +0 -0
- yearn_treasury/rules/ignore/swaps/unwrapper.py +17 -0
- yearn_treasury/rules/ignore/swaps/vaults.cp311-win_amd64.pyd +0 -0
- yearn_treasury/rules/ignore/swaps/vaults.py +264 -0
- yearn_treasury/rules/ignore/swaps/woofy.cp311-win_amd64.pyd +0 -0
- yearn_treasury/rules/ignore/swaps/woofy.py +80 -0
- yearn_treasury/rules/ignore/swaps/yfi.cp311-win_amd64.pyd +0 -0
- yearn_treasury/rules/ignore/swaps/yfi.py +111 -0
- yearn_treasury/rules/ignore/swaps/yla.cp311-win_amd64.pyd +0 -0
- yearn_treasury/rules/ignore/swaps/yla.py +28 -0
- yearn_treasury/rules/ignore/unit.cp311-win_amd64.pyd +0 -0
- yearn_treasury/rules/ignore/unit.py +40 -0
- yearn_treasury/rules/ignore/weth.cp311-win_amd64.pyd +0 -0
- yearn_treasury/rules/ignore/weth.py +12 -4
- yearn_treasury/rules/ignore/ygov.cp311-win_amd64.pyd +0 -0
- yearn_treasury/rules/other_expense/__init__.cp311-win_amd64.pyd +0 -0
- yearn_treasury/rules/other_expense/__init__.py +1 -0
- yearn_treasury/rules/other_expense/boost.cp311-win_amd64.pyd +0 -0
- yearn_treasury/rules/other_expense/bugs.cp311-win_amd64.pyd +0 -0
- yearn_treasury/rules/other_expense/donations.cp311-win_amd64.pyd +0 -0
- yearn_treasury/rules/other_expense/donations.py +43 -0
- yearn_treasury/rules/other_expense/dyfi.cp311-win_amd64.pyd +0 -0
- yearn_treasury/rules/other_expense/events.cp311-win_amd64.pyd +0 -0
- yearn_treasury/rules/other_expense/events.py +6 -0
- yearn_treasury/rules/other_expense/match_on_to_address.yaml +3 -2
- yearn_treasury/rules/other_expense/misc.cp311-win_amd64.pyd +0 -0
- yearn_treasury/rules/other_expense/misc.py +22 -0
- yearn_treasury/rules/other_expense/revshare.cp311-win_amd64.pyd +0 -0
- yearn_treasury/rules/other_income/__init__.cp311-win_amd64.pyd +0 -0
- yearn_treasury/rules/other_income/__init__.py +2 -77
- yearn_treasury/rules/other_income/airdrops.cp311-win_amd64.pyd +0 -0
- yearn_treasury/rules/other_income/airdrops.py +30 -0
- yearn_treasury/rules/other_income/match_on_hash.yaml +2 -0
- yearn_treasury/rules/other_income/misc.cp311-win_amd64.pyd +0 -0
- yearn_treasury/rules/other_income/misc.py +80 -0
- yearn_treasury/rules/revenue/bribes.cp311-win_amd64.pyd +0 -0
- yearn_treasury/rules/revenue/farming.cp311-win_amd64.pyd +0 -0
- yearn_treasury/rules/revenue/keepcoins.cp311-win_amd64.pyd +0 -0
- yearn_treasury/rules/revenue/seasolver.cp311-win_amd64.pyd +0 -0
- yearn_treasury/rules/revenue/vaults.py +25 -22
- yearn_treasury/rules/revenue/yteams.cp311-win_amd64.pyd +0 -0
- yearn_treasury/shitcoins.py +93 -2
- yearn_treasury/vaults.cp311-win_amd64.pyd +0 -0
- yearn_treasury/vaults.py +17 -4
- yearn_treasury/wallets.yaml +54 -0
- yearn_treasury/yteams.py +208 -0
- {yearn_treasury-0.0.15.dist-info → yearn_treasury-0.1.3.dist-info}/METADATA +21 -8
- yearn_treasury-0.1.3.dist-info/RECORD +128 -0
- yearn_treasury-0.1.3.dist-info/top_level.txt +2 -0
- yearn_treasury__mypyc.cp311-win_amd64.pyd +0 -0
- 3ddd27aee2f79379b6d9__mypyc.cp311-win_amd64.pyd +0 -0
- yearn_treasury-0.0.15.dist-info/RECORD +0 -85
- yearn_treasury-0.0.15.dist-info/top_level.txt +0 -2
- {yearn_treasury-0.0.15.dist-info → yearn_treasury-0.1.3.dist-info}/WHEEL +0 -0
- {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
|
+
)
|
|
Binary file
|
|
@@ -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
|
|
Binary file
|
|
@@ -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
|
|
Binary file
|
|
@@ -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
|
+
)
|
|
Binary file
|
|
@@ -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]
|
|
Binary file
|
|
@@ -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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
)
|
|
Binary file
|
|
Binary file
|
|
@@ -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 *
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -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
|
+
)
|
|
Binary file
|
|
Binary file
|
|
@@ -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 (
|
|
Binary file
|