iwa 0.0.1a2__py3-none-any.whl → 0.0.1a4__py3-none-any.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.
- iwa/core/chain/interface.py +51 -61
- iwa/core/chain/models.py +7 -7
- iwa/core/chain/rate_limiter.py +21 -10
- iwa/core/cli.py +27 -2
- iwa/core/constants.py +6 -5
- iwa/core/contracts/abis/erc20.json +930 -0
- iwa/core/contracts/abis/multisend.json +24 -0
- iwa/core/contracts/abis/multisend_call_only.json +17 -0
- iwa/core/contracts/contract.py +16 -4
- iwa/core/ipfs.py +149 -0
- iwa/core/keys.py +259 -29
- iwa/core/mnemonic.py +3 -13
- iwa/core/models.py +28 -6
- iwa/core/pricing.py +4 -4
- iwa/core/secrets.py +77 -0
- iwa/core/services/safe.py +3 -3
- iwa/core/utils.py +6 -1
- iwa/core/wallet.py +4 -0
- iwa/plugins/gnosis/safe.py +2 -2
- iwa/plugins/gnosis/tests/test_safe.py +1 -1
- iwa/plugins/olas/constants.py +8 -0
- iwa/plugins/olas/contracts/abis/activity_checker.json +110 -0
- iwa/plugins/olas/contracts/abis/mech.json +740 -0
- iwa/plugins/olas/contracts/abis/mech_marketplace.json +1293 -0
- iwa/plugins/olas/contracts/abis/mech_new.json +954 -0
- iwa/plugins/olas/contracts/abis/service_manager.json +1382 -0
- iwa/plugins/olas/contracts/abis/service_registry.json +1909 -0
- iwa/plugins/olas/contracts/abis/staking.json +1400 -0
- iwa/plugins/olas/contracts/abis/staking_token.json +1274 -0
- iwa/plugins/olas/contracts/mech.py +30 -2
- iwa/plugins/olas/plugin.py +2 -2
- iwa/plugins/olas/tests/test_plugin_full.py +3 -3
- iwa/plugins/olas/tests/test_staking_integration.py +2 -2
- iwa/tools/__init__.py +1 -0
- iwa/tools/check_profile.py +6 -5
- iwa/tools/list_contracts.py +136 -0
- iwa/tools/release.py +9 -3
- iwa/tools/reset_env.py +2 -2
- iwa/tools/reset_tenderly.py +26 -24
- iwa/tools/wallet_check.py +150 -0
- iwa/web/dependencies.py +4 -4
- iwa/web/routers/state.py +1 -0
- iwa/web/static/app.js +3096 -0
- iwa/web/static/index.html +543 -0
- iwa/web/static/style.css +1443 -0
- iwa/web/tests/test_web_endpoints.py +3 -2
- iwa/web/tests/test_web_swap_coverage.py +156 -0
- {iwa-0.0.1a2.dist-info → iwa-0.0.1a4.dist-info}/METADATA +6 -3
- {iwa-0.0.1a2.dist-info → iwa-0.0.1a4.dist-info}/RECORD +64 -44
- iwa-0.0.1a4.dist-info/entry_points.txt +6 -0
- {iwa-0.0.1a2.dist-info → iwa-0.0.1a4.dist-info}/top_level.txt +0 -1
- tests/test_chain.py +1 -1
- tests/test_chain_interface_coverage.py +92 -0
- tests/test_contract.py +2 -0
- tests/test_keys.py +58 -15
- tests/test_migration.py +52 -0
- tests/test_mnemonic.py +1 -1
- tests/test_pricing.py +7 -7
- tests/test_safe_coverage.py +1 -1
- tests/test_safe_service.py +3 -3
- tests/test_staking_router.py +13 -1
- tools/verify_drain.py +1 -1
- conftest.py +0 -22
- iwa/core/settings.py +0 -95
- iwa-0.0.1a2.dist-info/entry_points.txt +0 -2
- {iwa-0.0.1a2.dist-info → iwa-0.0.1a4.dist-info}/WHEEL +0 -0
- {iwa-0.0.1a2.dist-info → iwa-0.0.1a4.dist-info}/licenses/LICENSE +0 -0
tests/test_keys.py
CHANGED
|
@@ -11,8 +11,8 @@ from iwa.core.keys import EncryptedAccount, KeyStorage, StoredSafeAccount
|
|
|
11
11
|
|
|
12
12
|
@pytest.fixture
|
|
13
13
|
def mock_secrets():
|
|
14
|
-
"""Mock
|
|
15
|
-
with patch("iwa.core.keys.
|
|
14
|
+
"""Mock secrets to provide test password."""
|
|
15
|
+
with patch("iwa.core.keys.secrets") as mock:
|
|
16
16
|
mock.wallet_password.get_secret_value.return_value = "test_password"
|
|
17
17
|
mock.gnosis_rpc.get_secret_value.return_value = "http://rpc"
|
|
18
18
|
yield mock
|
|
@@ -51,7 +51,12 @@ def mock_account():
|
|
|
51
51
|
)
|
|
52
52
|
|
|
53
53
|
def create_side_effect():
|
|
54
|
+
# Skip the first address if it's reserved for the master account
|
|
55
|
+
# (to avoid overwriting master if create_account is called immediately)
|
|
54
56
|
addr = next(addresses)
|
|
57
|
+
if addr == "0x5B38Da6a701c568545dCfcB03FcB875f56beddC4":
|
|
58
|
+
addr = next(addresses)
|
|
59
|
+
|
|
55
60
|
m = MagicMock()
|
|
56
61
|
m.key.hex.return_value = f"0xPrivateKey{addr}"
|
|
57
62
|
m.address = addr
|
|
@@ -60,19 +65,49 @@ def mock_account():
|
|
|
60
65
|
mock.create.side_effect = create_side_effect
|
|
61
66
|
|
|
62
67
|
def from_key_side_effect(private_key):
|
|
63
|
-
|
|
68
|
+
# 1. Handle the master private key
|
|
69
|
+
if (
|
|
70
|
+
private_key == "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" # gitleaks:allow
|
|
71
|
+
):
|
|
72
|
+
addr = "0x5B38Da6a701c568545dCfcB03FcB875f56beddC4"
|
|
73
|
+
# 2. Handle our mock key format
|
|
74
|
+
elif isinstance(private_key, str) and private_key.startswith("0xPrivateKey"):
|
|
64
75
|
addr = private_key.replace("0xPrivateKey", "")
|
|
76
|
+
# 3. Default fallback
|
|
65
77
|
else:
|
|
66
|
-
addr = "
|
|
78
|
+
addr = "0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B"
|
|
79
|
+
|
|
67
80
|
m = MagicMock()
|
|
68
81
|
m.address = addr
|
|
69
|
-
m.key = private_key.encode() if isinstance(private_key, str) else private_key
|
|
70
82
|
return m
|
|
71
83
|
|
|
72
84
|
mock.from_key.side_effect = from_key_side_effect
|
|
73
85
|
yield mock
|
|
74
86
|
|
|
75
87
|
|
|
88
|
+
@pytest.fixture
|
|
89
|
+
def mock_bip_utils():
|
|
90
|
+
"""Mock BIP-utils for mnemonic derivation."""
|
|
91
|
+
with (
|
|
92
|
+
patch("iwa.core.keys.Bip39MnemonicGenerator") as mock_gen,
|
|
93
|
+
patch("iwa.core.keys.Bip39SeedGenerator") as mock_seed,
|
|
94
|
+
patch("iwa.core.keys.Bip44") as mock_bip44,
|
|
95
|
+
):
|
|
96
|
+
# Generator
|
|
97
|
+
mock_gen.return_value.FromWordsNumber.return_value.ToStr.return_value = (
|
|
98
|
+
"word " * 23 + "word"
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
# Derivation chain
|
|
102
|
+
mock_bip44.FromSeed.return_value.Purpose.return_value.Coin.return_value.Account.return_value.Change.return_value.AddressIndex.return_value.PrivateKey.return_value.Raw.return_value.ToHex.return_value = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
|
|
103
|
+
|
|
104
|
+
yield {
|
|
105
|
+
"gen": mock_gen,
|
|
106
|
+
"seed": mock_seed,
|
|
107
|
+
"bip44": mock_bip44,
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
|
|
76
111
|
# --- EncryptedAccount tests (no file I/O needed) ---
|
|
77
112
|
|
|
78
113
|
|
|
@@ -109,7 +144,9 @@ def test_encrypted_account_decrypt_private_key(mock_scrypt, mock_aesgcm, mock_se
|
|
|
109
144
|
# --- KeyStorage tests using tmp_path ---
|
|
110
145
|
|
|
111
146
|
|
|
112
|
-
def test_keystorage_init_new(
|
|
147
|
+
def test_keystorage_init_new(
|
|
148
|
+
tmp_path, mock_secrets, mock_account, mock_aesgcm, mock_scrypt, mock_bip_utils
|
|
149
|
+
):
|
|
113
150
|
"""Test initialization of new KeyStorage creates master account."""
|
|
114
151
|
wallet_path = tmp_path / "wallet.json"
|
|
115
152
|
storage = KeyStorage(wallet_path, password="test_password")
|
|
@@ -140,7 +177,9 @@ def test_keystorage_init_existing(tmp_path, mock_secrets):
|
|
|
140
177
|
assert storage.get_account("master") is not None
|
|
141
178
|
|
|
142
179
|
|
|
143
|
-
def test_keystorage_init_corrupted(
|
|
180
|
+
def test_keystorage_init_corrupted(
|
|
181
|
+
tmp_path, mock_secrets, mock_account, mock_aesgcm, mock_scrypt, mock_bip_utils
|
|
182
|
+
):
|
|
144
183
|
"""Test handling of corrupted wallet file."""
|
|
145
184
|
wallet_path = tmp_path / "wallet.json"
|
|
146
185
|
wallet_path.write_text("{invalid json")
|
|
@@ -153,7 +192,9 @@ def test_keystorage_init_corrupted(tmp_path, mock_secrets, mock_account, mock_ae
|
|
|
153
192
|
mock_logger.error.assert_called()
|
|
154
193
|
|
|
155
194
|
|
|
156
|
-
def test_keystorage_save(
|
|
195
|
+
def test_keystorage_save(
|
|
196
|
+
tmp_path, mock_secrets, mock_account, mock_aesgcm, mock_scrypt, mock_bip_utils
|
|
197
|
+
):
|
|
157
198
|
"""Test saving wallet to file."""
|
|
158
199
|
wallet_path = tmp_path / "wallet.json"
|
|
159
200
|
storage = KeyStorage(wallet_path, password="test_password")
|
|
@@ -165,7 +206,9 @@ def test_keystorage_save(tmp_path, mock_secrets, mock_account, mock_aesgcm, mock
|
|
|
165
206
|
assert "accounts" in data
|
|
166
207
|
|
|
167
208
|
|
|
168
|
-
def test_keystorage_create_account(
|
|
209
|
+
def test_keystorage_create_account(
|
|
210
|
+
tmp_path, mock_secrets, mock_account, mock_aesgcm, mock_scrypt, mock_bip_utils
|
|
211
|
+
):
|
|
169
212
|
"""Test creating additional accounts."""
|
|
170
213
|
wallet_path = tmp_path / "wallet.json"
|
|
171
214
|
storage = KeyStorage(wallet_path, password="test_password")
|
|
@@ -177,7 +220,7 @@ def test_keystorage_create_account(tmp_path, mock_secrets, mock_account, mock_ae
|
|
|
177
220
|
|
|
178
221
|
|
|
179
222
|
def test_keystorage_create_account_duplicate_tag(
|
|
180
|
-
tmp_path, mock_secrets, mock_account, mock_aesgcm, mock_scrypt
|
|
223
|
+
tmp_path, mock_secrets, mock_account, mock_aesgcm, mock_scrypt, mock_bip_utils
|
|
181
224
|
):
|
|
182
225
|
"""Test creating account with duplicate tag raises error."""
|
|
183
226
|
wallet_path = tmp_path / "wallet.json"
|
|
@@ -263,7 +306,7 @@ def test_keystorage_get_account(tmp_path, mock_secrets, mock_account, mock_aesgc
|
|
|
263
306
|
|
|
264
307
|
|
|
265
308
|
def test_keystorage_get_tag_by_address(
|
|
266
|
-
tmp_path, mock_secrets, mock_account, mock_aesgcm, mock_scrypt
|
|
309
|
+
tmp_path, mock_secrets, mock_account, mock_aesgcm, mock_scrypt, mock_bip_utils
|
|
267
310
|
):
|
|
268
311
|
"""Test getting tag by address."""
|
|
269
312
|
wallet_path = tmp_path / "wallet.json"
|
|
@@ -315,7 +358,7 @@ def test_keystorage_master_account_fallback(tmp_path, mock_secrets):
|
|
|
315
358
|
|
|
316
359
|
|
|
317
360
|
def test_keystorage_master_account_success(
|
|
318
|
-
tmp_path, mock_secrets, mock_account, mock_aesgcm, mock_scrypt
|
|
361
|
+
tmp_path, mock_secrets, mock_account, mock_aesgcm, mock_scrypt, mock_bip_utils
|
|
319
362
|
):
|
|
320
363
|
"""Test master_account property returns master."""
|
|
321
364
|
wallet_path = tmp_path / "wallet.json"
|
|
@@ -326,7 +369,7 @@ def test_keystorage_master_account_success(
|
|
|
326
369
|
|
|
327
370
|
|
|
328
371
|
def test_keystorage_create_account_default_tag(
|
|
329
|
-
tmp_path, mock_secrets, mock_account, mock_aesgcm, mock_scrypt
|
|
372
|
+
tmp_path, mock_secrets, mock_account, mock_aesgcm, mock_scrypt, mock_bip_utils
|
|
330
373
|
):
|
|
331
374
|
"""Test creating account with custom tag."""
|
|
332
375
|
wallet_path = tmp_path / "wallet.json"
|
|
@@ -338,7 +381,7 @@ def test_keystorage_create_account_default_tag(
|
|
|
338
381
|
|
|
339
382
|
|
|
340
383
|
def test_keystorage_remove_account_not_found(
|
|
341
|
-
tmp_path, mock_secrets, mock_account, mock_aesgcm, mock_scrypt
|
|
384
|
+
tmp_path, mock_secrets, mock_account, mock_aesgcm, mock_scrypt, mock_bip_utils
|
|
342
385
|
):
|
|
343
386
|
"""Test removing non-existent account doesn't raise."""
|
|
344
387
|
wallet_path = tmp_path / "wallet.json"
|
|
@@ -349,7 +392,7 @@ def test_keystorage_remove_account_not_found(
|
|
|
349
392
|
|
|
350
393
|
|
|
351
394
|
def test_keystorage_get_account_auto_load_safe(
|
|
352
|
-
tmp_path, mock_secrets, mock_account, mock_aesgcm, mock_scrypt
|
|
395
|
+
tmp_path, mock_secrets, mock_account, mock_aesgcm, mock_scrypt, mock_bip_utils
|
|
353
396
|
):
|
|
354
397
|
"""Test getting StoredSafeAccount."""
|
|
355
398
|
wallet_path = tmp_path / "wallet.json"
|
tests/test_migration.py
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
from iwa.core.keys import EncryptedAccount
|
|
2
|
+
from iwa.core.models import EncryptedData
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def test_legacy_account_migration():
|
|
6
|
+
"""Test that a legacy account dict is correctly upgraded."""
|
|
7
|
+
legacy_data = {
|
|
8
|
+
"address": "0xCD0184079fb3Bd15ddE5Bcbc248d81a893482280",
|
|
9
|
+
"tag": "master",
|
|
10
|
+
"salt": "FW19o5XPRxinb18TuQroxg==",
|
|
11
|
+
"nonce": "s1K+wNb24JY2BGeH",
|
|
12
|
+
"ciphertext": "i5BYYBjR7jiSW6OTNFuXJ03FaNQjlgbXMSo8uEgz71jtWQ24MAOZqJYckEjEqPFv4GX/Dwi5QVZFWV+tqECNh7AK0zY3/po4FDL7DU3s5ms=",
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
# Load into model
|
|
16
|
+
account = EncryptedAccount(**legacy_data)
|
|
17
|
+
|
|
18
|
+
# Verify legacy fields are mapped
|
|
19
|
+
assert account.kdf_salt == legacy_data["salt"]
|
|
20
|
+
assert account.nonce == legacy_data["nonce"]
|
|
21
|
+
assert account.ciphertext == legacy_data["ciphertext"]
|
|
22
|
+
|
|
23
|
+
# Verify default KDF params are set
|
|
24
|
+
assert account.kdf == "scrypt"
|
|
25
|
+
assert account.kdf_n == 16384
|
|
26
|
+
assert account.kdf_r == 8
|
|
27
|
+
assert account.kdf_p == 1
|
|
28
|
+
assert account.cipher == "aesgcm"
|
|
29
|
+
|
|
30
|
+
# Verify it is an instance of EncryptedData (implicitly checking inheritance)
|
|
31
|
+
assert isinstance(account, EncryptedData)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def test_new_account_format():
|
|
35
|
+
"""Test that a new account is created with explicit params."""
|
|
36
|
+
# We use a mocked password and private key
|
|
37
|
+
# This just tests structure, not actual crypto (covered by other tests)
|
|
38
|
+
account = EncryptedAccount.encrypt_private_key(
|
|
39
|
+
"0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
|
|
40
|
+
"password",
|
|
41
|
+
tag="new_test",
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
data = account.model_dump()
|
|
45
|
+
|
|
46
|
+
assert data["kdf"] == "scrypt"
|
|
47
|
+
assert "kdf_salt" in data
|
|
48
|
+
assert data["kdf_n"] == 16384
|
|
49
|
+
assert data["cipher"] == "aesgcm"
|
|
50
|
+
|
|
51
|
+
# Ensure legacy salt is NOT present (avoid duplication)
|
|
52
|
+
assert "salt" not in data
|
tests/test_mnemonic.py
CHANGED
|
@@ -83,7 +83,7 @@ def test_encrypted_mnemonic_decrypt_unsupported_kdf():
|
|
|
83
83
|
|
|
84
84
|
def test_encrypted_mnemonic_decrypt_unsupported_cipher():
|
|
85
85
|
em = EncryptedMnemonic(
|
|
86
|
-
|
|
86
|
+
cipher="unsupported", kdf_salt="salt", nonce="nonce", ciphertext="ciphertext"
|
|
87
87
|
)
|
|
88
88
|
with pytest.raises(ValueError, match="Unsupported cipher"):
|
|
89
89
|
em.decrypt("password")
|
tests/test_pricing.py
CHANGED
|
@@ -8,7 +8,7 @@ from iwa.core.pricing import PriceService
|
|
|
8
8
|
|
|
9
9
|
@pytest.fixture
|
|
10
10
|
def mock_secrets():
|
|
11
|
-
with patch("iwa.core.pricing.
|
|
11
|
+
with patch("iwa.core.pricing.secrets") as mock:
|
|
12
12
|
mock.coingecko_api_key.get_secret_value.return_value = "test_api_key"
|
|
13
13
|
yield mock
|
|
14
14
|
|
|
@@ -81,8 +81,8 @@ def test_get_token_price_key_not_found(price_service):
|
|
|
81
81
|
|
|
82
82
|
def test_get_token_price_rate_limit():
|
|
83
83
|
"""Test rate limit (429) handling with retries."""
|
|
84
|
-
with patch("iwa.core.pricing.
|
|
85
|
-
|
|
84
|
+
with patch("iwa.core.pricing.secrets") as mock_secrets:
|
|
85
|
+
mock_secrets.coingecko_api_key = None
|
|
86
86
|
|
|
87
87
|
service = PriceService()
|
|
88
88
|
|
|
@@ -102,8 +102,8 @@ def test_get_token_price_rate_limit_then_success():
|
|
|
102
102
|
"""Test rate limit recovery on retry."""
|
|
103
103
|
from unittest.mock import MagicMock
|
|
104
104
|
|
|
105
|
-
with patch("iwa.core.pricing.
|
|
106
|
-
|
|
105
|
+
with patch("iwa.core.pricing.secrets") as mock_secrets:
|
|
106
|
+
mock_secrets.coingecko_api_key = None
|
|
107
107
|
|
|
108
108
|
service = PriceService()
|
|
109
109
|
|
|
@@ -126,8 +126,8 @@ def test_get_token_price_rate_limit_then_success():
|
|
|
126
126
|
|
|
127
127
|
def test_get_token_price_no_api_key():
|
|
128
128
|
"""Test getting price without API key."""
|
|
129
|
-
with patch("iwa.core.pricing.
|
|
130
|
-
|
|
129
|
+
with patch("iwa.core.pricing.secrets") as mock_secrets:
|
|
130
|
+
mock_secrets.coingecko_api_key = None
|
|
131
131
|
|
|
132
132
|
service = PriceService()
|
|
133
133
|
|
tests/test_safe_coverage.py
CHANGED
|
@@ -119,7 +119,7 @@ def test_redeploy_safes(safe_service, mock_deps):
|
|
|
119
119
|
|
|
120
120
|
mock_deps["key_storage"].accounts = {"0xSafe1": account1}
|
|
121
121
|
|
|
122
|
-
with patch("iwa.core.services.safe.
|
|
122
|
+
with patch("iwa.core.services.safe.secrets") as mock_settings:
|
|
123
123
|
mock_settings.gnosis_rpc.get_secret_value.return_value = "http://rpc"
|
|
124
124
|
|
|
125
125
|
with patch("iwa.core.services.safe.EthereumClient") as mock_eth_client:
|
tests/test_safe_service.py
CHANGED
|
@@ -53,12 +53,12 @@ def mock_dependencies():
|
|
|
53
53
|
patch("iwa.core.services.safe.EthereumClient") as mock_client,
|
|
54
54
|
patch("iwa.core.services.safe.Safe") as mock_safe,
|
|
55
55
|
patch("iwa.core.services.safe.ProxyFactory") as mock_proxy_factory,
|
|
56
|
-
patch("iwa.core.services.safe.
|
|
56
|
+
patch("iwa.core.services.safe.secrets") as mock_secrets,
|
|
57
57
|
patch("iwa.core.services.safe.log_transaction") as mock_log,
|
|
58
58
|
patch("iwa.core.services.safe.get_safe_master_copy_address") as mock_master,
|
|
59
59
|
patch("iwa.core.services.safe.get_safe_proxy_factory_address") as mock_factory,
|
|
60
60
|
):
|
|
61
|
-
|
|
61
|
+
mock_secrets.gnosis_rpc.get_secret_value.return_value = "http://rpc"
|
|
62
62
|
|
|
63
63
|
# Setup Safe creation return
|
|
64
64
|
mock_create_tx = MagicMock()
|
|
@@ -104,7 +104,7 @@ def mock_dependencies():
|
|
|
104
104
|
"client": mock_client,
|
|
105
105
|
"safe": mock_safe,
|
|
106
106
|
"proxy_factory": mock_proxy_factory,
|
|
107
|
-
"
|
|
107
|
+
"secrets": mock_secrets,
|
|
108
108
|
"log": mock_log,
|
|
109
109
|
"master": mock_master,
|
|
110
110
|
"factory": mock_factory,
|
tests/test_staking_router.py
CHANGED
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
"""Tests for staking.py router coverage."""
|
|
2
2
|
|
|
3
|
-
from unittest.mock import MagicMock
|
|
3
|
+
from unittest.mock import MagicMock, patch
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@pytest.fixture(autouse=True)
|
|
9
|
+
def mock_key_storage():
|
|
10
|
+
"""Bypass KeyStorage security check."""
|
|
11
|
+
with (
|
|
12
|
+
patch("iwa.core.keys.KeyStorage.__init__", return_value=None),
|
|
13
|
+
patch("iwa.web.dependencies.wallet", MagicMock()),
|
|
14
|
+
):
|
|
15
|
+
yield
|
|
4
16
|
|
|
5
17
|
|
|
6
18
|
def test_check_availability_exception():
|
tools/verify_drain.py
CHANGED
|
@@ -71,7 +71,7 @@ def verify_drain(): # noqa: C901, D103
|
|
|
71
71
|
assert manager.olas_config is not None, "Olas config not initialized"
|
|
72
72
|
service = manager.olas_config.get_service("gnosis", service_id)
|
|
73
73
|
if not service:
|
|
74
|
-
|
|
74
|
+
raise ValueError(f"Service {service_id} not found in config")
|
|
75
75
|
safe_addr = service.multisig_address
|
|
76
76
|
agent_addr = service.agent_address
|
|
77
77
|
|
conftest.py
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
"""Pytest configuration."""
|
|
2
|
-
|
|
3
|
-
import logging
|
|
4
|
-
|
|
5
|
-
import pytest
|
|
6
|
-
from loguru import logger
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
@pytest.fixture(autouse=True)
|
|
10
|
-
def caplog(caplog):
|
|
11
|
-
"""Make loguru logs visible to pytest caplog."""
|
|
12
|
-
|
|
13
|
-
class PropagateHandler(logging.Handler):
|
|
14
|
-
def emit(self, record):
|
|
15
|
-
logging.getLogger(record.name).handle(record)
|
|
16
|
-
|
|
17
|
-
handler_id = logger.add(PropagateHandler(), format="{message}")
|
|
18
|
-
yield caplog
|
|
19
|
-
try:
|
|
20
|
-
logger.remove(handler_id)
|
|
21
|
-
except ValueError:
|
|
22
|
-
pass
|
iwa/core/settings.py
DELETED
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
"""Configuration settings module."""
|
|
2
|
-
|
|
3
|
-
import os
|
|
4
|
-
from typing import Optional
|
|
5
|
-
|
|
6
|
-
from dotenv import load_dotenv
|
|
7
|
-
from pydantic import ConfigDict, SecretStr, model_validator
|
|
8
|
-
from pydantic_settings import BaseSettings
|
|
9
|
-
|
|
10
|
-
from iwa.core.constants import SECRETS_PATH
|
|
11
|
-
from iwa.core.utils import singleton
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
@singleton
|
|
15
|
-
class Settings(BaseSettings):
|
|
16
|
-
"""Application Settings loaded from environment and secrets file."""
|
|
17
|
-
|
|
18
|
-
# Testing mode - when True, uses Tenderly test RPCs; when False, uses production RPCs
|
|
19
|
-
testing: bool = True
|
|
20
|
-
|
|
21
|
-
# RPC endpoints (loaded from gnosis_rpc/ethereum_rpc/base_rpc in secrets.env)
|
|
22
|
-
# When testing=True, these get overwritten with *_test_rpc values
|
|
23
|
-
gnosis_rpc: Optional[SecretStr] = None
|
|
24
|
-
base_rpc: Optional[SecretStr] = None
|
|
25
|
-
ethereum_rpc: Optional[SecretStr] = None
|
|
26
|
-
|
|
27
|
-
# Test RPCs
|
|
28
|
-
gnosis_test_rpc: Optional[SecretStr] = None
|
|
29
|
-
ethereum_test_rpc: Optional[SecretStr] = None
|
|
30
|
-
base_test_rpc: Optional[SecretStr] = None
|
|
31
|
-
|
|
32
|
-
gnosisscan_api_key: Optional[SecretStr] = None
|
|
33
|
-
coingecko_api_key: Optional[SecretStr] = None
|
|
34
|
-
wallet_password: Optional[SecretStr] = None
|
|
35
|
-
security_word: Optional[SecretStr] = None
|
|
36
|
-
|
|
37
|
-
# Tenderly profile (1 or 2) - determines which credentials to load
|
|
38
|
-
tenderly_profile: int = 1
|
|
39
|
-
|
|
40
|
-
# Tenderly credentials - loaded dynamically based on profile
|
|
41
|
-
tenderly_account_slug: Optional[SecretStr] = None
|
|
42
|
-
tenderly_project_slug: Optional[SecretStr] = None
|
|
43
|
-
tenderly_access_key: Optional[SecretStr] = None
|
|
44
|
-
|
|
45
|
-
# Tenderly funding configuration
|
|
46
|
-
tenderly_native_funds: float = 1000.0
|
|
47
|
-
tenderly_olas_funds: float = 100000.0
|
|
48
|
-
|
|
49
|
-
web_enabled: bool = False
|
|
50
|
-
web_port: int = 8080
|
|
51
|
-
webui_password: Optional[SecretStr] = None
|
|
52
|
-
|
|
53
|
-
model_config = ConfigDict(env_file=str(SECRETS_PATH), env_file_encoding="utf-8", extra="ignore")
|
|
54
|
-
|
|
55
|
-
def __init__(self, **values):
|
|
56
|
-
"""Initialize Settings and load environment variables."""
|
|
57
|
-
# Force load dotenv to ensure os.environ variables are set
|
|
58
|
-
load_dotenv(SECRETS_PATH, override=True)
|
|
59
|
-
super().__init__(**values)
|
|
60
|
-
|
|
61
|
-
@model_validator(mode="after")
|
|
62
|
-
def load_tenderly_profile_credentials(self) -> "Settings":
|
|
63
|
-
"""Load Tenderly credentials based on the selected profile."""
|
|
64
|
-
profile = self.tenderly_profile
|
|
65
|
-
|
|
66
|
-
# Load profile-specific credentials from environment
|
|
67
|
-
account = os.getenv(f"tenderly_account_slug_{profile}")
|
|
68
|
-
project = os.getenv(f"tenderly_project_slug_{profile}")
|
|
69
|
-
access_key = os.getenv(f"tenderly_access_key_{profile}")
|
|
70
|
-
|
|
71
|
-
if account:
|
|
72
|
-
self.tenderly_account_slug = SecretStr(account)
|
|
73
|
-
if project:
|
|
74
|
-
self.tenderly_project_slug = SecretStr(project)
|
|
75
|
-
if access_key:
|
|
76
|
-
self.tenderly_access_key = SecretStr(access_key)
|
|
77
|
-
|
|
78
|
-
# When in testing mode, override RPCs with test RPCs (Tenderly)
|
|
79
|
-
if self.testing:
|
|
80
|
-
if self.gnosis_test_rpc:
|
|
81
|
-
self.gnosis_rpc = self.gnosis_test_rpc
|
|
82
|
-
if self.ethereum_test_rpc:
|
|
83
|
-
self.ethereum_rpc = self.ethereum_test_rpc
|
|
84
|
-
if self.base_test_rpc:
|
|
85
|
-
self.base_rpc = self.base_test_rpc
|
|
86
|
-
|
|
87
|
-
# Convert empty webui_password to None (no auth required)
|
|
88
|
-
if self.webui_password and not self.webui_password.get_secret_value():
|
|
89
|
-
self.webui_password = None
|
|
90
|
-
|
|
91
|
-
return self
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
# Global settings instance
|
|
95
|
-
settings = Settings()
|
|
File without changes
|
|
File without changes
|