yearn-treasury 0.0.32__cp310-cp310-macosx_11_0_arm64.whl → 0.0.48__cp310-cp310-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 (76) hide show
  1. yearn_treasury/_db.cpython-310-darwin.so +0 -0
  2. yearn_treasury/_db.py +8 -0
  3. yearn_treasury/_ens.cpython-310-darwin.so +0 -0
  4. yearn_treasury/_logging.cpython-310-darwin.so +0 -0
  5. yearn_treasury/_logging.py +17 -1
  6. yearn_treasury/address_labels.yaml +6 -0
  7. yearn_treasury/budget/__init__.cpython-310-darwin.so +0 -0
  8. yearn_treasury/budget/_request.cpython-310-darwin.so +0 -0
  9. yearn_treasury/budget/_requests.cpython-310-darwin.so +0 -0
  10. yearn_treasury/constants.py +3 -0
  11. yearn_treasury/main.py +43 -22
  12. yearn_treasury/rules/__init__.py +14 -0
  13. yearn_treasury/rules/constants.cpython-310-darwin.so +0 -0
  14. yearn_treasury/rules/cost_of_revenue/gas.cpython-310-darwin.so +0 -0
  15. yearn_treasury/rules/expense/__init__.cpython-310-darwin.so +0 -0
  16. yearn_treasury/rules/expense/general.cpython-310-darwin.so +0 -0
  17. yearn_treasury/rules/expense/infrastructure.cpython-310-darwin.so +0 -0
  18. yearn_treasury/rules/expense/people.cpython-310-darwin.so +0 -0
  19. yearn_treasury/rules/expense/security.cpython-310-darwin.so +0 -0
  20. yearn_treasury/rules/ignore/general.cpython-310-darwin.so +0 -0
  21. yearn_treasury/rules/ignore/passthru.py +1 -1
  22. yearn_treasury/rules/ignore/swaps/__init__.py +1 -0
  23. yearn_treasury/rules/ignore/swaps/aave.py +15 -8
  24. yearn_treasury/rules/ignore/swaps/auctions.cpython-310-darwin.so +0 -0
  25. yearn_treasury/rules/ignore/swaps/auctions.py +31 -0
  26. yearn_treasury/rules/ignore/swaps/compound.py +28 -18
  27. yearn_treasury/rules/ignore/swaps/conversion_factory.cpython-310-darwin.so +0 -0
  28. yearn_treasury/rules/ignore/swaps/cowswap.py +16 -13
  29. yearn_treasury/rules/ignore/swaps/curve.py +46 -15
  30. yearn_treasury/rules/ignore/swaps/gearbox.cpython-310-darwin.so +0 -0
  31. yearn_treasury/rules/ignore/swaps/iearn.cpython-310-darwin.so +0 -0
  32. yearn_treasury/rules/ignore/swaps/iearn.py +4 -4
  33. yearn_treasury/rules/ignore/swaps/otc.cpython-310-darwin.so +0 -0
  34. yearn_treasury/rules/ignore/swaps/pooltogether.cpython-310-darwin.so +0 -0
  35. yearn_treasury/rules/ignore/swaps/synthetix.cpython-310-darwin.so +0 -0
  36. yearn_treasury/rules/ignore/swaps/uniswap.py +26 -6
  37. yearn_treasury/rules/ignore/swaps/unwrapper.cpython-310-darwin.so +0 -0
  38. yearn_treasury/rules/ignore/swaps/vaults.cpython-310-darwin.so +0 -0
  39. yearn_treasury/rules/ignore/swaps/vaults.py +11 -9
  40. yearn_treasury/rules/ignore/swaps/woofy.cpython-310-darwin.so +0 -0
  41. yearn_treasury/rules/ignore/swaps/yfi.cpython-310-darwin.so +0 -0
  42. yearn_treasury/rules/ignore/swaps/yfi.py +17 -7
  43. yearn_treasury/rules/ignore/swaps/yla.cpython-310-darwin.so +0 -0
  44. yearn_treasury/rules/ignore/unit.cpython-310-darwin.so +0 -0
  45. yearn_treasury/rules/ignore/weth.cpython-310-darwin.so +0 -0
  46. yearn_treasury/rules/ignore/ygov.cpython-310-darwin.so +0 -0
  47. yearn_treasury/rules/other_expense/__init__.cpython-310-darwin.so +0 -0
  48. yearn_treasury/rules/other_expense/boost.cpython-310-darwin.so +0 -0
  49. yearn_treasury/rules/other_expense/bugs.cpython-310-darwin.so +0 -0
  50. yearn_treasury/rules/other_expense/donations.cpython-310-darwin.so +0 -0
  51. yearn_treasury/rules/other_expense/dyfi.cpython-310-darwin.so +0 -0
  52. yearn_treasury/rules/other_expense/events.cpython-310-darwin.so +0 -0
  53. yearn_treasury/rules/other_expense/misc.cpython-310-darwin.so +0 -0
  54. yearn_treasury/rules/other_expense/revshare.cpython-310-darwin.so +0 -0
  55. yearn_treasury/rules/other_income/__init__.cpython-310-darwin.so +0 -0
  56. yearn_treasury/rules/other_income/airdrops.cpython-310-darwin.so +0 -0
  57. yearn_treasury/rules/other_income/misc.cpython-310-darwin.so +0 -0
  58. yearn_treasury/rules/other_income/misc.py +1 -1
  59. yearn_treasury/rules/revenue/bribes.cpython-310-darwin.so +0 -0
  60. yearn_treasury/rules/revenue/farming.cpython-310-darwin.so +0 -0
  61. yearn_treasury/rules/revenue/keepcoins.cpython-310-darwin.so +0 -0
  62. yearn_treasury/rules/revenue/seasolver.cpython-310-darwin.so +0 -0
  63. yearn_treasury/rules/revenue/vaults.py +14 -10
  64. yearn_treasury/rules/revenue/yteams.cpython-310-darwin.so +0 -0
  65. yearn_treasury/shitcoins.py +33 -6
  66. yearn_treasury/vaults.cpython-310-darwin.so +0 -0
  67. yearn_treasury/vaults.py +3 -4
  68. yearn_treasury/yteams.py +208 -0
  69. {yearn_treasury-0.0.32.dist-info → yearn_treasury-0.0.48.dist-info}/METADATA +3 -3
  70. {yearn_treasury-0.0.32.dist-info → yearn_treasury-0.0.48.dist-info}/RECORD +74 -68
  71. yearn_treasury-0.0.48.dist-info/top_level.txt +2 -0
  72. yearn_treasury__mypyc.cpython-310-darwin.so +0 -0
  73. 0e100735679f22f49703__mypyc.cpython-310-darwin.so +0 -0
  74. yearn_treasury-0.0.32.dist-info/top_level.txt +0 -2
  75. {yearn_treasury-0.0.32.dist-info → yearn_treasury-0.0.48.dist-info}/WHEEL +0 -0
  76. {yearn_treasury-0.0.32.dist-info → yearn_treasury-0.0.48.dist-info}/entry_points.txt +0 -0
Binary file
yearn_treasury/_db.py CHANGED
@@ -20,6 +20,14 @@ from yearn_treasury import constants
20
20
 
21
21
 
22
22
  def prepare_db() -> None:
23
+ """
24
+ Set up address nicknames in the Yearn Treasury database.
25
+
26
+ Maps key Yearn Treasury addresses to human-readable labels for improved
27
+ clarity in analytics and reporting. This function is typically called
28
+ during database preparation to ensure wallet addresses are labeled
29
+ within the DAO Treasury database entity system.
30
+ """
23
31
  chad = {Network.Mainnet: "y", Network.Fantom: "f"}[CHAINID] # type: ignore [index]
24
32
 
25
33
  labels = {
Binary file
@@ -1,6 +1,13 @@
1
1
  # mypy: disable-error-code="list-item"
2
2
  """
3
- This file contains logic for suppressing eth-portfolio error logs where desired.
3
+ Error log suppression utilities for the Yearn Treasury exporter.
4
+
5
+ This module suppresses noisy or irrelevant eth-portfolio error logs for specific
6
+ token addresses that are known to be deprecated or otherwise unpricable.
7
+
8
+ To suppress logs for additional tokens, add their addresses to the
9
+ `suppress_logs_for[Network.<chain>]` mapping. The rest will be done
10
+ automatically on package import.
4
11
  """
5
12
 
6
13
  from typing import Dict, Final, List
@@ -18,10 +25,19 @@ suppress_logs_for: Final[Dict[Network, List[HexAddress]]] = {
18
25
  "0xBF7AA989192b020a8d3e1C65a558e123834325cA", # unpriceable yvWBTC - This vault had a bug and does not have a pricePerShare
19
26
  "0x5aFE3855358E112B5647B952709E6165e1c1eEEe", # SAFE - This was not tradeable at the time of the first airdrops
20
27
  "0x718AbE90777F5B778B52D553a5aBaa148DD0dc5D", # yvCurve-alETH - The underlying curve pool had an issue and is unpriceable
28
+ "0x3819f64f282bf135d62168C1e513280dAF905e06", # HDRN
29
+ "0x5fAa989Af96Af85384b8a938c2EdE4A7378D9875", # GAL
21
30
  ],
22
31
  }
23
32
 
24
33
 
25
34
  def setup_eth_portfolio_logging() -> None:
35
+ """
36
+ Suppress eth-portfolio error logs for specific tokens on the current chain.
37
+
38
+ Appends token addresses from the suppress_logs_for mapping (for the current
39
+ CHAINID) to the SUPPRESS_ERROR_LOGS list, preventing error logs for these
40
+ tokens from being emitted during analytics and reporting.
41
+ """
26
42
  for token in suppress_logs_for.get(CHAINID, []): # type: ignore [call-overload]
27
43
  SUPPRESS_ERROR_LOGS.append(to_checksum_address(token))
@@ -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"
@@ -87,3 +87,6 @@ class Args:
87
87
 
88
88
  nicknames: Final[Path] = _YEARN_TREASURY_ROOT_DIR / "address_labels.yaml"
89
89
  """The path where yearn-treasury's address nicknames are defined."""
90
+
91
+ custom_bucket: Final[list[str]] = [f"{YFI}:Other long term assets"]
92
+ """Custom bucket mapping for token addresses, tells DAO Treasury to categorize YFI as 'Other long term assets'."""
yearn_treasury/main.py CHANGED
@@ -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: 50",
46
- default=50,
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
@@ -1,6 +1,20 @@
1
+ import os
2
+
3
+ __ALRU_ENV_NAME = "ASYNC_LRU_ALLOW_SYNC"
4
+ __ALRU_ENV_VAL = os.environ.get(__ALRU_ENV_NAME)
5
+ os.environ[__ALRU_ENV_NAME] = "1"
6
+
1
7
  from yearn_treasury.rules.cost_of_revenue import *
2
8
  from yearn_treasury.rules.expense import *
3
9
  from yearn_treasury.rules.ignore import *
4
10
  from yearn_treasury.rules.other_expense import *
5
11
  from yearn_treasury.rules.other_income import *
6
12
  from yearn_treasury.rules.revenue import *
13
+
14
+ if __ALRU_ENV_VAL is None:
15
+ os.environ.pop(__ALRU_ENV_NAME)
16
+ else:
17
+ os.environ[__ALRU_ENV_NAME] = __ALRU_ENV_VAL
18
+
19
+ del __ALRU_ENV_NAME
20
+ del __ALRU_ENV_VAL
@@ -44,7 +44,7 @@ def is_ycrv(tx: TreasuryTx) -> bool:
44
44
  if tx.from_address == owner and tx.token == sell_token and buy_token == ycrv:
45
45
  scaled = Decimal(sell_amount) / 10**18
46
46
  # TODO: remove this rounding when we implement postgres
47
- if scaled == tx.amount:
47
+ if round(scaled, 11) == round(tx.amount, 11):
48
48
  return True
49
49
  print(f"bribes for ycrv amount no match: [{scaled}, {tx.amount}]")
50
50
 
@@ -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
@@ -15,17 +15,21 @@ async def is_compound_deposit(tx: TreasuryTx) -> bool:
15
15
  minter = event["minter"]
16
16
  minted = tx.token.scale_value(event["mintTokens"])
17
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
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
+ )
24
25
  # underlying side
25
- elif (
26
- tx.to_address == event.address and tx.from_address == minter and minted == tx.amount
27
- ):
28
- return True
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
+ )
29
33
  return False
30
34
 
31
35
 
@@ -36,13 +40,19 @@ async def is_compound_withdrawal(tx: TreasuryTx) -> bool:
36
40
  redeemer = event["redeemer"]
37
41
  redeemed = tx.token.scale_value(event["redeemTokens"])
38
42
  # cToken side
39
- if tx.token == event.address and tx.from_address == redeemer and redeemed == tx.amount:
40
- return True
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
+ )
41
50
  # 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
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
+ )
48
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
 
@@ -1,10 +1,10 @@
1
1
  from typing import Final
2
2
 
3
- from async_lru import alru_cache
4
3
  from brownie.exceptions import EventLookupError
5
4
  from brownie.network.event import _EventItem
6
5
  from dao_treasury import TreasuryTx, TreasuryWallet
7
6
  from eth_typing import ChecksumAddress
7
+ from faster_async_lru import alru_cache
8
8
  from y import Contract, Network
9
9
 
10
10
  from yearn_treasury.constants import CHAINID, ZERO_ADDRESS
@@ -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
 
@@ -120,19 +141,29 @@ async def _is_curve_withdrawal_multi(tx: TreasuryTx) -> bool:
120
141
  pool = await Contract.coroutine(event.address) # type: ignore [assignment]
121
142
  if await _is_old_style(tx, pool) or _is_new_style(tx, pool):
122
143
  return True
123
- print(f"wtf is this: {tx}")
144
+ print(
145
+ f"unhandled curve pool: {tx} symbol={tx.symbol} name={tx.token.name} address={tx.token_address}"
146
+ )
124
147
  # Tokens rec'd
125
148
  elif tx.from_address == event.address and TreasuryWallet.check_membership(
126
149
  tx.to_address.address, tx.block # type: ignore [union-attr, arg-type]
127
150
  ):
151
+ tx_amount = round(tx.amount, 7)
128
152
  try:
129
153
  for i, amount in enumerate(event["token_amounts"]):
130
- 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:
131
157
  pool = await Contract.coroutine(event.address) # type: ignore [assignment]
132
158
  if hasattr(pool, "underlying_coins"):
133
- return tx.token == await pool.underlying_coins.coroutine(i)
159
+ coin: ChecksumAddress = await pool.underlying_coins.coroutine(i)
160
+ return tx.token == coin
134
161
  else:
135
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
+ )
136
167
  except EventLookupError:
137
168
  # some other event has different keys, maybe we need to implement logic to capture these. time will tell.
138
169
  pass
@@ -1,5 +1,5 @@
1
1
  # mypy: disable-error-code=dict-item
2
- from typing import Dict, Final
2
+ from typing import Dict, Final, cast
3
3
 
4
4
  from dao_treasury import TreasuryTx
5
5
  from eth_typing import ChecksumAddress
@@ -36,8 +36,8 @@ POOL_TO_UNDERLYING: Final[Dict[ChecksumAddress, ChecksumAddress]] = {
36
36
  def is_iearn_withdrawal(tx: TreasuryTx) -> bool:
37
37
  # Vault side
38
38
  if tx.to_address == ZERO_ADDRESS:
39
- return tx.token.address.address in POOLS
39
+ return tx.token_address in POOLS
40
40
  # Token side
41
- from_address: ChecksumAddress = tx.from_address.address # type: ignore [union-attr, assignment]
42
- token_address = tx.token.address.address
41
+ from_address = cast(ChecksumAddress, tx.from_address.address)
42
+ token_address = tx.token_address
43
43
  return POOL_TO_UNDERLYING.get(from_address) == token_address