yearn-treasury 0.0.20__cp311-cp311-macosx_11_0_arm64.whl → 0.0.49__cp311-cp311-macosx_11_0_arm64.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 (99) hide show
  1. yearn_treasury/__init__.py +2 -0
  2. yearn_treasury/_db.cpython-311-darwin.so +0 -0
  3. yearn_treasury/_db.py +21 -0
  4. yearn_treasury/_ens.cpython-311-darwin.so +0 -0
  5. yearn_treasury/_ens.py +14 -0
  6. yearn_treasury/_logging.cpython-311-darwin.so +0 -0
  7. yearn_treasury/_logging.py +43 -0
  8. yearn_treasury/address_labels.yaml +10 -0
  9. yearn_treasury/budget/__init__.cpython-311-darwin.so +0 -0
  10. yearn_treasury/budget/__init__.py +2 -2
  11. yearn_treasury/budget/_request.cpython-311-darwin.so +0 -0
  12. yearn_treasury/budget/_request.py +8 -0
  13. yearn_treasury/budget/_requests.cpython-311-darwin.so +0 -0
  14. yearn_treasury/budget/_requests.py +44 -17
  15. yearn_treasury/constants.py +20 -1
  16. yearn_treasury/main.py +54 -20
  17. yearn_treasury/rules/__init__.py +14 -0
  18. yearn_treasury/rules/constants.cpython-311-darwin.so +0 -0
  19. yearn_treasury/rules/cost_of_revenue/gas.cpython-311-darwin.so +0 -0
  20. yearn_treasury/rules/cost_of_revenue/gas.py +9 -1
  21. yearn_treasury/rules/expense/__init__.cpython-311-darwin.so +0 -0
  22. yearn_treasury/rules/expense/general.cpython-311-darwin.so +0 -0
  23. yearn_treasury/rules/expense/infrastructure.cpython-311-darwin.so +0 -0
  24. yearn_treasury/rules/expense/infrastructure.py +7 -0
  25. yearn_treasury/rules/expense/people.cpython-311-darwin.so +0 -0
  26. yearn_treasury/rules/expense/people.py +43 -0
  27. yearn_treasury/rules/expense/security.cpython-311-darwin.so +0 -0
  28. yearn_treasury/rules/expense/security.py +17 -0
  29. yearn_treasury/rules/ignore/general.cpython-311-darwin.so +0 -0
  30. yearn_treasury/rules/ignore/maker.py +36 -24
  31. yearn_treasury/rules/ignore/passthru.py +154 -11
  32. yearn_treasury/rules/ignore/swaps/__init__.py +4 -0
  33. yearn_treasury/rules/ignore/swaps/aave.py +26 -10
  34. yearn_treasury/rules/ignore/swaps/auctions.cpython-311-darwin.so +0 -0
  35. yearn_treasury/rules/ignore/swaps/auctions.py +31 -0
  36. yearn_treasury/rules/ignore/swaps/compound.py +32 -22
  37. yearn_treasury/rules/ignore/swaps/conversion_factory.cpython-311-darwin.so +0 -0
  38. yearn_treasury/rules/ignore/swaps/conversion_factory.py +21 -0
  39. yearn_treasury/rules/ignore/swaps/cowswap.py +16 -13
  40. yearn_treasury/rules/ignore/swaps/curve.py +54 -16
  41. yearn_treasury/rules/ignore/swaps/gearbox.cpython-311-darwin.so +0 -0
  42. yearn_treasury/rules/ignore/swaps/gearbox.py +8 -0
  43. yearn_treasury/rules/ignore/swaps/iearn.cpython-311-darwin.so +0 -0
  44. yearn_treasury/rules/ignore/swaps/iearn.py +4 -4
  45. yearn_treasury/rules/ignore/swaps/{curve.cpython-311-darwin.so → otc.cpython-311-darwin.so} +0 -0
  46. yearn_treasury/rules/ignore/swaps/otc.py +58 -0
  47. yearn_treasury/rules/ignore/swaps/pooltogether.cpython-311-darwin.so +0 -0
  48. yearn_treasury/rules/ignore/swaps/pooltogether.py +7 -0
  49. yearn_treasury/rules/ignore/swaps/synthetix.cpython-311-darwin.so +0 -0
  50. yearn_treasury/rules/ignore/swaps/uniswap.py +29 -6
  51. yearn_treasury/rules/ignore/swaps/unwrapper.cpython-311-darwin.so +0 -0
  52. yearn_treasury/rules/ignore/swaps/unwrapper.py +8 -0
  53. yearn_treasury/rules/ignore/swaps/vaults.cpython-311-darwin.so +0 -0
  54. yearn_treasury/rules/ignore/swaps/vaults.py +13 -11
  55. yearn_treasury/rules/ignore/swaps/woofy.cpython-311-darwin.so +0 -0
  56. yearn_treasury/rules/ignore/swaps/woofy.py +80 -0
  57. yearn_treasury/rules/ignore/swaps/yfi.cpython-311-darwin.so +0 -0
  58. yearn_treasury/rules/ignore/swaps/yfi.py +59 -7
  59. yearn_treasury/rules/ignore/swaps/yla.cpython-311-darwin.so +0 -0
  60. yearn_treasury/rules/ignore/unit.cpython-311-darwin.so +0 -0
  61. yearn_treasury/rules/ignore/unit.py +6 -1
  62. yearn_treasury/rules/ignore/weth.cpython-311-darwin.so +0 -0
  63. yearn_treasury/rules/ignore/weth.py +8 -0
  64. yearn_treasury/rules/ignore/ygov.cpython-311-darwin.so +0 -0
  65. yearn_treasury/rules/other_expense/__init__.cpython-311-darwin.so +0 -0
  66. yearn_treasury/rules/other_expense/boost.cpython-311-darwin.so +0 -0
  67. yearn_treasury/rules/other_expense/bugs.cpython-311-darwin.so +0 -0
  68. yearn_treasury/rules/other_expense/donations.cpython-311-darwin.so +0 -0
  69. yearn_treasury/rules/other_expense/donations.py +8 -0
  70. yearn_treasury/rules/other_expense/dyfi.cpython-311-darwin.so +0 -0
  71. yearn_treasury/rules/other_expense/events.cpython-311-darwin.so +0 -0
  72. yearn_treasury/rules/other_expense/misc.cpython-311-darwin.so +0 -0
  73. yearn_treasury/rules/other_expense/misc.py +8 -0
  74. yearn_treasury/rules/other_expense/revshare.cpython-311-darwin.so +0 -0
  75. yearn_treasury/rules/other_income/__init__.cpython-311-darwin.so +0 -0
  76. yearn_treasury/rules/other_income/__init__.py +2 -100
  77. yearn_treasury/rules/other_income/airdrops.cpython-311-darwin.so +0 -0
  78. yearn_treasury/rules/other_income/airdrops.py +30 -0
  79. yearn_treasury/rules/other_income/misc.cpython-311-darwin.so +0 -0
  80. yearn_treasury/rules/other_income/misc.py +80 -0
  81. yearn_treasury/rules/revenue/bribes.cpython-311-darwin.so +0 -0
  82. yearn_treasury/rules/revenue/farming.cpython-311-darwin.so +0 -0
  83. yearn_treasury/rules/revenue/keepcoins.cpython-311-darwin.so +0 -0
  84. yearn_treasury/rules/revenue/seasolver.cpython-311-darwin.so +0 -0
  85. yearn_treasury/rules/revenue/vaults.py +14 -10
  86. yearn_treasury/rules/revenue/yteams.cpython-311-darwin.so +0 -0
  87. yearn_treasury/shitcoins.py +61 -2
  88. yearn_treasury/vaults.cpython-311-darwin.so +0 -0
  89. yearn_treasury/vaults.py +17 -4
  90. yearn_treasury/yteams.py +208 -0
  91. {yearn_treasury-0.0.20.dist-info → yearn_treasury-0.0.49.dist-info}/METADATA +3 -3
  92. yearn_treasury-0.0.49.dist-info/RECORD +128 -0
  93. yearn_treasury-0.0.49.dist-info/top_level.txt +2 -0
  94. yearn_treasury__mypyc.cpython-311-darwin.so +0 -0
  95. de0b3d5d46e8d426aa59__mypyc.cpython-311-darwin.so +0 -0
  96. yearn_treasury-0.0.20.dist-info/RECORD +0 -113
  97. yearn_treasury-0.0.20.dist-info/top_level.txt +0 -2
  98. {yearn_treasury-0.0.20.dist-info → yearn_treasury-0.0.49.dist-info}/WHEEL +0 -0
  99. {yearn_treasury-0.0.20.dist-info → yearn_treasury-0.0.49.dist-info}/entry_points.txt +0 -0
@@ -1,3 +1,11 @@
1
+ """
2
+ Other expense rules for miscellaneous cases in Yearn Treasury.
3
+
4
+ This module defines matching logic for miscellaneous other expenses.
5
+ If it doesn't really fit anywhere else in :mod:`~other_expenses`,
6
+ it will end up in here.
7
+ """
8
+
1
9
  from dao_treasury import TreasuryTx, other_expense
2
10
  from y import Network
3
11
 
@@ -1,100 +1,2 @@
1
- from decimal import Decimal
2
- from typing import Final
3
-
4
- from dao_treasury import TreasuryTx, other_income
5
- from y import Contract, ContractNotVerified, ERC20, Network # type: ignore [attr-defined]
6
-
7
- from yearn_treasury.rules.constants import ZERO_ADDRESS
8
-
9
-
10
- _POINT_ONE: Final = Decimal("0.1")
11
-
12
-
13
- @other_income("Airdrop", Network.Mainnet)
14
- def is_airdrop(tx: TreasuryTx) -> bool:
15
- txhash = tx.hash
16
- return txhash in {
17
- "0x327684dab9e3ce61d125b36fe0b59cbfbc8aa5ac7a5b051125ab7cac3b93b90b",
18
- "0x3424e8a6688c89f7974968213c8c25f3bd8100f78c54475edb001c11a8ad5d21", # Gnosis SAFE airdrop
19
- "0xb39f2991fdc2c70b43046be3eac36bff35c21c7f66e2888a52afc3956abae451", # Gnosis SAFE airdrop
20
- "0x4923fd32b4eacdc1617700c67176935676ca4d06bbfbb73644730c55534623db", # Gnosis SAFE airdrop
21
- "0x5ba604cae0d355835b182fa23c8a58ae695905e69ed08c7cf8a52f3eca889484", # Gnosis SAFE airdrop
22
- "0x44f7d3b2030799ea45932baf6049528a059aabd6387f3128993d646d01c8e877", # TKX
23
- "0xf2dbe58dffd3bc1476755e9f74e2ae07531579d0a3ea9e2aaac2ef902e080c2a", # TKX
24
- "0x8079e9cae847da196dc5507561bc9d1434f765f05045bc1a82df735ec83bc6ec", # MTV
25
- "0x037a9cc5baa7d63a11d0f0720ee552bbf4ad85118ee5425220a263695fedbe9f", # Gnosis SAFE airdrop
26
- # NOTE: this one was rec'd elsewhere, dumped, and WETH sent to treasury
27
- "0xc12ded505ea158717890e4ae6e7ab5eb5cb61edbc13dfd125dd0e6f9b1af9477", # Gnosis SAFE airdrop
28
- "0x7c086a82b43b2f49db93b76a0698cf86a9c620b3bf924f0003175b04a17455ad", # PRISMA
29
- } or (
30
- # Gnosis SAFE airdrop
31
- txhash == "0xe8b5a4ebf1f04048f6226b22b2865a33621e88ea255dcea0cfd7a975a3a7e387"
32
- and tx.log_index == 72
33
- )
34
-
35
-
36
- @other_income("aToken Yield", Network.Mainnet)
37
- def is_atoken_yield(tx: TreasuryTx) -> bool:
38
- return (
39
- tx.symbol in ("aLEND", "aLINK")
40
- and tx.from_address.address == ZERO_ADDRESS
41
- and tx.to_nickname in ("Yearn Treasury", "Yearn Treasury V1")
42
- )
43
-
44
-
45
- @other_income("RoboVault Thank You", Network.Fantom)
46
- async def is_robovault_share(tx: TreasuryTx) -> bool:
47
- """
48
- After Yearn devs helped robovault with a vulnerability, robovault committed to sending Yearn a portion of their fees.
49
- """
50
- if not tx.symbol.startswith("rv") and tx.from_address.is_contract:
51
- return False
52
-
53
- try:
54
- strat = await Contract.coroutine(tx.from_address.address)
55
- except ContractNotVerified:
56
- return False
57
-
58
- if not hasattr(strat, "vault"):
59
- return False
60
-
61
- if await strat.vault.coroutine(block_identifier=tx.block) == tx.token:
62
- return True
63
-
64
- return (
65
- tx.from_nickname == "Contract: Strategy"
66
- and tx.symbol == "rv3USDCc"
67
- and await ERC20( # type: ignore [call-overload]
68
- await strat.vault.coroutine(block_identifier=tx.block),
69
- asynchronous=True,
70
- ).symbol
71
- == "rv3USDCb"
72
- )
73
-
74
-
75
- @other_income("Cowswap Gas Reimbursement", Network.Mainnet)
76
- def is_cowswap_gas_reimbursement(tx: TreasuryTx) -> bool:
77
- return (
78
- tx.symbol == "ETH"
79
- and tx.from_nickname == "Cowswap Multisig"
80
- and tx.to_nickname == "yMechs Multisig"
81
- )
82
-
83
-
84
- @other_income("USDS Referral Code", Network.Mainnet)
85
- def is_usds_referral_code(tx: TreasuryTx) -> bool:
86
- """Yearn earns some USDS for referring deposits to Maker"""
87
- return (
88
- tx.symbol == "USDS"
89
- and tx.from_address.address == "0x3C5142F28567E6a0F172fd0BaaF1f2847f49D02F"
90
- )
91
-
92
-
93
- @other_income("yETH Application Fee", Network.Mainnet)
94
- def is_yeth_application_fee(tx: TreasuryTx) -> bool:
95
- return tx.symbol == "yETH" and tx.to_nickname == "Yearn Treasury" and tx.amount == _POINT_ONE
96
-
97
-
98
- @other_income("yPRISMA Fees", Network.Mainnet)
99
- def is_yprisma_fees(tx: TreasuryTx) -> bool:
100
- return tx.symbol == "yvmkUSD-A" and tx.from_nickname == "Contract: YPrismaFeeDistributor"
1
+ from yearn_treasury.rules.other_income.airdrops import *
2
+ from yearn_treasury.rules.other_income.misc import *
@@ -0,0 +1,30 @@
1
+ from typing import Final
2
+
3
+ from dao_treasury import TreasuryTx, other_income
4
+ from y import Network
5
+
6
+
7
+ airdrop: Final = other_income("Airdrop")
8
+
9
+ _SAFE_AIRDROP_CONTRACTS: Final = (
10
+ "0xA0b937D5c8E32a80E3a8ed4227CD020221544ee6",
11
+ "0xC0fde70A65C7569Fe919bE57492228DEE8cDb585",
12
+ )
13
+
14
+
15
+ @airdrop("SAFE", Network.Mainnet)
16
+ def is_safe_airdrop(tx: TreasuryTx) -> bool:
17
+ return tx.symbol == "SAFE" and tx.from_address.address in _SAFE_AIRDROP_CONTRACTS # type: ignore [union-attr]
18
+
19
+
20
+ @airdrop("Other", Network.Mainnet)
21
+ def is_airdrop(tx: TreasuryTx) -> bool:
22
+ return tx.hash in {
23
+ "0x327684dab9e3ce61d125b36fe0b59cbfbc8aa5ac7a5b051125ab7cac3b93b90b",
24
+ "0x44f7d3b2030799ea45932baf6049528a059aabd6387f3128993d646d01c8e877", # TKX
25
+ "0xf2dbe58dffd3bc1476755e9f74e2ae07531579d0a3ea9e2aaac2ef902e080c2a", # TKX
26
+ "0x8079e9cae847da196dc5507561bc9d1434f765f05045bc1a82df735ec83bc6ec", # MTV
27
+ # NOTE: this one was rec'd elsewhere, dumped, and WETH sent to treasury
28
+ "0xc12ded505ea158717890e4ae6e7ab5eb5cb61edbc13dfd125dd0e6f9b1af9477", # Gnosis SAFE airdrop
29
+ "0x7c086a82b43b2f49db93b76a0698cf86a9c620b3bf924f0003175b04a17455ad", # PRISMA
30
+ }
@@ -0,0 +1,80 @@
1
+ # mypy: disable-error-code="union-attr"
2
+ from decimal import Decimal
3
+ from typing import Final, Optional
4
+
5
+ from dao_treasury import TreasuryTx, other_income
6
+ from y import Contract, ContractNotVerified, ERC20, Network # type: ignore [attr-defined]
7
+
8
+ from yearn_treasury.rules.constants import ZERO_ADDRESS
9
+
10
+
11
+ _POINT_ONE: Final = Decimal("0.1")
12
+
13
+
14
+ @other_income("aToken Yield", Network.Mainnet)
15
+ def is_atoken_yield(tx: TreasuryTx) -> bool:
16
+ return (
17
+ tx.symbol in ("aLEND", "aLINK")
18
+ and tx.from_address.address == ZERO_ADDRESS
19
+ and tx.to_nickname in ("Yearn Treasury", "Yearn Treasury V1")
20
+ )
21
+
22
+
23
+ @other_income("RoboVault Thank You", Network.Fantom)
24
+ async def is_robovault_share(tx: TreasuryTx) -> bool:
25
+ """
26
+ After Yearn devs helped robovault with a vulnerability, robovault committed to sending Yearn a portion of their fees.
27
+ """
28
+ if not tx.symbol.startswith("rv") and tx.from_address.is_contract:
29
+ return False
30
+
31
+ try:
32
+ strat = await tx.from_address.contract_coro
33
+ except ContractNotVerified:
34
+ return False
35
+ else:
36
+ vault: Optional[Contract] = getattr(strat, "vault", None)
37
+
38
+ if vault is None:
39
+ return False
40
+
41
+ if await vault.coroutine(block_identifier=tx.block) == tx.token:
42
+ return True
43
+
44
+ return (
45
+ tx.from_nickname == "Contract: Strategy"
46
+ and tx.symbol == "rv3USDCc"
47
+ and await ERC20( # type: ignore [call-overload]
48
+ await vault.coroutine(block_identifier=tx.block),
49
+ asynchronous=True,
50
+ ).symbol
51
+ == "rv3USDCb"
52
+ )
53
+
54
+
55
+ @other_income("Cowswap Gas Reimbursement", Network.Mainnet)
56
+ def is_cowswap_gas_reimbursement(tx: TreasuryTx) -> bool:
57
+ return (
58
+ tx.symbol == "ETH"
59
+ and tx.from_nickname == "Cowswap Multisig"
60
+ and tx.to_nickname == "yMechs Multisig"
61
+ )
62
+
63
+
64
+ @other_income("USDS Referral Code", Network.Mainnet)
65
+ def is_usds_referral_code(tx: TreasuryTx) -> bool:
66
+ """Yearn earns some USDS for referring deposits to Maker"""
67
+ return (
68
+ tx.symbol == "USDS"
69
+ and tx.from_address.address == "0x3C5142F28567E6a0F172fd0BaaF1f2847f49D02F"
70
+ )
71
+
72
+
73
+ @other_income("yETH Application Fee", Network.Mainnet)
74
+ def is_yeth_application_fee(tx: TreasuryTx) -> bool:
75
+ return tx.symbol == "yETH" and tx.to_nickname == "Yearn Treasury" and tx.amount == _POINT_ONE
76
+
77
+
78
+ @other_income("yPRISMA Fees", Network.Mainnet)
79
+ def is_yprisma_fees(tx: TreasuryTx) -> bool:
80
+ return tx.symbol == "yvmkUSD-A" and tx.from_nickname == "Contract: YPrismaFeeDistributor"
@@ -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
@@ -55,7 +56,7 @@ async def _get_controller(vault: Contract) -> Contract:
55
56
 
56
57
  @fees("Vaults V1", Network.Mainnet)
57
58
  async def is_v1_vault_fees(tx: TreasuryTx) -> bool:
58
- token = tx.token.address.address
59
+ token = tx.token_address
59
60
 
60
61
  # Fees from single-sided strategies are not denominated in `vault.token` but everything else is
61
62
  is_single_sided = _is_single_sided(tx)
@@ -85,7 +86,9 @@ async def is_v1_vault_fees(tx: TreasuryTx) -> bool:
85
86
  return False
86
87
 
87
88
 
88
- def is_inverse_fees_from_stash_contract(from_address: str, to_nickname: str) -> bool:
89
+ def is_inverse_fees_from_stash_contract(
90
+ from_address: ChecksumAddress, to_nickname: str | None
91
+ ) -> bool:
89
92
  return (
90
93
  from_address == "0xE376e8e8E3B0793CD61C6F1283bA18548b726C2e"
91
94
  and to_nickname == "Token: Curve stETH Pool yVault"
@@ -94,18 +97,19 @@ def is_inverse_fees_from_stash_contract(from_address: str, to_nickname: str) ->
94
97
 
95
98
  @fees("Vaults V2")
96
99
  async def is_v2_vault_fees(tx: TreasuryTx) -> bool:
97
- token = tx.token.address.address
98
- from_address = tx.from_address.address
100
+ token = tx.token_address
101
+ from_address = cast(ChecksumAddress, tx.from_address.address)
99
102
  if (
100
103
  from_address == token
101
- and (vault := v2.get(from_address)) # type: ignore [arg-type]
104
+ and (vault := v2.get(from_address))
102
105
  and tx.to_address == await vault.rewards.coroutine(block_identifier=tx.block)
103
106
  ):
104
107
  return True
105
108
 
106
- if is_inverse_fees_from_stash_contract(from_address, tx.to_nickname): # type: ignore [arg-type]
107
- if tx.value_usd > 0: # type: ignore [operator]
108
- 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
109
113
  return True
110
114
 
111
115
  return False
@@ -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,6 +50,13 @@ _SHITCOINS = {
34
50
  "0xe5868468Cb6Dd5d6D7056bd93f084816c6eF075f",
35
51
  "0x0a24Bb4842c301276c65086B5d78D5C872993c72",
36
52
  "0x63125c0d5Cd9071de9A1ac84c400982f41C697AE",
53
+ "0x4d22921215cF37e8d49e2Ac6d1F5e9672f63A7c6",
54
+ "0xe2549E429B78458fa60BC7B1b284d4411E1D5105",
55
+ "0xCfdD747d041397bcE08B0Fe6ebF7Ef65E9F46795",
56
+ "0x9745969171a38B40db05c506fe2DA2C36f317627",
57
+ "0x6051C1354Ccc51b4d561e43b02735DEaE64768B8",
58
+ "0xf0814d0E47F2390a8082C4a1BD819FDDe50f9bFc",
59
+ "0x2DBd330bC9B7f3A822a9173aB52172BdDDcAcE2A",
37
60
  # just andre tinkering
38
61
  "0x5cB5e2d7Ab9Fd32021dF8F1D3E5269bD437Ec3Bf",
39
62
  # these arent shitcoins per se but we can't price them and dont expect to in the future, lets save cpu cycles
@@ -50,6 +73,18 @@ _SHITCOINS = {
50
73
  "0x57f19E7e1A9066a741f59484481C4D2E9150e9E2", # MOCO
51
74
  "0xb092D8E13Ba50963D57bEcB17a037728D883D02d", # BABYLABUBU
52
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
53
88
  # test token?
54
89
  "0x372d5d02c6b4075bd58892f80300cA590e92d29E", # tOUSG
55
90
  # dETH? don't think this is needed
@@ -69,6 +104,16 @@ _SHITCOINS = {
69
104
  "0xD2F89F59fBC7125b406e3F60A992DFa9FdB76524", # MISTRAL AI
70
105
  "0xa0CCdBCeB5Da30F9d62F7A727F2B35C69dF08760", # CHUNK AI
71
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
72
117
  # matt furry spam
73
118
  "0x73228b3D33cC71cB721Fc62950577bE63bd9c8C9", # Maskman by Matt Furie
74
119
  "0x7c28e66436C93BB9F657dDF2BA0eeeCf61369b92", # Bloodboy by Matt Furie
@@ -78,6 +123,20 @@ _SHITCOINS = {
78
123
  "0x2F375Ce83EE85e505150d24E85A1742fd03cA593", # TEST
79
124
  ),
80
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
+
81
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.
82
138
 
83
- 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
@@ -27,11 +41,10 @@ if chain.id == Network.Mainnet:
27
41
 
28
42
  # TODO: make resolve_ens util in eth-port and refactor this out
29
43
  v2_registries = [
30
- event["newAddress"].hex() # type: ignore [attr-defined]
31
- for event in Events(addresses=resolver, topics=topics).events(now)
44
+ event["newAddress"].hex() for event in Events(addresses=resolver, topics=topics).events(now)
32
45
  ]
33
46
 
34
47
  for event in Events(addresses=list(map(str, v2_registries))).events(now):
35
48
  if event.name == "NewVault":
36
49
  vault_address = event["vault"]
37
- v2[vault_address] = Contract(vault_address) # type: ignore [index]
50
+ v2[vault_address] = Contract(vault_address)
@@ -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)
@@ -1,13 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: yearn_treasury
3
- Version: 0.0.20
3
+ Version: 0.0.49
4
4
  Summary: This WIP library runs the eth-portfolio and dao-treasury exporters for the Yearn Finance treasury.
5
5
  Classifier: Development Status :: 3 - Alpha
6
6
  Classifier: Intended Audience :: Developers
7
7
  Classifier: Intended Audience :: Science/Research
8
8
  Classifier: Intended Audience :: End Users/Desktop
9
9
  Classifier: Programming Language :: Python :: 3
10
- Classifier: Programming Language :: Python :: 3.9
11
10
  Classifier: Programming Language :: Python :: 3.10
12
11
  Classifier: Programming Language :: Python :: 3.11
13
12
  Classifier: Programming Language :: Python :: 3.12
@@ -16,7 +15,8 @@ Classifier: Operating System :: OS Independent
16
15
  Classifier: Topic :: Software Development :: Libraries
17
16
  Requires-Python: >=3.10,<3.13
18
17
  Description-Content-Type: text/markdown
19
- Requires-Dist: dao-treasury<0.1,>=0.0.39
18
+ Requires-Dist: dao-treasury==0.0.72
19
+ Requires-Dist: faster-async-lru<3,>=2.0.5
20
20
  Dynamic: classifier
21
21
  Dynamic: description
22
22
  Dynamic: description-content-type