yearn-treasury 0.0.39__tar.gz → 0.0.45__tar.gz

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 (91) hide show
  1. {yearn_treasury-0.0.39/yearn_treasury.egg-info → yearn_treasury-0.0.45}/PKG-INFO +2 -3
  2. {yearn_treasury-0.0.39 → yearn_treasury-0.0.45}/pyproject.toml +6 -6
  3. {yearn_treasury-0.0.39 → yearn_treasury-0.0.45}/setup.py +4 -2
  4. {yearn_treasury-0.0.39 → yearn_treasury-0.0.45}/yearn_treasury/_logging.py +2 -0
  5. {yearn_treasury-0.0.39 → yearn_treasury-0.0.45}/yearn_treasury/address_labels.yaml +6 -0
  6. {yearn_treasury-0.0.39 → yearn_treasury-0.0.45}/yearn_treasury/main.py +43 -22
  7. {yearn_treasury-0.0.39 → yearn_treasury-0.0.45}/yearn_treasury/rules/ignore/swaps/__init__.py +1 -0
  8. {yearn_treasury-0.0.39 → yearn_treasury-0.0.45}/yearn_treasury/rules/ignore/swaps/aave.py +15 -8
  9. yearn_treasury-0.0.45/yearn_treasury/rules/ignore/swaps/auctions.py +31 -0
  10. yearn_treasury-0.0.45/yearn_treasury/rules/ignore/swaps/compound.py +58 -0
  11. {yearn_treasury-0.0.39 → yearn_treasury-0.0.45}/yearn_treasury/rules/ignore/swaps/cowswap.py +16 -13
  12. {yearn_treasury-0.0.39 → yearn_treasury-0.0.45}/yearn_treasury/rules/ignore/swaps/curve.py +40 -12
  13. {yearn_treasury-0.0.39 → yearn_treasury-0.0.45}/yearn_treasury/rules/ignore/swaps/uniswap.py +26 -6
  14. {yearn_treasury-0.0.39 → yearn_treasury-0.0.45}/yearn_treasury/rules/ignore/swaps/yfi.py +2 -1
  15. {yearn_treasury-0.0.39 → yearn_treasury-0.0.45}/yearn_treasury/rules/other_income/misc.py +1 -1
  16. {yearn_treasury-0.0.39 → yearn_treasury-0.0.45}/yearn_treasury/shitcoins.py +7 -0
  17. {yearn_treasury-0.0.39 → yearn_treasury-0.0.45}/yearn_treasury/vaults.py +3 -4
  18. yearn_treasury-0.0.45/yearn_treasury/yteams.py +208 -0
  19. {yearn_treasury-0.0.39 → yearn_treasury-0.0.45/yearn_treasury.egg-info}/PKG-INFO +2 -3
  20. {yearn_treasury-0.0.39 → yearn_treasury-0.0.45}/yearn_treasury.egg-info/SOURCES.txt +2 -0
  21. yearn_treasury-0.0.45/yearn_treasury.egg-info/requires.txt +2 -0
  22. yearn_treasury-0.0.39/yearn_treasury/rules/ignore/swaps/compound.py +0 -48
  23. yearn_treasury-0.0.39/yearn_treasury.egg-info/requires.txt +0 -3
  24. {yearn_treasury-0.0.39 → yearn_treasury-0.0.45}/MANIFEST.in +0 -0
  25. {yearn_treasury-0.0.39 → yearn_treasury-0.0.45}/README.md +0 -0
  26. {yearn_treasury-0.0.39 → yearn_treasury-0.0.45}/setup.cfg +0 -0
  27. {yearn_treasury-0.0.39 → yearn_treasury-0.0.45}/yearn_treasury/__init__.py +0 -0
  28. {yearn_treasury-0.0.39 → yearn_treasury-0.0.45}/yearn_treasury/_db.py +0 -0
  29. {yearn_treasury-0.0.39 → yearn_treasury-0.0.45}/yearn_treasury/_ens.py +0 -0
  30. {yearn_treasury-0.0.39 → yearn_treasury-0.0.45}/yearn_treasury/budget/__init__.py +0 -0
  31. {yearn_treasury-0.0.39 → yearn_treasury-0.0.45}/yearn_treasury/budget/_request.py +0 -0
  32. {yearn_treasury-0.0.39 → yearn_treasury-0.0.45}/yearn_treasury/budget/_requests.py +0 -0
  33. {yearn_treasury-0.0.39 → yearn_treasury-0.0.45}/yearn_treasury/constants.py +0 -0
  34. {yearn_treasury-0.0.39 → yearn_treasury-0.0.45}/yearn_treasury/py.typed +0 -0
  35. {yearn_treasury-0.0.39 → yearn_treasury-0.0.45}/yearn_treasury/rules/__init__.py +0 -0
  36. {yearn_treasury-0.0.39 → yearn_treasury-0.0.45}/yearn_treasury/rules/constants.py +0 -0
  37. {yearn_treasury-0.0.39 → yearn_treasury-0.0.45}/yearn_treasury/rules/cost_of_revenue/__init__.py +0 -0
  38. {yearn_treasury-0.0.39 → yearn_treasury-0.0.45}/yearn_treasury/rules/cost_of_revenue/gas.py +0 -0
  39. {yearn_treasury-0.0.39 → yearn_treasury-0.0.45}/yearn_treasury/rules/cost_of_revenue/match_on_hash.yaml +0 -0
  40. {yearn_treasury-0.0.39 → yearn_treasury-0.0.45}/yearn_treasury/rules/expense/__init__.py +0 -0
  41. {yearn_treasury-0.0.39 → yearn_treasury-0.0.45}/yearn_treasury/rules/expense/general.py +0 -0
  42. {yearn_treasury-0.0.39 → yearn_treasury-0.0.45}/yearn_treasury/rules/expense/infrastructure.py +0 -0
  43. {yearn_treasury-0.0.39 → yearn_treasury-0.0.45}/yearn_treasury/rules/expense/match_on_hash.yaml +0 -0
  44. {yearn_treasury-0.0.39 → yearn_treasury-0.0.45}/yearn_treasury/rules/expense/match_on_to_address.yaml +0 -0
  45. {yearn_treasury-0.0.39 → yearn_treasury-0.0.45}/yearn_treasury/rules/expense/people.py +0 -0
  46. {yearn_treasury-0.0.39 → yearn_treasury-0.0.45}/yearn_treasury/rules/expense/security.py +0 -0
  47. {yearn_treasury-0.0.39 → yearn_treasury-0.0.45}/yearn_treasury/rules/ignore/__init__.py +0 -0
  48. {yearn_treasury-0.0.39 → yearn_treasury-0.0.45}/yearn_treasury/rules/ignore/general.py +0 -0
  49. {yearn_treasury-0.0.39 → yearn_treasury-0.0.45}/yearn_treasury/rules/ignore/maker.py +0 -0
  50. {yearn_treasury-0.0.39 → yearn_treasury-0.0.45}/yearn_treasury/rules/ignore/passthru.py +0 -0
  51. {yearn_treasury-0.0.39 → yearn_treasury-0.0.45}/yearn_treasury/rules/ignore/staking.py +0 -0
  52. {yearn_treasury-0.0.39 → yearn_treasury-0.0.45}/yearn_treasury/rules/ignore/swaps/_skip_tokens.py +0 -0
  53. {yearn_treasury-0.0.39 → yearn_treasury-0.0.45}/yearn_treasury/rules/ignore/swaps/conversion_factory.py +0 -0
  54. {yearn_treasury-0.0.39 → yearn_treasury-0.0.45}/yearn_treasury/rules/ignore/swaps/gearbox.py +0 -0
  55. {yearn_treasury-0.0.39 → yearn_treasury-0.0.45}/yearn_treasury/rules/ignore/swaps/iearn.py +0 -0
  56. {yearn_treasury-0.0.39 → yearn_treasury-0.0.45}/yearn_treasury/rules/ignore/swaps/otc.py +0 -0
  57. {yearn_treasury-0.0.39 → yearn_treasury-0.0.45}/yearn_treasury/rules/ignore/swaps/pooltogether.py +0 -0
  58. {yearn_treasury-0.0.39 → yearn_treasury-0.0.45}/yearn_treasury/rules/ignore/swaps/synthetix.py +0 -0
  59. {yearn_treasury-0.0.39 → yearn_treasury-0.0.45}/yearn_treasury/rules/ignore/swaps/unwrapper.py +0 -0
  60. {yearn_treasury-0.0.39 → yearn_treasury-0.0.45}/yearn_treasury/rules/ignore/swaps/vaults.py +0 -0
  61. {yearn_treasury-0.0.39 → yearn_treasury-0.0.45}/yearn_treasury/rules/ignore/swaps/woofy.py +0 -0
  62. {yearn_treasury-0.0.39 → yearn_treasury-0.0.45}/yearn_treasury/rules/ignore/swaps/yla.py +0 -0
  63. {yearn_treasury-0.0.39 → yearn_treasury-0.0.45}/yearn_treasury/rules/ignore/unit.py +0 -0
  64. {yearn_treasury-0.0.39 → yearn_treasury-0.0.45}/yearn_treasury/rules/ignore/weth.py +0 -0
  65. {yearn_treasury-0.0.39 → yearn_treasury-0.0.45}/yearn_treasury/rules/ignore/ygov.py +0 -0
  66. {yearn_treasury-0.0.39 → yearn_treasury-0.0.45}/yearn_treasury/rules/other_expense/__init__.py +0 -0
  67. {yearn_treasury-0.0.39 → yearn_treasury-0.0.45}/yearn_treasury/rules/other_expense/boost.py +0 -0
  68. {yearn_treasury-0.0.39 → yearn_treasury-0.0.45}/yearn_treasury/rules/other_expense/bugs.py +0 -0
  69. {yearn_treasury-0.0.39 → yearn_treasury-0.0.45}/yearn_treasury/rules/other_expense/donations.py +0 -0
  70. {yearn_treasury-0.0.39 → yearn_treasury-0.0.45}/yearn_treasury/rules/other_expense/dyfi.py +0 -0
  71. {yearn_treasury-0.0.39 → yearn_treasury-0.0.45}/yearn_treasury/rules/other_expense/events.py +0 -0
  72. {yearn_treasury-0.0.39 → yearn_treasury-0.0.45}/yearn_treasury/rules/other_expense/match_on_hash.yaml +0 -0
  73. {yearn_treasury-0.0.39 → yearn_treasury-0.0.45}/yearn_treasury/rules/other_expense/match_on_to_address.yaml +0 -0
  74. {yearn_treasury-0.0.39 → yearn_treasury-0.0.45}/yearn_treasury/rules/other_expense/misc.py +0 -0
  75. {yearn_treasury-0.0.39 → yearn_treasury-0.0.45}/yearn_treasury/rules/other_expense/revshare.py +0 -0
  76. {yearn_treasury-0.0.39 → yearn_treasury-0.0.45}/yearn_treasury/rules/other_income/__init__.py +0 -0
  77. {yearn_treasury-0.0.39 → yearn_treasury-0.0.45}/yearn_treasury/rules/other_income/airdrops.py +0 -0
  78. {yearn_treasury-0.0.39 → yearn_treasury-0.0.45}/yearn_treasury/rules/other_income/match_on_hash.yaml +0 -0
  79. {yearn_treasury-0.0.39 → yearn_treasury-0.0.45}/yearn_treasury/rules/revenue/__init__.py +0 -0
  80. {yearn_treasury-0.0.39 → yearn_treasury-0.0.45}/yearn_treasury/rules/revenue/bribes.py +0 -0
  81. {yearn_treasury-0.0.39 → yearn_treasury-0.0.45}/yearn_treasury/rules/revenue/farming.py +0 -0
  82. {yearn_treasury-0.0.39 → yearn_treasury-0.0.45}/yearn_treasury/rules/revenue/keepcoins.py +0 -0
  83. {yearn_treasury-0.0.39 → yearn_treasury-0.0.45}/yearn_treasury/rules/revenue/match_on_hash.yaml +0 -0
  84. {yearn_treasury-0.0.39 → yearn_treasury-0.0.45}/yearn_treasury/rules/revenue/seasolver.py +0 -0
  85. {yearn_treasury-0.0.39 → yearn_treasury-0.0.45}/yearn_treasury/rules/revenue/vaults.py +0 -0
  86. {yearn_treasury-0.0.39 → yearn_treasury-0.0.45}/yearn_treasury/rules/revenue/yteams.py +0 -0
  87. {yearn_treasury-0.0.39 → yearn_treasury-0.0.45}/yearn_treasury/wallets.yaml +0 -0
  88. {yearn_treasury-0.0.39 → yearn_treasury-0.0.45}/yearn_treasury.egg-info/dependency_links.txt +0 -0
  89. {yearn_treasury-0.0.39 → yearn_treasury-0.0.45}/yearn_treasury.egg-info/entry_points.txt +0 -0
  90. {yearn_treasury-0.0.39 → yearn_treasury-0.0.45}/yearn_treasury.egg-info/not-zip-safe +0 -0
  91. {yearn_treasury-0.0.39 → yearn_treasury-0.0.45}/yearn_treasury.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: yearn_treasury
3
- Version: 0.0.39
3
+ Version: 0.0.45
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
@@ -15,8 +15,7 @@ Classifier: Operating System :: OS Independent
15
15
  Classifier: Topic :: Software Development :: Libraries
16
16
  Requires-Python: >=3.10,<3.13
17
17
  Description-Content-Type: text/markdown
18
- Requires-Dist: dao-treasury<0.1,>=0.0.55
19
- Requires-Dist: eth-brownie<1.23,>=1.21.0
18
+ Requires-Dist: dao-treasury==0.0.67
20
19
  Requires-Dist: faster-async-lru<3,>=2.0.5
21
20
  Dynamic: classifier
22
21
  Dynamic: description
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "yearn-treasury"
3
- version = "0.0.39"
3
+ version = "0.0.45"
4
4
  description = "This WIP library runs the eth-portfolio and dao-treasury exporters for the Yearn Finance treasury."
5
5
  authors = ["Bob The Buidler <you@example.com>"]
6
6
  packages = [{ include = "yearn_treasury/*" }]
@@ -12,8 +12,9 @@ requires = [
12
12
  "mypy[mypyc]==1.18.2",
13
13
  "tomli>=2.0.0; python_version < '3.11'",
14
14
  "types-requests",
15
- "dao-treasury>=0.0.45,<0.1; platform_system == 'Linux'",
16
- "eth-typing==4.4.0",
15
+ "dao-treasury==0.0.66; platform_system == 'Linux'",
16
+ "eth-brownie==1.22.0.dev0; platform_system == 'Linux'",
17
+ "eth-typing==5.2.1",
17
18
  ]
18
19
  build-backend = "setuptools.build_meta"
19
20
 
@@ -22,12 +23,11 @@ yearn-treasury = "yearn_treasury.main:main"
22
23
 
23
24
  [tool.poetry.dependencies]
24
25
  python = ">=3.10,<3.13"
25
- dao-treasury = ">=0.0.55,<0.1"
26
- eth-brownie = ">=1.21.0,<1.23"
26
+ dao-treasury = "==0.0.67"
27
27
  faster-async-lru = ">=2.0.5,<3"
28
28
 
29
29
  [tool.poetry.group.dev.dependencies]
30
- mypy = "*"
30
+ mypy = "1.18.2"
31
31
  types-requests = "*"
32
32
 
33
33
  [tool.poetry.group.docs.dependencies]
@@ -14,6 +14,7 @@ else:
14
14
  "yearn_treasury/rules/expense",
15
15
  # "yearn_treasury/rules/ignore",
16
16
  "yearn_treasury/rules/ignore/swaps/conversion_factory.py",
17
+ "yearn_treasury/rules/ignore/swaps/auctions.py",
17
18
  # "yearn_treasury/rules/ignore/swaps/curve.py", enable with brownie 1.22.0
18
19
  "yearn_treasury/rules/ignore/swaps/gearbox.py",
19
20
  "yearn_treasury/rules/ignore/swaps/iearn.py",
@@ -23,7 +24,7 @@ else:
23
24
  "yearn_treasury/rules/ignore/swaps/unwrapper.py",
24
25
  "yearn_treasury/rules/ignore/swaps/vaults.py",
25
26
  "yearn_treasury/rules/ignore/swaps/woofy.py",
26
- # "yearn_treasury/rules/ignore/swaps/yfi.py", enable with brownie 1.22.0
27
+ "yearn_treasury/rules/ignore/swaps/yfi.py",
27
28
  "yearn_treasury/rules/ignore/swaps/yla.py",
28
29
  "yearn_treasury/rules/ignore/general.py",
29
30
  "yearn_treasury/rules/ignore/unit.py",
@@ -40,7 +41,7 @@ else:
40
41
  "yearn_treasury/_db.py",
41
42
  "yearn_treasury/_ens.py",
42
43
  "yearn_treasury/_logging.py",
43
- # "yearn_treasury/vaults.py", enable with brownie 1.22.0
44
+ "yearn_treasury/vaults.py",
44
45
  "--strict",
45
46
  "--pretty",
46
47
  "--disable-error-code=unused-ignore",
@@ -48,6 +49,7 @@ else:
48
49
  "--disable-error-code=no-any-return",
49
50
  "--disable-error-code=misc", # TODO: drop support for 32bit systems
50
51
  ],
52
+ group_name="yearn_treasury",
51
53
  )
52
54
 
53
55
 
@@ -25,6 +25,8 @@ suppress_logs_for: Final[Dict[Network, List[HexAddress]]] = {
25
25
  "0xBF7AA989192b020a8d3e1C65a558e123834325cA", # unpriceable yvWBTC - This vault had a bug and does not have a pricePerShare
26
26
  "0x5aFE3855358E112B5647B952709E6165e1c1eEEe", # SAFE - This was not tradeable at the time of the first airdrops
27
27
  "0x718AbE90777F5B778B52D553a5aBaa148DD0dc5D", # yvCurve-alETH - The underlying curve pool had an issue and is unpriceable
28
+ "0x3819f64f282bf135d62168C1e513280dAF905e06", # HDRN
29
+ "0x5fAa989Af96Af85384b8a938c2EdE4A7378D9875", # GAL
28
30
  ],
29
31
  }
30
32
 
@@ -28,6 +28,12 @@
28
28
  - "0xC564EE9f21Ed8A2d8E7e76c085740d5e4c5FaFbE"
29
29
  yLockers Multisig:
30
30
  - "0x4444AAAACDBa5580282365e25b16309Bd770ce4a"
31
+ yRoboTreasury Treasury Contract:
32
+ - "0xEf77cc176c748d291EfB6CdC982c5744fC7211c8"
33
+ yRoboTreasury Stables Reserve:
34
+ - "0x278374fFb10B7D16E7633444c13e6E565EA57c28"
35
+ yearn.fi Dutch Auctions:
36
+ - "0x861fE45742f70054917B65bE18904662bD0dBd30"
31
37
  250:
32
38
  Yearn Strategist Multisig:
33
39
  - "0x72a34AbafAB09b15E7191822A679f28E067C4a16"
@@ -2,9 +2,9 @@
2
2
  Command-line interface for the Yearn Treasury exporter.
3
3
 
4
4
  This module provides the `yearn-treasury` CLI, which connects to a Brownie network
5
- based on the `--network` option (or the `BROWNIE_NETWORK_ID` environment variable),
6
- periodically snapshots treasury balances via :func:`export_balances`, and
7
- pushes metrics to Victoria Metrics.
5
+ (based on the `--network` option or the `BROWNIE_NETWORK_ID` environment variable),
6
+ periodically snapshots treasury metrics, and pushes them to Victoria Metrics.
7
+ It also launches Yearn Treasury's Grafana dashboard and an optional renderer for visual reports.
8
8
 
9
9
  Example:
10
10
  Run export every 12 hours on mainnet:
@@ -12,15 +12,24 @@ Example:
12
12
  .. code-block:: bash
13
13
 
14
14
  yearn-treasury run --network mainnet --interval 12h
15
+
16
+ CLI Options:
17
+ --network Brownie network identifier (default: mainnet)
18
+ --interval Time interval between datapoints (default: 12h)
19
+ --concurrency Max number of historical blocks to export concurrently (default: 30)
20
+ --daemon Run as a background daemon (currently unsupported)
21
+ --grafana-port Port for the Grafana dashboard (default: 3004)
22
+ --victoria-port Port for the Victoria metrics endpoint (default: 8430)
23
+ --start-renderer Start the Grafana renderer container for dashboard image export
24
+ --renderer-port Port for the renderer service (default: 8080)
15
25
  """
16
26
 
17
27
  import asyncio
18
28
  import os
19
29
  from argparse import ArgumentParser
20
- from pathlib import Path
21
30
  from typing import Final, final
22
31
 
23
- from eth_portfolio_scripts.balances import export_balances
32
+ from yearn_treasury import yteams
24
33
 
25
34
 
26
35
  parser = ArgumentParser(description="Treasury CLI")
@@ -42,8 +51,8 @@ run_parser.add_argument(
42
51
  run_parser.add_argument(
43
52
  "--concurrency",
44
53
  type=int,
45
- help="The max number of historical blocks to export concurrently. default: 40",
46
- default=40,
54
+ help="The max number of historical blocks to export concurrently. default: 30",
55
+ default=30,
47
56
  )
48
57
  run_parser.add_argument(
49
58
  "--daemon",
@@ -83,23 +92,19 @@ BROWNIE_NETWORK = os.environ["BROWNIE_NETWORK_ID"]
83
92
  # TODO: run forever arg
84
93
  def main() -> None:
85
94
  """
86
- Connect to the configured Brownie network and start the export loop.
95
+ Connect to the configured Brownie network, clean up the database, and start the export loop.
87
96
 
88
97
  This function is registered as a console script entrypoint under
89
- ``yearn-treasury`` and delegates execution to Brownie's script runner.
90
-
91
- Steps:
98
+ ``yearn-treasury``. It performs the following steps:
92
99
  1. Reads the ``BROWNIE_NETWORK_ID`` environment variable (populated from
93
100
  the ``--network`` option or existing env var).
94
- 2. Connects to that Brownie network.
95
- 3. Patches the global SHITCOINS mapping with local tokens.
96
- 4. Constructs a frozen Args subclass to pass CLI parameters to
97
- :func:`export_balances`.
98
- 5. Exports ports for external services into environment variables.
99
- 6. Runs :func:`eth_portfolio_scripts.balances.export_balances` under asyncio.
100
-
101
- Raises:
102
- RuntimeError: If the Brownie network cannot be determined.
101
+ 2. Connects to the specified Brownie network.
102
+ 3. Merges local SHITCOINS into eth_portfolio's config to skip tokens we don't care about.
103
+ 4. Drops any shitcoin transactions that might be in the database.
104
+ 5. Constructs an immutable Args subclass to pass CLI parameters to
105
+ :func:`dao_treasury.main.export`.
106
+ 6. Exports ports for external services into environment variables.
107
+ 7. Runs both the DAO Treasury export and Yearn team revenue/expenses calculation concurrently under asyncio.
103
108
  """
104
109
  import dao_treasury.db
105
110
  import eth_portfolio
@@ -115,7 +120,10 @@ def main() -> None:
115
120
  @final
116
121
  class Args(constants.Args):
117
122
  """
118
- Immutable container of CLI arguments for :func:`~export_balances`.
123
+ Immutable container of CLI arguments for export and dashboard/renderer configuration.
124
+
125
+ Inherits from DAO Treasury's :class:`constants.Args` and is used to pass runtime
126
+ parameters to the DAO Treasury export and related services.
119
127
  """
120
128
 
121
129
  network: Final[str] = args.network
@@ -149,7 +157,20 @@ def main() -> None:
149
157
  os.environ["DAO_TREASURY_RENDERER_PORT"] = str(Args.renderer_port)
150
158
  os.environ["VICTORIA_PORT"] = str(Args.victoria_port)
151
159
 
160
+ async def yearn_wrapper():
161
+ """
162
+ Run the DAO Treasury export and Yearn team revenue/expenses calculation concurrently.
163
+
164
+ This coroutine gathers:
165
+ - The main DAO Treasury export process (dao_treasury.main.export)
166
+ - The Yearn teams' revenue and expenses calculation (yteams.calculate_teams_revenue_expenses)
167
+ """
168
+ return await asyncio.gather(
169
+ dao_treasury.main.export(Args),
170
+ yteams.calculate_teams_revenue_expenses(),
171
+ )
172
+
152
173
  # Start the balance export routine
153
- asyncio.get_event_loop().run_until_complete(dao_treasury.main.export(Args))
174
+ asyncio.get_event_loop().run_until_complete(yearn_wrapper())
154
175
 
155
176
  rules # I just put this here so the import isn't flagged as unused
@@ -7,6 +7,7 @@ swaps: Final[SortRuleFactory[IgnoreSortRule]] = ignore("Swaps")
7
7
 
8
8
 
9
9
  from yearn_treasury.rules.ignore.swaps.aave import *
10
+ from yearn_treasury.rules.ignore.swaps.auctions import *
10
11
  from yearn_treasury.rules.ignore.swaps.compound import *
11
12
  from yearn_treasury.rules.ignore.swaps.conversion_factory import *
12
13
  from yearn_treasury.rules.ignore.swaps.cowswap import *
@@ -43,20 +43,27 @@ async def is_aave_withdrawal(tx: TreasuryTx) -> bool:
43
43
  if (
44
44
  from_address == event["_user"]
45
45
  and await token.contract.underlyingAssetAddress == event["_reserve"]
46
- and token.scale_value(event["_amount"]) == tx.amount
47
46
  ):
48
- return True
47
+ # TODO get rid of this rounding when we migrate the db to postgres
48
+ event_amount = round(token.scale_value(event["_amount"]), 11)
49
+ if event_amount == round(tx.amount, 11):
50
+ return True
51
+ print(
52
+ f"Aave Withdrawal atoken side does not match: {round(tx.amount, 14)} {event_amount}"
53
+ )
49
54
 
50
55
  # Underlying side
51
56
  if TreasuryWallet.check_membership(tx.to_address.address, tx.block): # type: ignore [union-attr, arg-type]
52
57
  token = tx.token
53
58
  for event in await tx.get_events("RedeemUnderlying", sync=False):
54
- if (
55
- token == event["_reserve"]
56
- and to_address == event["_user"]
57
- and token.scale_value(event["_amount"]) == tx.amount
58
- ):
59
- return True
59
+ if token == event["_reserve"] and to_address == event["_user"]:
60
+ # TODO get rid of this rounding when we migrate the db to postgres
61
+ event_amount = round(token.scale_value(event["_amount"]), 11)
62
+ if event_amount == round(tx.amount, 11):
63
+ return True
64
+ print(
65
+ f"Aave Withdrawal underlying side does not match: {round(tx.amount, 14)} {event_amount}"
66
+ )
60
67
 
61
68
  # TODO: If these end up becoming more frequent, figure out sorting hueristics.
62
69
  return tx.hash == "0x36ee5631859a15f57b44e41b8590023cf6f0c7b12d28ea760e9d8f8003f4fc50"
@@ -0,0 +1,31 @@
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
+ auctions: Final = swaps("Auctions")
10
+
11
+ YEARNFI_DUTCH_AUCTIONS: Final = "0x861fE45742f70054917B65bE18904662bD0dBd30"
12
+
13
+
14
+ @auctions("Auction Proceeds", Network.Mainnet)
15
+ async def is_auction_proceeds(tx: TreasuryTx) -> bool:
16
+ # NOTE: the other side of these swaps is currently recorded under
17
+ # 'Ignore:Internal Transfer' when it goes to the Generic bucket contract
18
+ if tx.from_nickname != "Contract: GPv2Settlement":
19
+ return False
20
+
21
+ for trade in await tx.get_events("Trade", sync=False):
22
+ if trade["owner"] != YEARNFI_DUTCH_AUCTIONS or tx.token != trade["buyToken"]:
23
+ continue
24
+ buy_amount = tx.token.scale_value(trade["buyAmount"])
25
+ if round(buy_amount, 14) == round(tx.amount, 14):
26
+ return True
27
+ print(
28
+ f"auction proceeds amount does not match: {round(buy_amount, 14)} {round(tx.amount, 14)}"
29
+ )
30
+
31
+ return False
@@ -0,0 +1,58 @@
1
+ from typing import Final
2
+
3
+ from dao_treasury import TreasuryTx
4
+
5
+ from yearn_treasury.rules.ignore.swaps import swaps
6
+
7
+
8
+ compound: Final = swaps("Compound")
9
+
10
+
11
+ @compound("Deposit")
12
+ async def is_compound_deposit(tx: TreasuryTx) -> bool:
13
+ for event in await tx.get_events("Mint", sync=False):
14
+ if all(arg in event for arg in ("minter", "mintTokens", "mintAmount")):
15
+ minter = event["minter"]
16
+ minted = tx.token.scale_value(event["mintTokens"])
17
+ # cToken side
18
+ if tx.token == tx.from_address == event.address and tx.to_address == minter:
19
+ # TODO: get rid of this rounding when we migrate to postgres
20
+ if round(minted, 14) == round(tx.amount, 14):
21
+ return True
22
+ print(
23
+ f"Compound deposit ctoken side does not match: {round(minted, 14)} {round(tx.amount, 14)}"
24
+ )
25
+ # underlying side
26
+ elif tx.to_address == event.address and tx.from_address == minter:
27
+ # TODO: get rid of this rounding when we migrate to postgres
28
+ if round(minted, 14) == round(tx.amount, 14):
29
+ return True
30
+ print(
31
+ f"Compound deposit underlying side does not match: {round(minted, 14)} {round(tx.amount, 14)}"
32
+ )
33
+ return False
34
+
35
+
36
+ @compound("Withdrawal")
37
+ async def is_compound_withdrawal(tx: TreasuryTx) -> bool:
38
+ for event in await tx.get_events("Redeem", sync=False):
39
+ if all(arg in event for arg in ("redeemer", "redeemTokens", "redeemAmount")):
40
+ redeemer = event["redeemer"]
41
+ redeemed = tx.token.scale_value(event["redeemTokens"])
42
+ # cToken side
43
+ if tx.token == event.address and tx.from_address == redeemer:
44
+ # TODO: get rid of this rounding when we migrate to postgres
45
+ if round(redeemed, 7) == round(tx.amount, 7):
46
+ return True
47
+ print(
48
+ f"Compound withdrawal ctoken side does not match: {round(redeemed, 7)} {round(tx.amount, 7)}"
49
+ )
50
+ # underlying side
51
+ elif tx.to_address == redeemer and tx.from_address == event.address:
52
+ # TODO: get rid of this rounding when we migrate to postgres
53
+ if round(redeemed, 14) == round(tx.amount, 14):
54
+ return True
55
+ print(
56
+ f"Compound withdrawal underlying side does not match: {round(redeemed, 14)} {round(tx.amount, 14)}"
57
+ )
58
+ return False
@@ -10,7 +10,7 @@ from yearn_treasury.rules.ignore.swaps import swaps
10
10
  from yearn_treasury.rules.ignore.swaps._skip_tokens import SKIP_TOKENS
11
11
 
12
12
 
13
- YSWAPS: Final = "0x9008D19f58AAbD9eD0D60971565AA8510560ab41"
13
+ COWSWAP: Final = "0x9008D19f58AAbD9eD0D60971565AA8510560ab41"
14
14
 
15
15
 
16
16
  @swaps("Cowswap", Network.Mainnet)
@@ -34,23 +34,26 @@ def is_cowswap_swap(tx: TreasuryTx) -> bool:
34
34
 
35
35
  for trade in tx.events["Trade"]:
36
36
  if (
37
- trade.address == YSWAPS
37
+ trade.address == COWSWAP
38
38
  and TreasuryWallet.check_membership(trade["owner"], block)
39
39
  and trade["buyToken"] not in SKIP_TOKENS
40
40
  ):
41
41
  # buy side
42
- if (
43
- token_address == trade["buyToken"]
44
- and TreasuryWallet.check_membership(tx.to_address.address, block) # type: ignore [union-attr, arg-type]
45
- and amount == token.scale_value(trade["buyAmount"])
42
+ if token_address == trade["buyToken"] and TreasuryWallet.check_membership(
43
+ tx.to_address.address, block # type: ignore [union-attr, arg-type]
46
44
  ):
47
- return True
45
+ # TODO get rid of this rounding when we move to postgres
46
+ buy_amount = round(token.scale_value(trade["buyAmount"]), 8)
47
+ if round(amount, 8) == buy_amount:
48
+ return True
49
+ print(f"Cowswap buy amount does not match: {round(amount, 8)} {buy_amount}")
48
50
  # sell side
49
- elif (
50
- token_address == trade["sellToken"]
51
- and tx.from_address == trade["owner"]
52
- and amount == token.scale_value(trade["sellAmount"])
53
- ):
51
+ elif token_address == trade["sellToken"] and tx.from_address == trade["owner"]:
52
+ # TODO get rid of this rounding when we move to postgres
53
+ sell_amount = round(token.scale_value(trade["sellAmount"]), 8)
54
+ if round(amount, 8) != sell_amount:
55
+ print(f"Cowswap sell amount does not match: {round(amount, 8)} {sell_amount}")
56
+ continue
54
57
  # Did Yearn actually receive the other side of the trade?
55
58
  for address in TREASURY_WALLETS:
56
59
  if TreasuryWallet.check_membership(address, block):
@@ -59,7 +62,7 @@ def is_cowswap_swap(tx: TreasuryTx) -> bool:
59
62
  for t in TreasuryTx # type: ignore [attr-defined]
60
63
  if t.hash == tx.hash
61
64
  and t.token.address.address == trade["buyToken"]
62
- and t.from_address.address == YSWAPS
65
+ and t.from_address.address == COWSWAP
63
66
  and t.to_address.address == address
64
67
  )
65
68
 
@@ -50,11 +50,19 @@ async def is_curve_deposit(tx: TreasuryTx) -> bool:
50
50
  # Tokens sent
51
51
  elif tx.to_address == event.address:
52
52
  try:
53
+ tx_amount = round(tx.amount, 8)
53
54
  for i, amount in enumerate(event["token_amounts"]):
54
- if tx.amount == tx.token.scale_value(amount):
55
+ # TODO: get rid of this rounding when we migrate to postgres
56
+ event_amount = round(tx.token.scale_value(amount), 8)
57
+ if tx_amount == event_amount:
55
58
  pool = await Contract.coroutine(event.address) # type: ignore [assignment]
56
59
  if tx.token == await _get_coin_at_index(pool, i):
57
60
  return True
61
+ return True
62
+ else:
63
+ print(
64
+ f"Curve AddLiquidity sent amount does not match: {tx_amount} {event_amount}"
65
+ )
58
66
  except EventLookupError:
59
67
  pass
60
68
 
@@ -67,10 +75,16 @@ async def is_curve_deposit(tx: TreasuryTx) -> bool:
67
75
  print(f"AddLiquidity-3crv: {event}")
68
76
  token = tx.token
69
77
  for i, amount in enumerate(event["token_amounts"]):
70
- if tx.amount == token.scale_value(amount):
78
+ event_amount = token.scale_value(amount)
79
+ # TODO: get rid of this rounding when we migrate to postgres
80
+ if round(tx.amount, 14) == round(event_amount, 14):
71
81
  pool = await Contract.coroutine(event.address) # type: ignore [assignment]
72
82
  if token == await _get_coin_at_index(pool, i):
73
83
  return True
84
+ else:
85
+ print(
86
+ f"AddLiquidity-3crv amount does not match: {round(tx.amount, 14)} {round(event_amount)}"
87
+ )
74
88
 
75
89
  # TODO: see if we can remove these with latest hueristics
76
90
  return CHAINID == Network.Mainnet and tx.hash in (
@@ -98,17 +112,24 @@ async def is_curve_withdrawal(tx: TreasuryTx) -> bool:
98
112
  def _is_curve_withdrawal_one(tx: TreasuryTx) -> bool:
99
113
  for event in tx.get_events("RemoveLiquidityOne"):
100
114
  # LP Token Side
101
- if (
102
- tx.to_address == ZERO_ADDRESS
103
- and _token_is_curvey(tx)
104
- and tx.amount == tx.token.scale_value(event["token_amount"])
105
- ):
106
- return True
115
+ if tx.to_address == ZERO_ADDRESS and _token_is_curvey(tx):
116
+ # TODO: get rid of this rounding when we migrate to postgres
117
+ event_amount = round(tx.token.scale_value(event["token_amount"]), 9)
118
+ if round(tx.amount, 9) == event_amount:
119
+ return True
120
+ print(
121
+ f"Curve withdrawal one curvey amount does not match: {round(tx.amount, 9)} {event_amount}"
122
+ )
107
123
  # Tokens rec'd
108
- elif tx.from_address == event.address and tx.amount == tx.token.scale_value(
109
- event["coin_amount"]
110
- ):
124
+ if tx.from_address != event.address:
125
+ continue
126
+ # TODO: get rid of this rounding when we migrate to postgres
127
+ event_amount = tx.token.scale_value(event["coin_amount"])
128
+ if round(tx.amount, 9) == round(event_amount, 9):
111
129
  return True
130
+ print(
131
+ f"Curve withdrawal one amount does not match: {round(tx.amount, 9)} {round(event_amount, 9)}"
132
+ )
112
133
  return False
113
134
 
114
135
 
@@ -127,15 +148,22 @@ async def _is_curve_withdrawal_multi(tx: TreasuryTx) -> bool:
127
148
  elif tx.from_address == event.address and TreasuryWallet.check_membership(
128
149
  tx.to_address.address, tx.block # type: ignore [union-attr, arg-type]
129
150
  ):
151
+ tx_amount = round(tx.amount, 7)
130
152
  try:
131
153
  for i, amount in enumerate(event["token_amounts"]):
132
- if tx.amount == tx.token.scale_value(amount):
154
+ # TODO: get rid of this rounding when we migrate to postgres
155
+ event_amount = round(tx.token.scale_value(amount), 7)
156
+ if tx_amount == event_amount:
133
157
  pool = await Contract.coroutine(event.address) # type: ignore [assignment]
134
158
  if hasattr(pool, "underlying_coins"):
135
159
  coin: ChecksumAddress = await pool.underlying_coins.coroutine(i)
136
160
  return tx.token == coin
137
161
  else:
138
162
  return tx.token == await _get_coin_at_index(pool, i)
163
+ else:
164
+ print(
165
+ f"Curve withdrawal multi amount does not match: {tx_amount} {event_amount}"
166
+ )
139
167
  except EventLookupError:
140
168
  # some other event has different keys, maybe we need to implement logic to capture these. time will tell.
141
169
  pass
@@ -57,7 +57,7 @@ async def is_uniswap_deposit(tx: TreasuryTx) -> bool:
57
57
  if any(
58
58
  tokens[1] == transfer.address
59
59
  and tx.to_address
60
- == transfer.values()[:1]
60
+ == transfer.values()[:1] # type: ignore [index]
61
61
  == [mint["sender"], mint.address]
62
62
  for transfer in transfers
63
63
  ):
@@ -78,7 +78,7 @@ async def is_uniswap_deposit(tx: TreasuryTx) -> bool:
78
78
  if any(
79
79
  tokens[0] == transfer.address
80
80
  and tx.to_address
81
- == transfer.values()[:1]
81
+ == transfer.values()[:1] # type: ignore [index]
82
82
  == [mint["sender"], mint.address]
83
83
  for transfer in transfers
84
84
  ):
@@ -243,11 +243,21 @@ async def is_uniswap_swap(tx: TreasuryTx) -> bool:
243
243
  continue
244
244
 
245
245
  if tx.token == token0:
246
- if tx.token.scale_value(swap["amount0In"]) == tx.amount:
246
+ # TODO: get rid of this rounding when we migrate to postgres
247
+ event_amount = round(tx.token.scale_value(swap["amount0In"]), 10)
248
+ if event_amount == round(tx.amount, 10):
247
249
  return True
250
+ print(
251
+ f"Uniswap sell token0 amount does not match: {round(tx.amount, 10)} {event_amount}"
252
+ )
248
253
  elif tx.token == token1:
249
- if tx.token.scale_value(swap["amount1In"]) == tx.amount:
254
+ # TODO: get rid of this rounding when we migrate to postgres
255
+ event_amount = round(tx.token.scale_value(swap["amount1In"]), 10)
256
+ if event_amount == round(tx.amount, 10):
250
257
  return True
258
+ print(
259
+ f"Uniswap sell token1 amount does not match: {round(tx.amount, 10)} {event_amount}"
260
+ )
251
261
 
252
262
  # Buy side
253
263
  elif tx.from_address == swap.address and TreasuryWallet._get_instance(
@@ -262,11 +272,21 @@ async def is_uniswap_swap(tx: TreasuryTx) -> bool:
262
272
  # This will be recorded elsewhere
263
273
  continue
264
274
  if "amount0Out" in swap and tx.token == token0:
265
- if tx.token.scale_value(swap["amount0Out"]) == tx.amount:
275
+ # TODO: get rid of this rounding when we migrate to postgres
276
+ event_amount = round(tx.token.scale_value(swap["amount0Out"]), 9)
277
+ if event_amount == round(tx.amount, 9):
266
278
  return True
279
+ print(
280
+ f"Uniswap buy token0 amount does not match: {round(tx.amount, 9)} {event_amount}"
281
+ )
267
282
  elif "amount1Out" in swap and tx.token == token1:
268
- if tx.token.scale_value(swap["amount1Out"]) == tx.amount:
283
+ # TODO: get rid of this rounding when we migrate to postgres
284
+ event_amount = round(tx.token.scale_value(swap["amount1Out"]), 10)
285
+ if event_amount == round(tx.amount, 10):
269
286
  return True
287
+ print(
288
+ f"Uniswap buy token1 amount does not match: {round(tx.amount, 10)} {event_amount}"
289
+ )
270
290
  return False
271
291
 
272
292
 
@@ -104,7 +104,8 @@ def is_buying_with_auction(tx: TreasuryTx) -> bool:
104
104
  if tx.from_address != receiver:
105
105
  print(f"Transfer does not match auction taker: taker={tx.from_address.address} transfer={receiver}") # type: ignore [union-attr]
106
106
  continue
107
- if amount == event["taken"]: # type: ignore [call-overload]
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]
108
109
  return True
109
110
  print(f"AuctionTaken: {event} amount does not match Transfer: {transfer}")
110
111
  return False
@@ -29,7 +29,7 @@ async def is_robovault_share(tx: TreasuryTx) -> bool:
29
29
  return False
30
30
 
31
31
  try:
32
- strat = await Contract.coroutine(tx.from_address.address)
32
+ strat = await tx.from_address.contract_coro
33
33
  except ContractNotVerified:
34
34
  return False
35
35
  else:
@@ -54,6 +54,9 @@ _SHITCOINS: Final = {
54
54
  "0xe2549E429B78458fa60BC7B1b284d4411E1D5105",
55
55
  "0xCfdD747d041397bcE08B0Fe6ebF7Ef65E9F46795",
56
56
  "0x9745969171a38B40db05c506fe2DA2C36f317627",
57
+ "0x6051C1354Ccc51b4d561e43b02735DEaE64768B8",
58
+ "0xf0814d0E47F2390a8082C4a1BD819FDDe50f9bFc",
59
+ "0x2DBd330bC9B7f3A822a9173aB52172BdDDcAcE2A",
57
60
  # just andre tinkering
58
61
  "0x5cB5e2d7Ab9Fd32021dF8F1D3E5269bD437Ec3Bf",
59
62
  # these arent shitcoins per se but we can't price them and dont expect to in the future, lets save cpu cycles
@@ -80,6 +83,8 @@ _SHITCOINS: Final = {
80
83
  "0xdE56173463d6461001B0891bC90DB224965f5762", # MAGNUS
81
84
  "0x922824A5b3B3D5f4485fF52F4Ab7Cba5eA491874", # POSEIDON
82
85
  "0x84F7D2f6FB447Bb11d3E7Ce64D83e1c02c2F3078", # VIRTUAL
86
+ "0x5C6Ed14E1017cf75C237A4A4b93Ce1D2f83EB002", # GRVT
87
+ "0xf76E6eFf109258fd5F52823d9F9feE7c90C97251", # wkeyDAO
83
88
  # test token?
84
89
  "0x372d5d02c6b4075bd58892f80300cA590e92d29E", # tOUSG
85
90
  # dETH? don't think this is needed
@@ -106,6 +111,8 @@ _SHITCOINS: Final = {
106
111
  "0xc83377b9eE3CEe4Cc03CCd58AfdE1FB12864aEE3", # E AI
107
112
  "0x927402ab67c0CDA3c187E9DFE34554AC581441f2", # SAITABIT
108
113
  "0x691539810DF6e879A377C24CfEE130BBE92708d8", # NVIDIA AI
114
+ "0xdC82aC0A89197854cb2240FaBF7E7760a4fF4d9e", # NVIDIA
115
+ "0x5Fba8ea5A559CF5c99BA6dd884Ae17C1d621fE5B", # OSCAR AI
109
116
  # matt furry spam
110
117
  "0x73228b3D33cC71cB721Fc62950577bE63bd9c8C9", # Maskman by Matt Furie
111
118
  "0x7c28e66436C93BB9F657dDF2BA0eeeCf61369b92", # Bloodboy by Matt Furie
@@ -12,7 +12,7 @@ Key Responsibilities:
12
12
  - Provide lookup tables for use in vault deposit/withdrawal sort rules.
13
13
  """
14
14
 
15
- from typing import Dict, Final, List
15
+ from typing import Dict, Final
16
16
 
17
17
  from brownie import chain
18
18
  from eth_typing import ChecksumAddress
@@ -41,11 +41,10 @@ if chain.id == Network.Mainnet:
41
41
 
42
42
  # TODO: make resolve_ens util in eth-port and refactor this out
43
43
  v2_registries = [
44
- event["newAddress"].hex() # type: ignore [attr-defined]
45
- for event in Events(addresses=resolver, topics=topics).events(now)
44
+ event["newAddress"].hex() for event in Events(addresses=resolver, topics=topics).events(now)
46
45
  ]
47
46
 
48
47
  for event in Events(addresses=list(map(str, v2_registries))).events(now):
49
48
  if event.name == "NewVault":
50
49
  vault_address = event["vault"]
51
- 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,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: yearn_treasury
3
- Version: 0.0.39
3
+ Version: 0.0.45
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
@@ -15,8 +15,7 @@ Classifier: Operating System :: OS Independent
15
15
  Classifier: Topic :: Software Development :: Libraries
16
16
  Requires-Python: >=3.10,<3.13
17
17
  Description-Content-Type: text/markdown
18
- Requires-Dist: dao-treasury<0.1,>=0.0.55
19
- Requires-Dist: eth-brownie<1.23,>=1.21.0
18
+ Requires-Dist: dao-treasury==0.0.67
20
19
  Requires-Dist: faster-async-lru<3,>=2.0.5
21
20
  Dynamic: classifier
22
21
  Dynamic: description
@@ -13,6 +13,7 @@ yearn_treasury/py.typed
13
13
  yearn_treasury/shitcoins.py
14
14
  yearn_treasury/vaults.py
15
15
  yearn_treasury/wallets.yaml
16
+ yearn_treasury/yteams.py
16
17
  yearn_treasury.egg-info/PKG-INFO
17
18
  yearn_treasury.egg-info/SOURCES.txt
18
19
  yearn_treasury.egg-info/dependency_links.txt
@@ -46,6 +47,7 @@ yearn_treasury/rules/ignore/ygov.py
46
47
  yearn_treasury/rules/ignore/swaps/__init__.py
47
48
  yearn_treasury/rules/ignore/swaps/_skip_tokens.py
48
49
  yearn_treasury/rules/ignore/swaps/aave.py
50
+ yearn_treasury/rules/ignore/swaps/auctions.py
49
51
  yearn_treasury/rules/ignore/swaps/compound.py
50
52
  yearn_treasury/rules/ignore/swaps/conversion_factory.py
51
53
  yearn_treasury/rules/ignore/swaps/cowswap.py
@@ -0,0 +1,2 @@
1
+ dao-treasury==0.0.67
2
+ faster-async-lru<3,>=2.0.5
@@ -1,48 +0,0 @@
1
- from typing import Final
2
-
3
- from dao_treasury import TreasuryTx
4
-
5
- from yearn_treasury.rules.ignore.swaps import swaps
6
-
7
-
8
- compound: Final = swaps("Compound")
9
-
10
-
11
- @compound("Deposit")
12
- async def is_compound_deposit(tx: TreasuryTx) -> bool:
13
- for event in await tx.get_events("Mint", sync=False):
14
- if all(arg in event for arg in ("minter", "mintTokens", "mintAmount")):
15
- minter = event["minter"]
16
- minted = tx.token.scale_value(event["mintTokens"])
17
- # cToken side
18
- if (
19
- tx.token == tx.from_address == event.address
20
- and tx.to_address == minter
21
- and minted == tx.amount
22
- ):
23
- return True
24
- # underlying side
25
- elif (
26
- tx.to_address == event.address and tx.from_address == minter and minted == tx.amount
27
- ):
28
- return True
29
- return False
30
-
31
-
32
- @compound("Withdrawal")
33
- async def is_compound_withdrawal(tx: TreasuryTx) -> bool:
34
- for event in await tx.get_events("Redeem", sync=False):
35
- if all(arg in event for arg in ("redeemer", "redeemTokens", "redeemAmount")):
36
- redeemer = event["redeemer"]
37
- redeemed = tx.token.scale_value(event["redeemTokens"])
38
- # cToken side
39
- if tx.token == event.address and tx.from_address == redeemer and redeemed == tx.amount:
40
- return True
41
- # underlying side
42
- elif (
43
- tx.to_address == redeemer
44
- and tx.from_address == event.address
45
- and redeemed == tx.amount
46
- ):
47
- return True
48
- return False
@@ -1,3 +0,0 @@
1
- dao-treasury<0.1,>=0.0.55
2
- eth-brownie<1.23,>=1.21.0
3
- faster-async-lru<3,>=2.0.5