yearn-treasury 0.0.8__cp310-cp310-win_amd64.whl → 0.0.48__cp310-cp310-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 (127) hide show
  1. yearn_treasury/__init__.py +16 -0
  2. yearn_treasury/_db.cp310-win_amd64.pyd +0 -0
  3. yearn_treasury/_db.py +41 -0
  4. yearn_treasury/_ens.cp310-win_amd64.pyd +0 -0
  5. yearn_treasury/_ens.py +14 -0
  6. yearn_treasury/_logging.cp310-win_amd64.pyd +0 -0
  7. yearn_treasury/_logging.py +43 -0
  8. yearn_treasury/address_labels.yaml +39 -0
  9. yearn_treasury/budget/__init__.cp310-win_amd64.pyd +0 -0
  10. yearn_treasury/budget/__init__.py +2 -2
  11. yearn_treasury/budget/_request.cp310-win_amd64.pyd +0 -0
  12. yearn_treasury/budget/_request.py +8 -0
  13. yearn_treasury/budget/_requests.cp310-win_amd64.pyd +0 -0
  14. yearn_treasury/budget/_requests.py +45 -14
  15. yearn_treasury/constants.py +34 -6
  16. yearn_treasury/main.py +77 -43
  17. yearn_treasury/rules/__init__.py +17 -0
  18. yearn_treasury/rules/constants.cp310-win_amd64.pyd +0 -0
  19. yearn_treasury/rules/constants.py +1 -1
  20. yearn_treasury/rules/cost_of_revenue/gas.cp310-win_amd64.pyd +0 -0
  21. yearn_treasury/rules/cost_of_revenue/gas.py +10 -3
  22. yearn_treasury/rules/expense/__init__.cp310-win_amd64.pyd +0 -0
  23. yearn_treasury/rules/expense/general.cp310-win_amd64.pyd +0 -0
  24. yearn_treasury/rules/expense/infrastructure.cp310-win_amd64.pyd +0 -0
  25. yearn_treasury/rules/expense/infrastructure.py +31 -1
  26. yearn_treasury/rules/expense/people.cp310-win_amd64.pyd +0 -0
  27. yearn_treasury/rules/expense/people.py +43 -0
  28. yearn_treasury/rules/expense/security.cp310-win_amd64.pyd +0 -0
  29. yearn_treasury/rules/expense/security.py +51 -27
  30. yearn_treasury/rules/ignore/__init__.py +8 -6
  31. yearn_treasury/rules/ignore/general.cp310-win_amd64.pyd +0 -0
  32. yearn_treasury/rules/ignore/general.py +3 -2
  33. yearn_treasury/rules/ignore/maker.py +48 -64
  34. yearn_treasury/rules/ignore/passthru.py +310 -0
  35. yearn_treasury/rules/ignore/staking.py +23 -18
  36. yearn_treasury/rules/ignore/swaps/__init__.py +18 -5
  37. yearn_treasury/rules/ignore/swaps/aave.py +39 -19
  38. yearn_treasury/rules/ignore/swaps/auctions.cp310-win_amd64.pyd +0 -0
  39. yearn_treasury/rules/ignore/swaps/auctions.py +31 -0
  40. yearn_treasury/rules/ignore/swaps/compound.py +38 -36
  41. yearn_treasury/rules/ignore/swaps/conversion_factory.cp310-win_amd64.pyd +0 -0
  42. yearn_treasury/rules/ignore/swaps/conversion_factory.py +21 -0
  43. yearn_treasury/rules/ignore/swaps/cowswap.py +87 -0
  44. yearn_treasury/rules/ignore/swaps/curve.py +170 -0
  45. yearn_treasury/rules/ignore/swaps/gearbox.cp310-win_amd64.pyd +0 -0
  46. yearn_treasury/rules/ignore/swaps/gearbox.py +37 -0
  47. yearn_treasury/rules/ignore/swaps/iearn.cp310-win_amd64.pyd +0 -0
  48. yearn_treasury/rules/ignore/swaps/iearn.py +43 -0
  49. yearn_treasury/rules/ignore/swaps/otc.cp310-win_amd64.pyd +0 -0
  50. yearn_treasury/rules/ignore/swaps/otc.py +58 -0
  51. yearn_treasury/rules/ignore/swaps/pooltogether.cp310-win_amd64.pyd +0 -0
  52. yearn_treasury/rules/ignore/swaps/pooltogether.py +23 -0
  53. yearn_treasury/rules/ignore/swaps/synthetix.cp310-win_amd64.pyd +0 -0
  54. yearn_treasury/rules/ignore/swaps/synthetix.py +10 -0
  55. yearn_treasury/rules/ignore/swaps/uniswap.py +222 -169
  56. yearn_treasury/rules/ignore/swaps/unwrapper.cp310-win_amd64.pyd +0 -0
  57. yearn_treasury/rules/ignore/swaps/unwrapper.py +17 -0
  58. yearn_treasury/rules/ignore/swaps/vaults.cp310-win_amd64.pyd +0 -0
  59. yearn_treasury/rules/ignore/swaps/vaults.py +264 -0
  60. yearn_treasury/rules/ignore/swaps/woofy.cp310-win_amd64.pyd +0 -0
  61. yearn_treasury/rules/ignore/swaps/woofy.py +80 -0
  62. yearn_treasury/rules/ignore/swaps/yfi.cp310-win_amd64.pyd +0 -0
  63. yearn_treasury/rules/ignore/swaps/yfi.py +111 -0
  64. yearn_treasury/rules/ignore/swaps/yla.cp310-win_amd64.pyd +0 -0
  65. yearn_treasury/rules/ignore/swaps/yla.py +28 -0
  66. yearn_treasury/rules/ignore/unit.cp310-win_amd64.pyd +0 -0
  67. yearn_treasury/rules/ignore/unit.py +40 -0
  68. yearn_treasury/rules/ignore/weth.cp310-win_amd64.pyd +0 -0
  69. yearn_treasury/rules/ignore/weth.py +12 -4
  70. yearn_treasury/rules/ignore/ygov.cp310-win_amd64.pyd +0 -0
  71. yearn_treasury/rules/other_expense/__init__.cp310-win_amd64.pyd +0 -0
  72. yearn_treasury/rules/other_expense/__init__.py +7 -0
  73. yearn_treasury/rules/other_expense/boost.cp310-win_amd64.pyd +0 -0
  74. yearn_treasury/rules/other_expense/boost.py +49 -0
  75. yearn_treasury/rules/other_expense/bugs.cp310-win_amd64.pyd +0 -0
  76. yearn_treasury/rules/other_expense/bugs.py +81 -0
  77. yearn_treasury/rules/other_expense/donations.cp310-win_amd64.pyd +0 -0
  78. yearn_treasury/rules/other_expense/donations.py +43 -0
  79. yearn_treasury/rules/other_expense/dyfi.cp310-win_amd64.pyd +0 -0
  80. yearn_treasury/rules/other_expense/dyfi.py +29 -0
  81. yearn_treasury/rules/other_expense/events.cp310-win_amd64.pyd +0 -0
  82. yearn_treasury/rules/other_expense/events.py +21 -0
  83. yearn_treasury/rules/other_expense/match_on_to_address.yaml +3 -2
  84. yearn_treasury/rules/other_expense/misc.cp310-win_amd64.pyd +0 -0
  85. yearn_treasury/rules/other_expense/misc.py +49 -0
  86. yearn_treasury/rules/other_expense/revshare.cp310-win_amd64.pyd +0 -0
  87. yearn_treasury/rules/other_expense/revshare.py +20 -0
  88. yearn_treasury/rules/other_income/__init__.cp310-win_amd64.pyd +0 -0
  89. yearn_treasury/rules/other_income/__init__.py +2 -77
  90. yearn_treasury/rules/other_income/airdrops.cp310-win_amd64.pyd +0 -0
  91. yearn_treasury/rules/other_income/airdrops.py +30 -0
  92. yearn_treasury/rules/other_income/match_on_hash.yaml +2 -0
  93. yearn_treasury/rules/other_income/misc.cp310-win_amd64.pyd +0 -0
  94. yearn_treasury/rules/other_income/misc.py +80 -0
  95. yearn_treasury/rules/revenue/__init__.py +5 -0
  96. yearn_treasury/rules/revenue/bribes.cp310-win_amd64.pyd +0 -0
  97. yearn_treasury/rules/revenue/farming.cp310-win_amd64.pyd +0 -0
  98. yearn_treasury/rules/revenue/farming.py +56 -0
  99. yearn_treasury/rules/revenue/keepcoins.cp310-win_amd64.pyd +0 -0
  100. yearn_treasury/rules/revenue/seasolver.cp310-win_amd64.pyd +0 -0
  101. yearn_treasury/rules/revenue/seasolver.py +7 -1
  102. yearn_treasury/rules/revenue/vaults.py +97 -34
  103. yearn_treasury/rules/revenue/yteams.cp310-win_amd64.pyd +0 -0
  104. yearn_treasury/rules/revenue/yteams.py +17 -0
  105. yearn_treasury/shitcoins.py +93 -2
  106. yearn_treasury/vaults.cp310-win_amd64.pyd +0 -0
  107. yearn_treasury/vaults.py +30 -22
  108. yearn_treasury/wallets.yaml +54 -0
  109. yearn_treasury/yteams.py +208 -0
  110. yearn_treasury-0.0.48.dist-info/METADATA +86 -0
  111. yearn_treasury-0.0.48.dist-info/RECORD +128 -0
  112. yearn_treasury-0.0.48.dist-info/top_level.txt +2 -0
  113. yearn_treasury__mypyc.cp310-win_amd64.pyd +0 -0
  114. f79b89f5f6693162015b__mypyc.cp310-win_amd64.pyd +0 -0
  115. yearn_treasury/rules/ignore/__init__.cp310-win_amd64.pyd +0 -0
  116. yearn_treasury/rules/ignore/maker.cp310-win_amd64.pyd +0 -0
  117. yearn_treasury/rules/ignore/staking.cp310-win_amd64.pyd +0 -0
  118. yearn_treasury/rules/ignore/swaps/__init__.cp310-win_amd64.pyd +0 -0
  119. yearn_treasury/rules/ignore/swaps/_skip_tokens.cp310-win_amd64.pyd +0 -0
  120. yearn_treasury/rules/ignore/swaps/aave.cp310-win_amd64.pyd +0 -0
  121. yearn_treasury/rules/ignore/swaps/compound.cp310-win_amd64.pyd +0 -0
  122. yearn_treasury/rules/ignore/swaps/uniswap.cp310-win_amd64.pyd +0 -0
  123. yearn_treasury-0.0.8.dist-info/METADATA +0 -69
  124. yearn_treasury-0.0.8.dist-info/RECORD +0 -76
  125. yearn_treasury-0.0.8.dist-info/top_level.txt +0 -2
  126. {yearn_treasury-0.0.8.dist-info → yearn_treasury-0.0.48.dist-info}/WHEEL +0 -0
  127. {yearn_treasury-0.0.8.dist-info → yearn_treasury-0.0.48.dist-info}/entry_points.txt +0 -0
@@ -1,9 +1,10 @@
1
+ from decimal import Decimal
1
2
  from logging import getLogger
2
- from typing import Final, Optional
3
+ from typing import Final, Optional, cast
3
4
 
4
- from async_lru import alru_cache
5
5
  from dao_treasury import TreasuryTx, revenue
6
6
  from eth_typing import ChecksumAddress
7
+ from faster_async_lru import alru_cache
7
8
  from y import Contract, Network
8
9
 
9
10
  from yearn_treasury.vaults import v1, v2
@@ -15,18 +16,25 @@ fees: Final = revenue("Fees")
15
16
 
16
17
 
17
18
  # v1 helpers
18
- def _is_y3crv(tx: TreasuryTx) -> bool:
19
+ def _is_single_sided(tx: TreasuryTx) -> bool:
20
+ """Fees from single-sided strategies are not denominated in `vault.token`."""
21
+ symbol = tx.symbol
22
+ from_nickname = tx.from_nickname
23
+ return _is_y3crv(symbol, from_nickname) or _is_ypool(symbol, from_nickname)
24
+
25
+
26
+ def _is_y3crv(symbol: str, from_nickname: str) -> bool:
19
27
  return (
20
- tx.symbol == "y3Crv"
21
- and (from_nickname := tx.from_nickname).startswith("Contract: Strategy")
28
+ symbol == "y3Crv"
29
+ and from_nickname.startswith("Contract: Strategy")
22
30
  and from_nickname.endswith("3pool")
23
31
  )
24
32
 
25
33
 
26
- def _is_ypool(tx: TreasuryTx) -> bool:
34
+ def _is_ypool(symbol: str, from_nickname: str) -> bool:
27
35
  return (
28
- tx.symbol == "yyDAI+yUSDC+yUSDT+yTUSD"
29
- and (from_nickname := tx.from_nickname).startswith("Contract: Strategy")
36
+ symbol == "yyDAI+yUSDC+yUSDT+yTUSD"
37
+ and from_nickname.startswith("Contract: Strategy")
30
38
  and from_nickname.endswith("ypool")
31
39
  )
32
40
 
@@ -48,11 +56,13 @@ async def _get_controller(vault: Contract) -> Contract:
48
56
 
49
57
  @fees("Vaults V1", Network.Mainnet)
50
58
  async def is_v1_vault_fees(tx: TreasuryTx) -> bool:
51
- token = tx.token.address.address
59
+ token = tx.token_address
60
+
61
+ # Fees from single-sided strategies are not denominated in `vault.token` but everything else is
62
+ is_single_sided = _is_single_sided(tx)
52
63
 
53
64
  for vault, underlying in v1.items():
54
- # Fees from single-sided strategies are not denominated in `vault.token` but everything else is
55
- if token != underlying and not _is_y3crv(tx) and not _is_ypool(tx):
65
+ if token != underlying and not is_single_sided:
56
66
  continue
57
67
 
58
68
  controller = await _get_controller(vault)
@@ -65,39 +75,41 @@ async def is_v1_vault_fees(tx: TreasuryTx) -> bool:
65
75
  strategy: ChecksumAddress = await controller.strategies.coroutine(
66
76
  underlying, block_identifier=tx.block
67
77
  )
68
- if tx.from_address != strategy:
69
- logger.debug(
70
- "from address %s doesnt match strategy %s set on controller %s",
71
- tx.from_address.address,
72
- strategy,
73
- controller,
74
- )
75
- continue
76
- return True
78
+ if tx.from_address == strategy:
79
+ return True
80
+ logger.debug(
81
+ "from address %s doesnt match strategy %s set on controller %s",
82
+ tx.from_address.address,
83
+ strategy,
84
+ controller,
85
+ )
77
86
  return False
78
87
 
79
88
 
80
- def is_inverse_fees_from_stash_contract(tx: TreasuryTx) -> bool:
89
+ def is_inverse_fees_from_stash_contract(
90
+ from_address: ChecksumAddress, to_nickname: str | None
91
+ ) -> bool:
81
92
  return (
82
- tx.from_address == "0xE376e8e8E3B0793CD61C6F1283bA18548b726C2e"
83
- and tx.to_nickname == "Token: Curve stETH Pool yVault"
93
+ from_address == "0xE376e8e8E3B0793CD61C6F1283bA18548b726C2e"
94
+ and to_nickname == "Token: Curve stETH Pool yVault"
84
95
  )
85
96
 
86
97
 
87
- # TODO: add Network param to SortRule
88
98
  @fees("Vaults V2")
89
99
  async def is_v2_vault_fees(tx: TreasuryTx) -> bool:
90
- for vault in v2:
91
- if (
92
- tx.from_address == vault.address
93
- and tx.token == vault.address
94
- and tx.to_address == await vault.rewards.coroutine(block_identifier=tx.block)
95
- ):
96
- return True
100
+ token = tx.token_address
101
+ from_address = cast(ChecksumAddress, tx.from_address.address)
102
+ if (
103
+ from_address == token
104
+ and (vault := v2.get(from_address))
105
+ and tx.to_address == await vault.rewards.coroutine(block_identifier=tx.block)
106
+ ):
107
+ return True
97
108
 
98
- if is_inverse_fees_from_stash_contract(tx):
99
- if tx.value_usd > 0: # type: ignore [operator]
100
- tx.value_usd *= -1 # type: ignore [operator]
109
+ if is_inverse_fees_from_stash_contract(from_address, tx.to_nickname):
110
+ value_usd = cast(Decimal, tx.value_usd)
111
+ if value_usd > 0:
112
+ tx.value_usd = -value_usd
101
113
  return True
102
114
 
103
115
  return False
@@ -107,3 +119,54 @@ async def is_v2_vault_fees(tx: TreasuryTx) -> bool:
107
119
  def is_v3_vault_fees(tx: TreasuryTx) -> bool:
108
120
  # TODO: fill me in too
109
121
  return False
122
+
123
+
124
+ @fees("YearnFed", Network.Mainnet)
125
+ def is_yearn_fed_fees(tx: TreasuryTx) -> bool:
126
+ symbol = tx.symbol
127
+ from_address = tx.from_address
128
+ # New version
129
+ if (
130
+ symbol in ["yvCurve-DOLA-U", "CRV"]
131
+ and from_address == "0x64e4fC597C70B26102464B7F70B1F00C77352910"
132
+ ):
133
+ return True
134
+ # Old versions
135
+ if symbol in ["yvCurve-DOLA-U", "yveCRV-DAO"] and from_address in [
136
+ "0x09F61718474e2FFB884f438275C0405E3D3559d3",
137
+ "0x7928becDda70755B9ABD5eE7c7D5E267F1412042",
138
+ ]:
139
+ return True
140
+ if (
141
+ symbol == "INV"
142
+ and tx.from_nickname == "Inverse Treasury"
143
+ and tx.to_nickname == "ySwap Multisig"
144
+ ):
145
+ return True
146
+ if (
147
+ from_address == "0x9D5Df30F475CEA915b1ed4C0CCa59255C897b61B"
148
+ and tx.to_nickname == "ySwap Multisig"
149
+ ):
150
+ return True
151
+ return False
152
+
153
+
154
+ @fees("DOLAFRAXBP", Network.Mainnet)
155
+ def is_dolafraxbp_fees(tx: TreasuryTx) -> bool:
156
+ return (
157
+ tx.from_nickname == "Contract: StrategyConvexFraxBpRewardsClonable"
158
+ and tx.to_nickname == "Yearn yChad Multisig"
159
+ and tx.symbol == "yvCurve-DOLA-FRAXBP-U"
160
+ )
161
+
162
+
163
+ @fees("TempleDAO Private Vault", Network.Mainnet)
164
+ def is_temple(tx: TreasuryTx) -> bool:
165
+ if tx.to_nickname in ["Yearn Treasury", "Yearn yChad Multisig"]: # fees have been sent to both
166
+ from_nickname = tx.from_nickname
167
+ symbol = tx.symbol
168
+ if from_nickname == "Contract: StrategyConvexCrvCvxPairsClonable" and symbol == "CRV":
169
+ return True
170
+ elif from_nickname == "Contract: Splitter" and symbol in ["yveCRV-DAO", "CRV"]:
171
+ return True
172
+ return False
@@ -0,0 +1,17 @@
1
+ from typing import Final
2
+
3
+ from dao_treasury import TreasuryTx, revenue
4
+ from y import Network
5
+
6
+
7
+ OXSPLIT_CONTRACT: Final = "0x2ed6c4B5dA6378c7897AC67Ba9e43102Feb694EE"
8
+ SPLITS_WAREHOUSE_CONTRACT: Final = "0x8fb66F38cF86A3d5e8768f8F1754A24A6c661Fb8"
9
+
10
+
11
+ @revenue("yTeam Rev Share", Network.Mainnet)
12
+ def is_yteam_rev_share(tx: TreasuryTx) -> bool:
13
+ return tx.from_address in [OXSPLIT_CONTRACT, SPLITS_WAREHOUSE_CONTRACT] or tx.hash in [
14
+ # These predate the split implementation and must be accounted for separately
15
+ # yAudit
16
+ "0x6e4f4405bd0970d42a48795a5219c14c763705f6ea9879affea652438758c065",
17
+ ]
@@ -1,8 +1,24 @@
1
+ """
2
+ List of "shitcoins" (but like, actual shit shitcoins) to ignore in all
3
+ Yearn Treasury analytics.
4
+
5
+ This module defines, for each blockchain network, a set of token addresses
6
+ known to be unpricable, considered as spam, or otherwise unwanted for
7
+ reporting and analytics. These tokens are passed in to :mod:`eth-portfolio`,
8
+ which contains the logic that prevents these shitcoins from being included in
9
+ any Yearn Treasury outputs.
10
+
11
+ Since these tokens do nothing but add noise to the outputs, transactions
12
+ involving them are excluded from treasury calculations, reports, and dashboards.
13
+ """
14
+
15
+ from typing import Final
16
+
1
17
  from y import Network, convert
2
18
  from y.constants import CHAINID
3
19
 
4
20
 
5
- _SHITCOINS = {
21
+ _SHITCOINS: Final = {
6
22
  Network.Mainnet: (
7
23
  "0xC36442b4a4522E871399CD717aBDD847Ab11FE88", # UNI-V3 NFT, not shitcoin but not pricable
8
24
  "0x0329b631464C43f4e8132df7B4ac29a2D89FFdC7",
@@ -34,18 +50,93 @@ _SHITCOINS = {
34
50
  "0xe5868468Cb6Dd5d6D7056bd93f084816c6eF075f",
35
51
  "0x0a24Bb4842c301276c65086B5d78D5C872993c72",
36
52
  "0x63125c0d5Cd9071de9A1ac84c400982f41C697AE",
53
+ "0x4d22921215cF37e8d49e2Ac6d1F5e9672f63A7c6",
54
+ "0xe2549E429B78458fa60BC7B1b284d4411E1D5105",
55
+ "0xCfdD747d041397bcE08B0Fe6ebF7Ef65E9F46795",
56
+ "0x9745969171a38B40db05c506fe2DA2C36f317627",
57
+ "0x6051C1354Ccc51b4d561e43b02735DEaE64768B8",
58
+ "0xf0814d0E47F2390a8082C4a1BD819FDDe50f9bFc",
59
+ "0x2DBd330bC9B7f3A822a9173aB52172BdDDcAcE2A",
60
+ # just andre tinkering
61
+ "0x5cB5e2d7Ab9Fd32021dF8F1D3E5269bD437Ec3Bf",
37
62
  # these arent shitcoins per se but we can't price them and dont expect to in the future, lets save cpu cycles
38
63
  "0x9d45DAb69f1309F1F55A7280b1f6a2699ec918E8", # yFamily NFT <3
39
64
  "0x57f1887a8BF19b14fC0dF6Fd9B2acc9Af147eA85", # ENS
40
65
  "0xD057B63f5E69CF1B929b356b579Cba08D7688048", # vCOW
41
66
  "0x4c1317326fD8EFDeBdBE5e1cd052010D97723bd6", # deprecated yCRV
42
67
  "0x8a0889d47f9Aa0Fac1cC718ba34E26b867437880", # deprecated st-yCRV
68
+ "0x55a290f08Bb4CAe8DcF1Ea5635A3FCfd4Da60456", # BITTO
69
+ "0x4770F3186225b1A6879983BD88C669CA55463886", # 69XMins
70
+ "0x05bAdF8A8e7fE5b43fae112108E26f2f663Bf1a2", # INUNOMICS
71
+ "0x3EF9181c9b96BAAafb3717A553E808Ccc72be37D", # MEMEPEPE
72
+ "0x5D22c8b4E3c90ca633e1377e6ca280a395fc61C0", # XMEME
73
+ "0x57f19E7e1A9066a741f59484481C4D2E9150e9E2", # MOCO
74
+ "0xb092D8E13Ba50963D57bEcB17a037728D883D02d", # BABYLABUBU
75
+ "0x4E51960bd33A6edA71a8B24A76311834BD98DD9f", # AICC
76
+ "0xB85485421d8a41C4648AB80bE1A725fA6b5Bc86d", # MEGA
77
+ "0x46D0Fb47b1e91130D53498EbeE7A968e7e6599f9", # Ghibli
78
+ "0x4FbB350052Bca5417566f188eB2EBCE5b19BC964", # GRG
79
+ "0x1f6DEb07E1a19bAfF90EC4075447eeF6eb96c0BA", # BABYMANYU
80
+ "0xD10EFABA11A51237fa120b15153DD432958bbDE3", # JIFFPOM
81
+ "0xCd9594cd25ED2a166362b6F76c523da08c4Ef2e5", # ESTHER
82
+ "0x16B907b5d1208Ae6086dE983a5EF45E7890eF272", # JUNFOX
83
+ "0xdE56173463d6461001B0891bC90DB224965f5762", # MAGNUS
84
+ "0x922824A5b3B3D5f4485fF52F4Ab7Cba5eA491874", # POSEIDON
85
+ "0x84F7D2f6FB447Bb11d3E7Ce64D83e1c02c2F3078", # VIRTUAL
86
+ "0x5C6Ed14E1017cf75C237A4A4b93Ce1D2f83EB002", # GRVT
87
+ "0xf76E6eFf109258fd5F52823d9F9feE7c90C97251", # wkeyDAO
43
88
  # test token?
44
89
  "0x372d5d02c6b4075bd58892f80300cA590e92d29E", # tOUSG
45
90
  # dETH? don't think this is needed
46
91
  "0x3d1E5Cf16077F349e999d6b21A4f646e83Cd90c5",
92
+ # ai shitcoin spam
93
+ "0xaf80B7dbeBbb5d9a4d33C453FcbF3d054DA53b25", # NODEPAY AI
94
+ "0xf960AbF9ccC883970BEa3E79f65027E27278e1A5", # ASK AI
95
+ "0xc136Eb8Abc892723aE87F355d12Cb721C4324D54", # Grok3
96
+ "0xc68bCEE3604F163f183Cf8B9a26E610E5961b037", # TESLA AI
97
+ "0xa65D56f8e074E773142205cD065FD0796B9aa483", # MASSIVE AI
98
+ "0x4e6c80aa486aF0ba20943Fbc067a5557DBcf5458", # SUNO AI
99
+ "0xC91223F844772dCdc2c6394585C8c30B3c1BE5C0", # SEND AI
100
+ "0x64b3336D1aDC6D3579e356760F53D3b018Cb11Bc", # ALC AI
101
+ "0x1495Ac869433698cCD2946c1e62D37bA766294A9", # NVIDIA AI PC
102
+ "0x8c0DF275c38092cd90Ae4d6147d8e619B3A24637", # COLLE AI
103
+ "0xe38f71fc2Ca5f5761cE21F39Fff2cE70662FA54c", # CHAINOPERA AI
104
+ "0xD2F89F59fBC7125b406e3F60A992DFa9FdB76524", # MISTRAL AI
105
+ "0xa0CCdBCeB5Da30F9d62F7A727F2B35C69dF08760", # CHUNK AI
106
+ "0x7CE31075d7450Aff4A9a82DdDF69D451B1e0C4E9", # DEEPSEEK AI
107
+ "0xf0f9C021AF9B6431FA59DAB75C8e6cB80c0dEa37", # TESLA AI
108
+ "0x635eeC65a7Ef10dCF96Bfe051D8A6e5960efe180", # KLING AI
109
+ "0xa3Efa0929569c15c20f89B591931899Fb05B4663", # GPT-5
110
+ "0x0A953979fdCfD82B08C77dB29705327BeC39ff13", # GROK4 AI
111
+ "0xc83377b9eE3CEe4Cc03CCd58AfdE1FB12864aEE3", # E AI
112
+ "0x927402ab67c0CDA3c187E9DFE34554AC581441f2", # SAITABIT
113
+ "0x691539810DF6e879A377C24CfEE130BBE92708d8", # NVIDIA AI
114
+ "0xdC82aC0A89197854cb2240FaBF7E7760a4fF4d9e", # NVIDIA
115
+ "0x5Fba8ea5A559CF5c99BA6dd884Ae17C1d621fE5B", # OSCAR AI
116
+ "0x57b055656460055192c8EAf616F90Ab76a32CC20", # Openx
117
+ # matt furry spam
118
+ "0x73228b3D33cC71cB721Fc62950577bE63bd9c8C9", # Maskman by Matt Furie
119
+ "0x7c28e66436C93BB9F657dDF2BA0eeeCf61369b92", # Bloodboy by Matt Furie
120
+ "0x70c5e1124569f17B1Be71E15833EaF1331f8727F", # Pac-hat by Matt Furie
121
+ "0xBd6555eC87C8A9a2280dCD6df45b9b074fC93Df2", # Bork by Matt Furie
122
+ # test token
123
+ "0x2F375Ce83EE85e505150d24E85A1742fd03cA593", # TEST
47
124
  ),
48
125
  }
126
+ """
127
+ Mapping of blockchain networks to tuples of token addresses that should be
128
+ ignored in Yearn Treasury analytics. These tokens are considered unpricable,
129
+ spam, or otherwise unwanted for reporting and analytics purposes.
130
+
131
+ Each tuple contains token contract addresses that will be excluded from
132
+ treasury calculations, reports, and dashboards for the corresponding network.
133
+ """
134
+
49
135
 
136
+ SHITCOINS: Final = {convert.to_address(shitcoin) for shitcoin in _SHITCOINS.get(CHAINID, ())} # type: ignore [call-overload]
137
+ """Set of checksummed token addresses to ignore for the current chain.
50
138
 
51
- SHITCOINS = {convert.to_address(shitcoin) for shitcoin in _SHITCOINS.get(CHAINID, ())} # type: ignore [call-overload]
139
+ This set is derived from the _SHITCOINS mapping for the current CHAINID,
140
+ and is used to filter out unpricable, spam, or otherwise unwanted tokens
141
+ from all Yearn Treasury analytics and reporting.
142
+ """
Binary file
yearn_treasury/vaults.py CHANGED
@@ -1,4 +1,18 @@
1
- from typing import Dict, Final, List
1
+ """
2
+ Vault discovery and tracking utilities for Yearn Treasury.
3
+
4
+ This module discovers Yearn vault contracts and maps them to their
5
+ underlying assets using Yearn's on-chain registry contracts. It
6
+ provides dictionaries for v1 and v2 vaults, supporting transaction
7
+ classification, analytics, and reporting across the Yearn Treasury
8
+ system.
9
+
10
+ Key Responsibilities:
11
+ - Discover and map all v1 and v2 vault contracts to underlying assets at startup.
12
+ - Provide lookup tables for use in vault deposit/withdrawal sort rules.
13
+ """
14
+
15
+ from typing import Dict, Final
2
16
 
3
17
  from brownie import chain
4
18
  from eth_typing import ChecksumAddress
@@ -7,36 +21,30 @@ from y import Contract, Events, Network
7
21
  from yearn_treasury._ens import resolver, topics
8
22
 
9
23
 
24
+ v1: Final[Dict[Contract, ChecksumAddress]] = {}
25
+ """Vault contract -> underlying address"""
26
+
27
+ v2: Final[Dict[ChecksumAddress, Contract]] = {}
28
+ """Vault address -> vault contract"""
29
+
30
+
10
31
  if chain.id == Network.Mainnet:
11
32
  _v1_addresses_provider = Contract("0x9be19Ee7Bc4099D62737a7255f5c227fBcd6dB93")
12
33
  _addresses_generator_v1_vaults = Contract(
13
34
  _v1_addresses_provider.addressById("ADDRESSES_GENERATOR_V1_VAULTS")
14
35
  )
15
36
 
16
- v1: Dict[Contract, ChecksumAddress] = {
17
- vault: vault.token() # type: ignore [misc]
18
- for vault in map(Contract, _addresses_generator_v1_vaults.assetsAddresses())
19
- }
37
+ for vault in map(Contract, _addresses_generator_v1_vaults.assetsAddresses()):
38
+ v1[vault] = vault.token()
20
39
 
21
40
  now = chain.height
22
41
 
42
+ # TODO: make resolve_ens util in eth-port and refactor this out
23
43
  v2_registries = [
24
- Contract(event["newAddress"].hex()) # type: ignore [attr-defined]
25
- for event in Events( # type: ignore [attr-defined]
26
- addresses=resolver, topics=topics
27
- ).events(now)
28
- ]
29
-
30
- v2: List[Contract] = [
31
- Contract(vault)
32
- for vault in {
33
- event["vault"]
34
- for event in Events(addresses=list(map(str, v2_registries))).events(now)
35
- if event.name == "NewVault"
36
- }
44
+ event["newAddress"].hex() for event in Events(addresses=resolver, topics=topics).events(now)
37
45
  ]
38
46
 
39
- else:
40
- v1 = {}
41
- v2 = []
42
- raise NotImplementedError(v2)
47
+ for event in Events(addresses=list(map(str, v2_registries))).events(now):
48
+ if event.name == "NewVault":
49
+ vault_address = event["vault"]
50
+ v2[vault_address] = Contract(vault_address)
@@ -0,0 +1,54 @@
1
+ # Treasury
2
+ "0x93A62dA5a14C80f265DAbC077fCEE437B1a0Efde":
3
+ networks:
4
+ - 1
5
+ "0x89716Ad7EDC3be3B35695789C475F3e7A3Deb12a":
6
+ networks:
7
+ - 250
8
+
9
+ # yChad
10
+ "0xFEB4acf3df3cDEA7399794D0869ef76A6EfAff52":
11
+ networks:
12
+ - 1
13
+ "0xC0E2830724C946a6748dDFE09753613cd38f6767":
14
+ networks:
15
+ - 250
16
+
17
+ # Yearn Treasury V1
18
+ "0xb99a40fcE04cb740EB79fC04976CA15aF69AaaaE":
19
+ networks:
20
+ - 1
21
+
22
+ # Yearn KP3R Wallet
23
+ "0x5f0845101857d2A91627478e302357860b1598a1":
24
+
25
+ # ySwap Multisig
26
+ "0x7d2aB9CA511EBD6F03971Fb417d3492aA82513f0":
27
+
28
+ # yMechs Multisig
29
+ "0x2C01B4AD51a67E2d8F02208F54dF9aC4c0B778B6":
30
+ end:
31
+ block:
32
+ 1: 17162286
33
+
34
+ # Fee Reimbursement Stash
35
+ "0xE376e8e8E3B0793CD61C6F1283bA18548b726C2e":
36
+
37
+ # New token dumping wallet
38
+ "0xC001d00d425Fa92C4F840baA8f1e0c27c4297a0B":
39
+
40
+ # veFarming wallet
41
+ "0x4fc1b14cD213e7B6212145Ba4f180C3d53d1A11e":
42
+
43
+ ### yRoboTreasury Wallets ###
44
+ # https://github.com/yearn/yRoboTreasury/blob/master/deployment.json
45
+
46
+ # Treasury
47
+ "0xEf77cc176c748d291EfB6CdC982c5744fC7211c8":
48
+ networks:
49
+ - 1
50
+
51
+ # Stables reserve
52
+ "0x278374fFb10B7D16E7633444c13e6E565EA57c28":
53
+ networks:
54
+ - 1
@@ -0,0 +1,208 @@
1
+ # This is a loose copy of an old script and will not likely be refactored into something pretty
2
+ import os
3
+ from datetime import datetime, timedelta, timezone
4
+ from decimal import Decimal
5
+ from functools import lru_cache
6
+ from logging import getLogger
7
+ from typing import Any, Dict, Final, List, Set, Tuple
8
+
9
+ import a_sync
10
+ from brownie import chain
11
+ from eth_portfolio.structs import TokenTransfer
12
+ from eth_portfolio._ydb.token_transfers import InboundTokenTransfers
13
+ from pandas import DataFrame, MultiIndex
14
+ from y import Contract, Network, get_block_at_timestamp
15
+ from y.exceptions import ContractNotVerified
16
+
17
+ from yearn_treasury.constants import ZERO_ADDRESS
18
+
19
+
20
+ DATA_FOLDER: Final = os.path.join(".", "data")
21
+ OUTPUT_FILE: Final = os.path.join(DATA_FOLDER, f"teams_revenue_{chain.id}.csv")
22
+ NUMBER_OF_MONTHS_TO_INCLUDE_IN_REPORT: Final = 36
23
+
24
+ if not os.path.exists(DATA_FOLDER):
25
+ os.makedirs(DATA_FOLDER)
26
+
27
+
28
+ # TODO: maybe move this into constants for reuse
29
+ yteams_addresses = {
30
+ Network.Mainnet: {
31
+ "v3": {
32
+ "ms": "0x33333333D5eFb92f19a5F94a43456b3cec2797AE",
33
+ "splits": {"0x2A12CAA2c13Af03c117D836CA3811a5Ca946133B": 12.5},
34
+ },
35
+ "dinobots": {
36
+ "ms": "0x2C01B4AD51a67E2d8F02208F54dF9aC4c0B778B6",
37
+ "splits": {"0xC4f238633A85A854C4702d2c66264771D1fa7904": 17.5},
38
+ },
39
+ "ylockers": {
40
+ "ms": "0x4444AAAACDBa5580282365e25b16309Bd770ce4a",
41
+ "splits": {
42
+ "0xac580302548FCCBBf00020de20C3A8AA516821AD": 2.5,
43
+ "0x794f80E899c772de9E326eC83cCfD8D94e208B49": 6.25,
44
+ "0x5FF0f87b05806ce89967638CA727Af8309d92A89": 12.5,
45
+ "0x5A7575368797695BefD785f546C6b8B7e9D37f8c": 15.625,
46
+ },
47
+ },
48
+ # "ylockers others": {"ms": "0x4444AAAACDBa5580282365e25b16309Bd770ce4a","splits":{"0x5FF0f87b05806ce89967638CA727Af8309d92A89":12.5, "0x5A7575368797695BefD785f546C6b8B7e9D37f8c":15.625}},
49
+ "yaudit": {
50
+ "ms": "0x8973B848775a87a0D5bcf262C555859b87E6F7dA",
51
+ "splits": {
52
+ "0xd7A1DBe236A38528D54317415a530b2326068373": 35,
53
+ "0xF104F38592287e25868BD8C3dcCCa1a311916f88": 35,
54
+ "0x1a9D272C3b7fE427639702A332D51348213B0bC1": 20,
55
+ },
56
+ },
57
+ "yeth": {
58
+ "ms": "0xeEEEEeeeEe274C3CCe13f77C85d8eBd9F7fd4479",
59
+ "splits": {"0x14EFe6390C6758E3fE4379A14e3B329274b1b072": 25},
60
+ },
61
+ "yfarm": {
62
+ "ms": "0x55157997cb324a374cCd7c40914ff879Fd9D515C",
63
+ "splits": {"0x0B3cCe59E038373F6008D9266B6D6eB4d21689b1": 50},
64
+ },
65
+ "sms": {
66
+ "ms": "0x16388463d60FFE0661Cf7F1f31a7D658aC790ff7",
67
+ "splits": {"0xd6748776CF06a80EbE36cd83D325B31bb916bf54": 25},
68
+ },
69
+ }
70
+ }[Network(chain.id)]
71
+
72
+
73
+ logger: Final = getLogger(__name__)
74
+
75
+ _not_verified: Final[Set[str]] = set()
76
+ _warned: Final[Set[TokenTransfer]] = set()
77
+
78
+ _known_tokens_without_prices: Final = frozenset({"SAFE", "vCOW"})
79
+ """When there is a PriceError for these tokens, no logs will be emitted."""
80
+
81
+
82
+ @lru_cache(maxsize=None)
83
+ def transfers_for(wallet: str) -> InboundTokenTransfers:
84
+ return InboundTokenTransfers(wallet, 0, load_prices=True) # type: ignore [arg-type]
85
+
86
+
87
+ async def calculate_teams_revenue_expenses() -> None:
88
+ logger.info("Starting process to calculate teams revenues and expenses")
89
+ timestamps = get_timestamps_for_report()
90
+
91
+ async def get_coros_for_timestamp(dt: datetime) -> Dict[str, Dict[str, Decimal]]:
92
+ return await a_sync.gather(
93
+ {
94
+ label: total(label, wallet_info, dt)
95
+ for label, wallet_info in yteams_addresses.items()
96
+ }
97
+ )
98
+
99
+ all_data = await a_sync.gather({dt: get_coros_for_timestamp(dt) for dt in timestamps})
100
+
101
+ result = {
102
+ (dt, teams, movement): values
103
+ for dt, data in all_data.items()
104
+ for teams, info in data.items()
105
+ for movement, values in info.items()
106
+ }
107
+ df = DataFrame.from_dict(result, orient="index")
108
+ print("------------")
109
+ # print(df.index)
110
+ # print(df.head(10))
111
+ df.index = MultiIndex.from_tuples(df.index)
112
+ print("********")
113
+ # print(df.index)
114
+ df.reset_index(inplace=True)
115
+ df.columns = ["datetime", "team", "label", "value"]
116
+ df.to_csv(OUTPUT_FILE)
117
+ logger.info(
118
+ f"Finished processing yteams calculations and saved file to {os.path.abspath(OUTPUT_FILE)}"
119
+ )
120
+
121
+
122
+ def get_timestamps_for_report() -> List[datetime]:
123
+ now = datetime.now(tz=timezone.utc)
124
+ prev_month_end = datetime(
125
+ year=now.year,
126
+ month=now.month,
127
+ day=1,
128
+ hour=0,
129
+ minute=0,
130
+ second=0,
131
+ microsecond=0,
132
+ tzinfo=timezone.utc,
133
+ ) - timedelta(microseconds=1)
134
+ datetimes = []
135
+ print("Exporting report for timestamps:")
136
+ for _ in range(NUMBER_OF_MONTHS_TO_INCLUDE_IN_REPORT):
137
+ print(f" - {prev_month_end}")
138
+ datetimes.append(prev_month_end)
139
+ prev_month_end -= timedelta(days=prev_month_end.day)
140
+ return datetimes
141
+
142
+
143
+ async def total(label: str, wallet_info: Dict[str, Any], timestamp: datetime) -> Dict[str, Decimal]:
144
+ rev = await sum_revenue_transfers.sum(wallet_info["splits"].items(), timestamp=timestamp)
145
+ grants = await sum_grants_received(wallet_info["ms"], timestamp)
146
+ if rev > 10_000_000:
147
+ raise ValueError(rev)
148
+ if grants > 10_000_000:
149
+ raise ValueError(grants)
150
+ net = rev - grants
151
+ if label == "yaudit":
152
+ logger.info("--- %s thru %s ---", label, timestamp)
153
+ logger.info("inbound %s", rev)
154
+ logger.info("grants -%s", grants)
155
+ logger.info("net %s", net)
156
+ return {"revenue": rev, "grants": grants, "total": net}
157
+
158
+
159
+ @a_sync.a_sync(default="async")
160
+ async def sum_revenue_transfers(params: Tuple[str, Decimal], timestamp: datetime) -> Decimal:
161
+ wallet, rev_share = params
162
+ block = await get_block_at_timestamp(timestamp)
163
+ total = Decimal(0)
164
+ async for transfer in transfers_for(wallet).yield_thru_block(block):
165
+ transfer = await transfer
166
+ if transfer is None:
167
+ # failed to decode, probably shitcoin
168
+ continue
169
+ if not transfer.value:
170
+ # zero value transfer
171
+ continue
172
+ if transfer.price:
173
+ total += transfer.value * transfer.price
174
+ elif transfer not in _warned and transfer.token not in _known_tokens_without_prices:
175
+ logger.warning(f"BAD: {transfer}")
176
+ _warned.add(transfer)
177
+ return round(total * Decimal((100 - rev_share) / 100), 8)
178
+
179
+
180
+ async def sum_grants_received(wallet: str, timestamp: datetime) -> Decimal:
181
+ grants = Decimal(0)
182
+ block = await get_block_at_timestamp(timestamp)
183
+ async for transfer in transfers_for(wallet).yield_thru_block(block):
184
+ transfer = await transfer
185
+ if transfer is None:
186
+ # failed to decode, probably shitcoin
187
+ continue
188
+ if not transfer.value:
189
+ # zero value transfer
190
+ continue
191
+ if transfer.price:
192
+ if transfer.from_address != ZERO_ADDRESS:
193
+ try:
194
+ contract = await Contract.coroutine(transfer.from_address)
195
+ if (
196
+ hasattr(contract, "recipient") and await contract.recipient == wallet
197
+ ) or transfer.from_address == "0xFEB4acf3df3cDEA7399794D0869ef76A6EfAff52":
198
+ grants += transfer.value * transfer.price
199
+ except ContractNotVerified as e:
200
+ if str(e) not in _not_verified:
201
+ _not_verified.add(str(e))
202
+ logger.debug(f"{e.__class__.__name__}: {e}")
203
+ except Exception as e:
204
+ logger.warning(f"{e.__class__.__name__}: {e}")
205
+ elif transfer not in _warned and transfer.token not in _known_tokens_without_prices:
206
+ logger.warning(f"BAD: {transfer}")
207
+ _warned.add(transfer)
208
+ return round(grants, 8)