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.
- {dao_treasury-0.0.44 → dao_treasury-0.0.45}/PKG-INFO +9 -2
- {dao_treasury-0.0.44 → dao_treasury-0.0.45}/dao_treasury/db.py +25 -3
- {dao_treasury-0.0.44 → dao_treasury-0.0.45}/dao_treasury/docker-compose.yaml +1 -1
- {dao_treasury-0.0.44 → dao_treasury-0.0.45}/dao_treasury.egg-info/PKG-INFO +10 -3
- {dao_treasury-0.0.44 → dao_treasury-0.0.45}/dao_treasury.egg-info/SOURCES.txt +3 -1
- dao_treasury-0.0.45/dao_treasury.egg-info/requires.txt +1 -0
- {dao_treasury-0.0.44 → dao_treasury-0.0.45}/pyproject.toml +2 -2
- dao_treasury-0.0.45/tests/test_treasury.py +63 -0
- dao_treasury-0.0.45/tests/test_wallet.py +298 -0
- dao_treasury-0.0.44/dao_treasury.egg-info/requires.txt +0 -1
- {dao_treasury-0.0.44 → dao_treasury-0.0.45}/MANIFEST.in +0 -0
- {dao_treasury-0.0.44 → dao_treasury-0.0.45}/README.md +0 -0
- {dao_treasury-0.0.44 → dao_treasury-0.0.45}/dao_treasury/.grafana/provisioning/dashboards/dashboards.yaml +0 -0
- {dao_treasury-0.0.44 → dao_treasury-0.0.45}/dao_treasury/.grafana/provisioning/dashboards/streams/LlamaPay.json +0 -0
- {dao_treasury-0.0.44 → dao_treasury-0.0.45}/dao_treasury/.grafana/provisioning/dashboards/summary/Monthly.json +0 -0
- {dao_treasury-0.0.44 → dao_treasury-0.0.45}/dao_treasury/.grafana/provisioning/dashboards/transactions/Treasury Transactions.json +0 -0
- {dao_treasury-0.0.44 → dao_treasury-0.0.45}/dao_treasury/.grafana/provisioning/dashboards/treasury/Cashflow (Including Unsorted).json +0 -0
- {dao_treasury-0.0.44 → dao_treasury-0.0.45}/dao_treasury/.grafana/provisioning/dashboards/treasury/Cashflow.json +0 -0
- {dao_treasury-0.0.44 → dao_treasury-0.0.45}/dao_treasury/.grafana/provisioning/dashboards/treasury/Operating Cashflow.json +0 -0
- {dao_treasury-0.0.44 → dao_treasury-0.0.45}/dao_treasury/.grafana/provisioning/dashboards/treasury/Treasury.json +0 -0
- {dao_treasury-0.0.44 → dao_treasury-0.0.45}/dao_treasury/.grafana/provisioning/datasources/datasources.yaml +0 -0
- {dao_treasury-0.0.44 → dao_treasury-0.0.45}/dao_treasury/ENVIRONMENT_VARIABLES.py +0 -0
- {dao_treasury-0.0.44 → dao_treasury-0.0.45}/dao_treasury/__init__.py +0 -0
- {dao_treasury-0.0.44 → dao_treasury-0.0.45}/dao_treasury/_docker.py +0 -0
- {dao_treasury-0.0.44 → dao_treasury-0.0.45}/dao_treasury/_nicknames.py +0 -0
- {dao_treasury-0.0.44 → dao_treasury-0.0.45}/dao_treasury/_wallet.py +0 -0
- {dao_treasury-0.0.44 → dao_treasury-0.0.45}/dao_treasury/constants.py +0 -0
- {dao_treasury-0.0.44 → dao_treasury-0.0.45}/dao_treasury/main.py +0 -0
- {dao_treasury-0.0.44 → dao_treasury-0.0.45}/dao_treasury/py.typed +0 -0
- {dao_treasury-0.0.44 → dao_treasury-0.0.45}/dao_treasury/sorting/__init__.py +0 -0
- {dao_treasury-0.0.44 → dao_treasury-0.0.45}/dao_treasury/sorting/_matchers.py +0 -0
- {dao_treasury-0.0.44 → dao_treasury-0.0.45}/dao_treasury/sorting/_rules.py +0 -0
- {dao_treasury-0.0.44 → dao_treasury-0.0.45}/dao_treasury/sorting/factory.py +0 -0
- {dao_treasury-0.0.44 → dao_treasury-0.0.45}/dao_treasury/sorting/rule.py +0 -0
- {dao_treasury-0.0.44 → dao_treasury-0.0.45}/dao_treasury/sorting/rules/__init__.py +0 -0
- {dao_treasury-0.0.44 → dao_treasury-0.0.45}/dao_treasury/sorting/rules/ignore/__init__.py +0 -0
- {dao_treasury-0.0.44 → dao_treasury-0.0.45}/dao_treasury/sorting/rules/ignore/llamapay.py +0 -0
- {dao_treasury-0.0.44 → dao_treasury-0.0.45}/dao_treasury/streams/__init__.py +0 -0
- {dao_treasury-0.0.44 → dao_treasury-0.0.45}/dao_treasury/streams/llamapay.py +0 -0
- {dao_treasury-0.0.44 → dao_treasury-0.0.45}/dao_treasury/treasury.py +0 -0
- {dao_treasury-0.0.44 → dao_treasury-0.0.45}/dao_treasury/types.py +0 -0
- {dao_treasury-0.0.44 → dao_treasury-0.0.45}/dao_treasury.egg-info/dependency_links.txt +0 -0
- {dao_treasury-0.0.44 → dao_treasury-0.0.45}/dao_treasury.egg-info/not-zip-safe +0 -0
- {dao_treasury-0.0.44 → dao_treasury-0.0.45}/dao_treasury.egg-info/top_level.txt +0 -0
- {dao_treasury-0.0.44 → dao_treasury-0.0.45}/setup.cfg +0 -0
- {dao_treasury-0.0.44 → dao_treasury-0.0.45}/setup.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
|
-
Metadata-Version: 2.
|
1
|
+
Metadata-Version: 2.4
|
2
2
|
Name: dao_treasury
|
3
|
-
Version: 0.0.
|
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
|
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
|
-
|
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
|
|
@@ -1,6 +1,6 @@
|
|
1
|
-
Metadata-Version: 2.
|
2
|
-
Name:
|
3
|
-
Version: 0.0.
|
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.
|
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.
|
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
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|