dao-treasury 0.0.44__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.
Files changed (46) hide show
  1. {dao_treasury-0.0.44 → dao_treasury-0.0.45}/PKG-INFO +9 -2
  2. {dao_treasury-0.0.44 → dao_treasury-0.0.45}/dao_treasury/db.py +25 -3
  3. {dao_treasury-0.0.44 → dao_treasury-0.0.45}/dao_treasury/docker-compose.yaml +1 -1
  4. {dao_treasury-0.0.44 → dao_treasury-0.0.45}/dao_treasury.egg-info/PKG-INFO +10 -3
  5. {dao_treasury-0.0.44 → dao_treasury-0.0.45}/dao_treasury.egg-info/SOURCES.txt +3 -1
  6. dao_treasury-0.0.45/dao_treasury.egg-info/requires.txt +1 -0
  7. {dao_treasury-0.0.44 → dao_treasury-0.0.45}/pyproject.toml +2 -2
  8. dao_treasury-0.0.45/tests/test_treasury.py +63 -0
  9. dao_treasury-0.0.45/tests/test_wallet.py +298 -0
  10. dao_treasury-0.0.44/dao_treasury.egg-info/requires.txt +0 -1
  11. {dao_treasury-0.0.44 → dao_treasury-0.0.45}/MANIFEST.in +0 -0
  12. {dao_treasury-0.0.44 → dao_treasury-0.0.45}/README.md +0 -0
  13. {dao_treasury-0.0.44 → dao_treasury-0.0.45}/dao_treasury/.grafana/provisioning/dashboards/dashboards.yaml +0 -0
  14. {dao_treasury-0.0.44 → dao_treasury-0.0.45}/dao_treasury/.grafana/provisioning/dashboards/streams/LlamaPay.json +0 -0
  15. {dao_treasury-0.0.44 → dao_treasury-0.0.45}/dao_treasury/.grafana/provisioning/dashboards/summary/Monthly.json +0 -0
  16. {dao_treasury-0.0.44 → dao_treasury-0.0.45}/dao_treasury/.grafana/provisioning/dashboards/transactions/Treasury Transactions.json +0 -0
  17. {dao_treasury-0.0.44 → dao_treasury-0.0.45}/dao_treasury/.grafana/provisioning/dashboards/treasury/Cashflow (Including Unsorted).json +0 -0
  18. {dao_treasury-0.0.44 → dao_treasury-0.0.45}/dao_treasury/.grafana/provisioning/dashboards/treasury/Cashflow.json +0 -0
  19. {dao_treasury-0.0.44 → dao_treasury-0.0.45}/dao_treasury/.grafana/provisioning/dashboards/treasury/Operating Cashflow.json +0 -0
  20. {dao_treasury-0.0.44 → dao_treasury-0.0.45}/dao_treasury/.grafana/provisioning/dashboards/treasury/Treasury.json +0 -0
  21. {dao_treasury-0.0.44 → dao_treasury-0.0.45}/dao_treasury/.grafana/provisioning/datasources/datasources.yaml +0 -0
  22. {dao_treasury-0.0.44 → dao_treasury-0.0.45}/dao_treasury/ENVIRONMENT_VARIABLES.py +0 -0
  23. {dao_treasury-0.0.44 → dao_treasury-0.0.45}/dao_treasury/__init__.py +0 -0
  24. {dao_treasury-0.0.44 → dao_treasury-0.0.45}/dao_treasury/_docker.py +0 -0
  25. {dao_treasury-0.0.44 → dao_treasury-0.0.45}/dao_treasury/_nicknames.py +0 -0
  26. {dao_treasury-0.0.44 → dao_treasury-0.0.45}/dao_treasury/_wallet.py +0 -0
  27. {dao_treasury-0.0.44 → dao_treasury-0.0.45}/dao_treasury/constants.py +0 -0
  28. {dao_treasury-0.0.44 → dao_treasury-0.0.45}/dao_treasury/main.py +0 -0
  29. {dao_treasury-0.0.44 → dao_treasury-0.0.45}/dao_treasury/py.typed +0 -0
  30. {dao_treasury-0.0.44 → dao_treasury-0.0.45}/dao_treasury/sorting/__init__.py +0 -0
  31. {dao_treasury-0.0.44 → dao_treasury-0.0.45}/dao_treasury/sorting/_matchers.py +0 -0
  32. {dao_treasury-0.0.44 → dao_treasury-0.0.45}/dao_treasury/sorting/_rules.py +0 -0
  33. {dao_treasury-0.0.44 → dao_treasury-0.0.45}/dao_treasury/sorting/factory.py +0 -0
  34. {dao_treasury-0.0.44 → dao_treasury-0.0.45}/dao_treasury/sorting/rule.py +0 -0
  35. {dao_treasury-0.0.44 → dao_treasury-0.0.45}/dao_treasury/sorting/rules/__init__.py +0 -0
  36. {dao_treasury-0.0.44 → dao_treasury-0.0.45}/dao_treasury/sorting/rules/ignore/__init__.py +0 -0
  37. {dao_treasury-0.0.44 → dao_treasury-0.0.45}/dao_treasury/sorting/rules/ignore/llamapay.py +0 -0
  38. {dao_treasury-0.0.44 → dao_treasury-0.0.45}/dao_treasury/streams/__init__.py +0 -0
  39. {dao_treasury-0.0.44 → dao_treasury-0.0.45}/dao_treasury/streams/llamapay.py +0 -0
  40. {dao_treasury-0.0.44 → dao_treasury-0.0.45}/dao_treasury/treasury.py +0 -0
  41. {dao_treasury-0.0.44 → dao_treasury-0.0.45}/dao_treasury/types.py +0 -0
  42. {dao_treasury-0.0.44 → dao_treasury-0.0.45}/dao_treasury.egg-info/dependency_links.txt +0 -0
  43. {dao_treasury-0.0.44 → dao_treasury-0.0.45}/dao_treasury.egg-info/not-zip-safe +0 -0
  44. {dao_treasury-0.0.44 → dao_treasury-0.0.45}/dao_treasury.egg-info/top_level.txt +0 -0
  45. {dao_treasury-0.0.44 → dao_treasury-0.0.45}/setup.cfg +0 -0
  46. {dao_treasury-0.0.44 → dao_treasury-0.0.45}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: dao_treasury
3
- Version: 0.0.44
3
+ Version: 0.0.45
4
4
  Summary: Produce comprehensive financial reports for your on-chain org
5
5
  Classifier: Development Status :: 3 - Alpha
6
6
  Classifier: Intended Audience :: Developers
@@ -14,6 +14,13 @@ Classifier: Operating System :: OS Independent
14
14
  Classifier: Topic :: Software Development :: Libraries
15
15
  Requires-Python: >=3.10,<3.13
16
16
  Description-Content-Type: text/markdown
17
+ Requires-Dist: eth-portfolio-temp<0.2,>=0.1.7.dev0
18
+ Dynamic: classifier
19
+ Dynamic: description
20
+ Dynamic: description-content-type
21
+ Dynamic: requires-dist
22
+ Dynamic: requires-python
23
+ Dynamic: summary
17
24
 
18
25
  DAO Treasury is a comprehensive financial reporting and treasury management solution designed specifically for decentralized organizations. Built as an extension to [eth-portfolio](https://github.com/BobTheBuidler/eth-portfolio)'s [Portfolio Exporter](https://bobthebuidler.github.io/eth-portfolio/exporter.html), DAO Treasury automates the collection and visualization of financial data, enabling organizations to monitor and report on treasury activities with clarity and transparency.
19
26
 
@@ -23,7 +23,18 @@ from functools import lru_cache
23
23
  from logging import getLogger
24
24
  from os import path
25
25
  from pathlib import Path
26
- from typing import TYPE_CHECKING, Dict, Final, Tuple, Union, final
26
+ from typing import (
27
+ TYPE_CHECKING,
28
+ Any,
29
+ Coroutine,
30
+ Dict,
31
+ Final,
32
+ Literal,
33
+ Tuple,
34
+ Union,
35
+ final,
36
+ overload,
37
+ )
27
38
  from datetime import date, datetime, time, timezone
28
39
 
29
40
  import eth_portfolio
@@ -33,13 +44,14 @@ from brownie.convert.datatypes import HexString
33
44
  from brownie.exceptions import EventLookupError
34
45
  from brownie.network.event import EventDict, _EventItem
35
46
  from brownie.network.transaction import TransactionReceipt
36
- from eth_typing import ChecksumAddress, HexAddress, HexStr
37
47
  from eth_portfolio.structs import (
38
48
  InternalTransfer,
39
49
  LedgerEntry,
40
50
  TokenTransfer,
41
51
  Transaction,
42
52
  )
53
+ from eth_retry import auto_retry
54
+ from eth_typing import ChecksumAddress, HexAddress, HexStr
43
55
  from pony.orm import (
44
56
  Database,
45
57
  InterfaceError,
@@ -71,6 +83,7 @@ SQLITE_DIR.mkdir(parents=True, exist_ok=True)
71
83
 
72
84
  _INSERT_THREAD = AsyncThreadPoolExecutor(1)
73
85
  _SORT_THREAD = AsyncThreadPoolExecutor(1)
86
+ _EVENTS_THREADS = AsyncThreadPoolExecutor(16)
74
87
  _SORT_SEMAPHORE = Semaphore(50)
75
88
 
76
89
  _UTC = timezone.utc
@@ -731,7 +744,15 @@ class TreasuryTx(DbEntity):
731
744
  """Decoded event logs for this transaction."""
732
745
  return self._transaction.events
733
746
 
734
- def get_events(self, event_name: str) -> _EventItem:
747
+ @overload
748
+ def get_events(
749
+ self, event_name: str, sync: Literal[False]
750
+ ) -> Coroutine[Any, Any, _EventItem]: ...
751
+ @overload
752
+ def get_events(self, event_name: str, sync: bool = True) -> _EventItem: ...
753
+ def get_events(self, event_name: str, sync: bool = True) -> _EventItem:
754
+ if not sync:
755
+ return _EVENTS_THREADS.run(self.get_events, event_name)
735
756
  try:
736
757
  return self.events[event_name]
737
758
  except EventLookupError:
@@ -748,6 +769,7 @@ class TreasuryTx(DbEntity):
748
769
  return get_transaction(self.hash)
749
770
 
750
771
  @staticmethod
772
+ @auto_retry
751
773
  async def insert(entry: LedgerEntry) -> None:
752
774
  """Asynchronously insert and sort a ledger entry.
753
775
 
@@ -5,7 +5,7 @@ networks:
5
5
 
6
6
  services:
7
7
  grafana:
8
- image: grafana/grafana:12.1.0
8
+ image: grafana/grafana:12.1.1
9
9
  ports:
10
10
  - 127.0.0.1:${DAO_TREASURY_GRAFANA_PORT:-3004}:3000
11
11
  environment:
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
2
- Name: dao-treasury
3
- Version: 0.0.44
1
+ Metadata-Version: 2.4
2
+ Name: dao_treasury
3
+ Version: 0.0.45
4
4
  Summary: Produce comprehensive financial reports for your on-chain org
5
5
  Classifier: Development Status :: 3 - Alpha
6
6
  Classifier: Intended Audience :: Developers
@@ -14,6 +14,13 @@ Classifier: Operating System :: OS Independent
14
14
  Classifier: Topic :: Software Development :: Libraries
15
15
  Requires-Python: >=3.10,<3.13
16
16
  Description-Content-Type: text/markdown
17
+ Requires-Dist: eth-portfolio-temp<0.2,>=0.1.7.dev0
18
+ Dynamic: classifier
19
+ Dynamic: description
20
+ Dynamic: description-content-type
21
+ Dynamic: requires-dist
22
+ Dynamic: requires-python
23
+ Dynamic: summary
17
24
 
18
25
  DAO Treasury is a comprehensive financial reporting and treasury management solution designed specifically for decentralized organizations. Built as an extension to [eth-portfolio](https://github.com/BobTheBuidler/eth-portfolio)'s [Portfolio Exporter](https://bobthebuidler.github.io/eth-portfolio/exporter.html), DAO Treasury automates the collection and visualization of financial data, enabling organizations to monitor and report on treasury activities with clarity and transparency.
19
26
 
@@ -38,4 +38,6 @@ dao_treasury/sorting/rules/__init__.py
38
38
  dao_treasury/sorting/rules/ignore/__init__.py
39
39
  dao_treasury/sorting/rules/ignore/llamapay.py
40
40
  dao_treasury/streams/__init__.py
41
- dao_treasury/streams/llamapay.py
41
+ dao_treasury/streams/llamapay.py
42
+ tests/test_treasury.py
43
+ tests/test_wallet.py
@@ -0,0 +1 @@
1
+ eth-portfolio-temp<0.2,>=0.1.7.dev0
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "dao-treasury"
3
- version = "0.0.44"
3
+ version = "0.0.45"
4
4
  description = "Produce comprehensive financial reports for your on-chain org"
5
5
  authors = ["BobTheBuidler <bobthebuidlerdefi@gmail.com>"]
6
6
  license = "MIT"
@@ -11,7 +11,7 @@ dao-treasury = "dao_treasury.main:main"
11
11
 
12
12
  [tool.poetry.dependencies]
13
13
  python = ">=3.10,<3.13"
14
- eth-portfolio-temp = ">=0.1.6.dev0,<0.2"
14
+ eth-portfolio-temp = ">=0.1.7.dev0,<0.2"
15
15
 
16
16
  [tool.poetry.group.dev.dependencies]
17
17
  pytest = "^6.2.5"
@@ -0,0 +1,63 @@
1
+ import pytest
2
+ from unittest.mock import AsyncMock, patch
3
+
4
+ from brownie.convert.datatypes import EthAddress
5
+
6
+ from dao_treasury import Treasury, TreasuryWallet, treasury
7
+
8
+
9
+ @pytest.fixture(autouse=True)
10
+ def reset_treasury():
11
+ # Clear global state before each test
12
+ treasury.TREASURY = None
13
+ yield
14
+ # Clear global state after each test
15
+ treasury.TREASURY = None
16
+
17
+
18
+ def test_successful_creation():
19
+ wallets = ["0x0000000000000000000000000000000000000001"]
20
+ treasury = Treasury(wallets)
21
+
22
+ assert isinstance(treasury, Treasury)
23
+ assert all(isinstance(wallet, TreasuryWallet) for wallet in treasury.wallets)
24
+
25
+
26
+ def test_wallet_processing():
27
+ wallets = [
28
+ "0x0000000000000000000000000000000000000001",
29
+ EthAddress("0x0000000000000000000000000000000000000002"),
30
+ TreasuryWallet("0x0000000000000000000000000000000000000003"),
31
+ ]
32
+ treasury = Treasury(wallets)
33
+ assert treasury.wallets[0].address == "0x0000000000000000000000000000000000000001"
34
+ assert treasury.wallets[1].address == "0x0000000000000000000000000000000000000002"
35
+ assert treasury.wallets[2].address == "0x0000000000000000000000000000000000000003"
36
+
37
+
38
+ def test_invalid_wallet_type():
39
+ with pytest.raises(TypeError):
40
+ Treasury(["0x0000000000000000000000000000000000000001", 123])
41
+
42
+
43
+ def test_asynchronous_creation():
44
+ wallets = ["0x0000000000000000000000000000000000000001"]
45
+ treasury = Treasury(wallets, asynchronous=True)
46
+ assert treasury.asynchronous == True
47
+
48
+
49
+ @patch(
50
+ "eth_portfolio_scripts._portfolio.ExportablePortfolio.describe",
51
+ new_callable=AsyncMock,
52
+ )
53
+ # @pytest.mark.asyncio
54
+ def test_describe(mock_describe):
55
+ wallets = ["0x0000000000000000000000000000000000000001"]
56
+ treasury = Treasury(wallets)
57
+
58
+ block = 1234567
59
+ mock_describe.return_value = {"balance": 100}
60
+ result = treasury.describe(block)
61
+
62
+ mock_describe.assert_awaited_once_with(block)
63
+ assert result == {"balance": 100}
@@ -0,0 +1,298 @@
1
+ import pytest
2
+ from brownie.convert.datatypes import EthAddress
3
+
4
+ from dao_treasury import TreasuryWallet
5
+
6
+
7
+ def test_successful_creation():
8
+ address = "0x0000000000000000000000000000000000000001"
9
+ wallet = TreasuryWallet(address)
10
+ assert wallet.address == EthAddress(address)
11
+ assert wallet.start_block is None
12
+ assert wallet.end_block is None
13
+ assert wallet.start_timestamp is None
14
+ assert wallet.end_timestamp is None
15
+
16
+
17
+ def test_eth_address_conversion():
18
+ address = "0x0000000000000000000000000000000000000001"
19
+ wallet = TreasuryWallet(address)
20
+ assert isinstance(wallet.address, EthAddress)
21
+
22
+
23
+ def test_conflicting_start_block_and_timestamp():
24
+ address = "0x0000000000000000000000000000000000000001"
25
+ with pytest.raises(ValueError) as excinfo:
26
+ TreasuryWallet(address, start_block=1, start_timestamp=1234567890)
27
+ assert "You can only pass a start block or a start timestamp" in str(excinfo.value)
28
+
29
+
30
+ def test_conflicting_end_block_and_timestamp():
31
+ address = "0x0000000000000000000000000000000000000001"
32
+ with pytest.raises(ValueError) as excinfo:
33
+ TreasuryWallet(address, end_block=1, end_timestamp=1234567890)
34
+ assert "You can only pass an end block or an end timestamp" in str(excinfo.value)
35
+
36
+
37
+ """
38
+ def test_conflicting_start_block_and_end_block():
39
+ address = "0x0000000000000000000000000000000000000001"
40
+ with pytest.raises(ValueError) as excinfo:
41
+ TreasuryWallet(address, end_block=1, end_timestamp=1234567890)
42
+ assert "You can only pass an end block or an end timestamp" in str(excinfo.value)
43
+
44
+ def test_conflicting_start_timestamp_and_end_timestamp():
45
+ address = "0x0000000000000000000000000000000000000001"
46
+ with pytest.raises(ValueError) as excinfo:
47
+ TreasuryWallet(address, end_block=1, end_timestamp=1234567890)
48
+ assert "You can only pass an end block or an end timestamp" in str(excinfo.value)
49
+
50
+ def test_conflicting_start_block_and_end_timestamp():
51
+ address = "0x0000000000000000000000000000000000000001"
52
+ with pytest.raises(ValueError) as excinfo:
53
+ TreasuryWallet(address, end_block=1, end_timestamp=1234567890)
54
+ assert "You can only pass an end block or an end timestamp" in str(excinfo.value)
55
+
56
+ def test_conflicting_start_timestamp_and_end_block():
57
+ address = "0x0000000000000000000000000000000000000001"
58
+ with pytest.raises(ValueError) as excinfo:
59
+ TreasuryWallet(address, end_block=1, end_timestamp=1234567890)
60
+ assert "You can only pass an end block or an end timestamp" in str(excinfo.value)
61
+ """
62
+
63
+
64
+ @pytest.mark.parametrize(
65
+ "wallet_address, other, expected, id",
66
+ [
67
+ # Happy path: same address, both as hex strings
68
+ (
69
+ "0x000000000000000000000000000000000000dead",
70
+ "0x000000000000000000000000000000000000dead",
71
+ True,
72
+ "same_hex_string",
73
+ ),
74
+ # Happy path: same address, other as checksummed
75
+ (
76
+ "0x000000000000000000000000000000000000dEaD",
77
+ "0x000000000000000000000000000000000000dead",
78
+ True,
79
+ "same_mixed_case",
80
+ ),
81
+ # Happy path: different addresses
82
+ (
83
+ "0x000000000000000000000000000000000000dead",
84
+ "0x000000000000000000000000000000000000beef",
85
+ False,
86
+ "different_addresses",
87
+ ),
88
+ # Edge: other is already a bytes address
89
+ (
90
+ "0x000000000000000000000000000000000000dead",
91
+ bytes.fromhex("000000000000000000000000000000000000dead"),
92
+ TypeError,
93
+ "other_bytes",
94
+ ),
95
+ # Edge: wallet address is checksummed, other is lower
96
+ (
97
+ "0x000000000000000000000000000000000000dEaD",
98
+ "0x000000000000000000000000000000000000dead",
99
+ True,
100
+ "wallet_checksummed",
101
+ ),
102
+ # Edge: wallet address is lower, other is checksummed
103
+ (
104
+ "0x000000000000000000000000000000000000dead",
105
+ "0x000000000000000000000000000000000000dEaD",
106
+ True,
107
+ "other_checksummed",
108
+ ),
109
+ # Edge: other is an int (invalid, should raise)
110
+ ("0x000000000000000000000000000000000000dead", 12345, TypeError, "other_int"),
111
+ # Edge: other is None (invalid, should raise)
112
+ ("0x000000000000000000000000000000000000dead", None, TypeError, "other_none"),
113
+ # Edge: wallet address is invalid (should raise on instantiation)
114
+ (
115
+ "not_an_address",
116
+ "0x000000000000000000000000000000000000dead",
117
+ ValueError,
118
+ "wallet_invalid",
119
+ ),
120
+ # Edge: other is invalid address string (should raise)
121
+ (
122
+ "0x000000000000000000000000000000000000dead",
123
+ "not_an_address",
124
+ TypeError,
125
+ "other_invalid",
126
+ ),
127
+ ],
128
+ ids=lambda x: x if isinstance(x, str) else None,
129
+ )
130
+ def test_wallet_eq(wallet_address, other, expected, id):
131
+ # sourcery skip: no-conditionals-in-tests
132
+ try:
133
+ exc_expected = issubclass(expected, Exception)
134
+ except TypeError:
135
+ exc_expected = False
136
+
137
+ if exc_expected:
138
+ with pytest.raises(expected):
139
+ wallet = TreasuryWallet(address=wallet_address)
140
+ _ = wallet.address == other
141
+
142
+ else:
143
+ wallet = TreasuryWallet(address=wallet_address)
144
+ result = wallet.address == other
145
+
146
+ # Assert
147
+ assert result is expected
148
+
149
+
150
+ # We cannot mock closest_block_after_timestamp, so we must use real values.
151
+ # closest_block_after_timestamp will be called with the provided timestamp.
152
+ # For edge cases, we can use 0 or very large/small timestamps.
153
+
154
+
155
+ @pytest.mark.parametrize(
156
+ "start_block, start_timestamp, expected, id",
157
+ [
158
+ # Happy path: start_block is set
159
+ (12345, None, 12345, "start_block_set"),
160
+ # Happy path: start_block is None, start_timestamp is set (use a realistic timestamp)
161
+ (None, 1700000000, None, "start_timestamp_set"),
162
+ # Edge: both start_block and start_timestamp are None
163
+ (None, None, 0, "both_none"),
164
+ # Edge: start_block is 0
165
+ (0, None, 0, "start_block_zero"),
166
+ # Edge: start_timestamp is 0
167
+ (None, 0, None, "start_timestamp_zero"),
168
+ ],
169
+ ids=lambda x: x if isinstance(x, str) else None,
170
+ )
171
+ def test_start_block_property(start_block, start_timestamp, expected, id):
172
+ # Arrange
173
+ # Use a valid EthAddress string (20 bytes hex)
174
+ address = "0x000000000000000000000000000000000000dead"
175
+ wallet = TreasuryWallet(
176
+ address=address,
177
+ start_block=start_block,
178
+ start_timestamp=start_timestamp,
179
+ )
180
+
181
+ # Act
182
+ result = wallet._start_block
183
+
184
+ # Assert
185
+ if start_block is not None:
186
+ assert result == expected
187
+ elif start_timestamp is not None:
188
+ # We can't know the exact block, but it should be an int and >= 0
189
+ assert isinstance(result, int)
190
+ # For negative timestamps, the block may be negative, but for 0 or positive, should be >= 0
191
+ if start_timestamp >= 0:
192
+ assert result >= 0
193
+ else:
194
+ assert result == 0
195
+
196
+
197
+ @pytest.mark.parametrize(
198
+ "start_block, start_timestamp, expected_exception, id",
199
+ [
200
+ # Error: both start_block and start_timestamp are set
201
+ (123, 456, ValueError, "both_start_block_and_start_timestamp"),
202
+ # Error: start_block is not an int (e.g., string)
203
+ ("not_a_block", None, TypeError, "start_block_invalid_type"),
204
+ # Error: start_timestamp is not an int (e.g., string)
205
+ (None, "not_a_timestamp", TypeError, "start_timestamp_invalid_type"),
206
+ # Error: both are invalid
207
+ ("not_a_block", "not_a_timestamp", TypeError, "both_invalid"),
208
+ # Error: start_block is negative
209
+ (-1, None, ValueError, "start_block_negative"),
210
+ # Error: start_timestamp is negative
211
+ (None, -100, ValueError, "start_timestamp_negative"),
212
+ ],
213
+ ids=lambda x: x if isinstance(x, str) else None,
214
+ )
215
+ def test_start_block_property_errors(
216
+ start_block, start_timestamp, expected_exception, id
217
+ ):
218
+ # Arrange
219
+ address = "0x000000000000000000000000000000000000dead"
220
+
221
+ # Act & Assert
222
+ with pytest.raises(expected_exception):
223
+ TreasuryWallet(
224
+ address=address,
225
+ start_block=start_block,
226
+ start_timestamp=start_timestamp,
227
+ )._start_block
228
+
229
+
230
+ @pytest.mark.parametrize(
231
+ "end_block, end_timestamp, expected, id",
232
+ [
233
+ # Happy path: end_block is set
234
+ (123456, None, 123456, "end_block_set"),
235
+ # Happy path: end_block is None, end_timestamp is set (realistic timestamp)
236
+ (None, 1700000000, None, "end_timestamp_set"),
237
+ # Edge: both end_block and end_timestamp are None
238
+ (None, None, None, "both_none"),
239
+ # Edge: end_block is 0
240
+ (0, None, 0, "end_block_zero"),
241
+ # Edge: end_timestamp is 0
242
+ (None, 0, None, "end_timestamp_zero"),
243
+ ],
244
+ ids=lambda x: x if isinstance(x, str) else None,
245
+ )
246
+ def test_end_block_property(end_block, end_timestamp, expected, id):
247
+ # Arrange
248
+ address = "0x000000000000000000000000000000000000dead"
249
+ wallet = TreasuryWallet(
250
+ address=address,
251
+ end_block=end_block,
252
+ end_timestamp=end_timestamp,
253
+ )
254
+
255
+ # Act
256
+ result = wallet._end_block
257
+
258
+ # Assert
259
+ if end_block is not None:
260
+ assert result == expected
261
+ elif end_timestamp is not None:
262
+ # We can't know the exact block, but it should be an int and >= 0
263
+ assert isinstance(result, int), (type(result), result)
264
+ if end_timestamp >= 0:
265
+ assert result >= 0
266
+ else:
267
+ assert result is None
268
+
269
+
270
+ @pytest.mark.parametrize(
271
+ "end_block, end_timestamp, expected_exception, id",
272
+ [
273
+ # Error: both end_block and end_timestamp are set
274
+ (123, 456, ValueError, "both_end_block_and_end_timestamp"),
275
+ # Error: end_block is not an int (e.g., string)
276
+ ("not_a_block", None, TypeError, "end_block_invalid_type"),
277
+ # Error: end_timestamp is not an int (e.g., string)
278
+ (None, "not_a_timestamp", TypeError, "end_timestamp_invalid_type"),
279
+ # Error: both are invalid
280
+ ("not_a_block", "not_a_timestamp", ValueError, "both_invalid"),
281
+ # Edge: end_block is negative
282
+ (-1, None, ValueError, "end_block_negative"),
283
+ # Edge: end_timestamp is negative
284
+ (None, -100, ValueError, "end_timestamp_negative"),
285
+ ],
286
+ ids=lambda x: x if isinstance(x, str) else None,
287
+ )
288
+ def test_end_block_property_errors(end_block, end_timestamp, expected_exception, id):
289
+ # Arrange
290
+ address = "0x000000000000000000000000000000000000dead"
291
+
292
+ # Act & Assert
293
+ with pytest.raises(expected_exception):
294
+ TreasuryWallet(
295
+ address=address,
296
+ end_block=end_block,
297
+ end_timestamp=end_timestamp,
298
+ )._end_block
@@ -1 +0,0 @@
1
- eth-portfolio-temp<0.2,>=0.1.6.dev0
File without changes
File without changes
File without changes
File without changes