iwa 0.0.58__py3-none-any.whl → 0.0.59__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 +32 -21
- iwa/core/chain/rate_limiter.py +0 -6
- iwa/core/chainlist.py +15 -10
- iwa/core/cli.py +3 -0
- iwa/core/contracts/cache.py +1 -1
- iwa/core/contracts/contract.py +1 -0
- iwa/core/contracts/decoder.py +10 -4
- iwa/core/http.py +31 -0
- iwa/core/ipfs.py +11 -19
- iwa/core/keys.py +10 -4
- iwa/core/models.py +1 -3
- iwa/core/pricing.py +3 -21
- iwa/core/rpc_monitor.py +1 -0
- iwa/core/services/balance.py +0 -1
- iwa/core/services/safe.py +8 -2
- iwa/core/services/safe_executor.py +52 -18
- iwa/core/services/transaction.py +32 -12
- iwa/core/services/transfer/erc20.py +0 -1
- iwa/core/services/transfer/native.py +1 -1
- iwa/core/tests/test_gnosis_fee.py +6 -2
- iwa/core/tests/test_ipfs.py +1 -1
- iwa/core/tests/test_regression_fixes.py +3 -6
- iwa/core/utils.py +2 -0
- iwa/core/wallet.py +3 -1
- iwa/plugins/olas/constants.py +15 -5
- iwa/plugins/olas/contracts/activity_checker.py +3 -3
- iwa/plugins/olas/contracts/staking.py +0 -1
- iwa/plugins/olas/events.py +15 -13
- iwa/plugins/olas/importer.py +26 -20
- iwa/plugins/olas/plugin.py +16 -14
- iwa/plugins/olas/service_manager/drain.py +1 -3
- iwa/plugins/olas/service_manager/lifecycle.py +9 -9
- iwa/plugins/olas/service_manager/staking.py +11 -6
- iwa/plugins/olas/tests/test_olas_archiving.py +25 -15
- iwa/plugins/olas/tests/test_olas_integration.py +49 -29
- iwa/plugins/olas/tests/test_service_manager.py +8 -10
- iwa/plugins/olas/tests/test_service_manager_errors.py +5 -4
- iwa/plugins/olas/tests/test_service_manager_flows.py +6 -5
- iwa/plugins/olas/tests/test_service_staking.py +64 -38
- iwa/tools/drain_accounts.py +2 -1
- iwa/tools/reset_env.py +2 -1
- iwa/tools/test_chainlist.py +5 -1
- iwa/tui/screens/wallets.py +1 -3
- iwa/web/routers/olas/services.py +10 -5
- {iwa-0.0.58.dist-info → iwa-0.0.59.dist-info}/METADATA +1 -1
- {iwa-0.0.58.dist-info → iwa-0.0.59.dist-info}/RECORD +58 -57
- tests/test_balance_service.py +0 -2
- tests/test_chain.py +1 -2
- tests/test_rate_limiter_retry.py +2 -7
- tests/test_rpc_efficiency.py +4 -1
- tests/test_rpc_rate_limit.py +1 -0
- tests/test_rpc_rotation.py +4 -4
- tests/test_safe_executor.py +76 -50
- tests/test_safe_integration.py +11 -6
- {iwa-0.0.58.dist-info → iwa-0.0.59.dist-info}/WHEEL +0 -0
- {iwa-0.0.58.dist-info → iwa-0.0.59.dist-info}/entry_points.txt +0 -0
- {iwa-0.0.58.dist-info → iwa-0.0.59.dist-info}/licenses/LICENSE +0 -0
- {iwa-0.0.58.dist-info → iwa-0.0.59.dist-info}/top_level.txt +0 -0
tests/test_safe_executor.py
CHANGED
|
@@ -64,10 +64,13 @@ def mock_safe():
|
|
|
64
64
|
# Test: Basic execution success
|
|
65
65
|
# =============================================================================
|
|
66
66
|
|
|
67
|
+
|
|
67
68
|
def test_execute_success_first_try(executor, mock_chain_interface, mock_safe_tx, mock_safe):
|
|
68
69
|
"""Test successful execution on first attempt."""
|
|
69
|
-
with patch.object(executor,
|
|
70
|
-
mock_chain_interface.web3.eth.wait_for_transaction_receipt.return_value = MagicMock(
|
|
70
|
+
with patch.object(executor, "_recreate_safe_client", return_value=mock_safe):
|
|
71
|
+
mock_chain_interface.web3.eth.wait_for_transaction_receipt.return_value = MagicMock(
|
|
72
|
+
status=1
|
|
73
|
+
)
|
|
71
74
|
mock_safe_tx.execute.return_value = b"tx_hash"
|
|
72
75
|
|
|
73
76
|
success, tx_hash, receipt = executor.execute_with_retry("0xSafe", mock_safe_tx, ["key1"])
|
|
@@ -81,10 +84,13 @@ def test_execute_success_first_try(executor, mock_chain_interface, mock_safe_tx,
|
|
|
81
84
|
# Test: Tuple vs bytes return handling
|
|
82
85
|
# =============================================================================
|
|
83
86
|
|
|
87
|
+
|
|
84
88
|
def test_execute_handles_tuple_return(executor, mock_chain_interface, mock_safe_tx, mock_safe):
|
|
85
89
|
"""Test that executor handles safe_tx.execute() returning tuple (tx_hash, tx)."""
|
|
86
|
-
with patch.object(executor,
|
|
87
|
-
mock_chain_interface.web3.eth.wait_for_transaction_receipt.return_value = MagicMock(
|
|
90
|
+
with patch.object(executor, "_recreate_safe_client", return_value=mock_safe):
|
|
91
|
+
mock_chain_interface.web3.eth.wait_for_transaction_receipt.return_value = MagicMock(
|
|
92
|
+
status=1
|
|
93
|
+
)
|
|
88
94
|
# Simulate tuple return: (tx_hash_bytes, tx_data)
|
|
89
95
|
mock_safe_tx.execute.return_value = (b"tx_hash", {"gas": 21000})
|
|
90
96
|
|
|
@@ -96,8 +102,10 @@ def test_execute_handles_tuple_return(executor, mock_chain_interface, mock_safe_
|
|
|
96
102
|
|
|
97
103
|
def test_execute_handles_bytes_return(executor, mock_chain_interface, mock_safe_tx, mock_safe):
|
|
98
104
|
"""Test that executor handles safe_tx.execute() returning raw bytes."""
|
|
99
|
-
with patch.object(executor,
|
|
100
|
-
mock_chain_interface.web3.eth.wait_for_transaction_receipt.return_value = MagicMock(
|
|
105
|
+
with patch.object(executor, "_recreate_safe_client", return_value=mock_safe):
|
|
106
|
+
mock_chain_interface.web3.eth.wait_for_transaction_receipt.return_value = MagicMock(
|
|
107
|
+
status=1
|
|
108
|
+
)
|
|
101
109
|
mock_safe_tx.execute.return_value = b"raw_hash"
|
|
102
110
|
|
|
103
111
|
success, tx_hash, receipt = executor.execute_with_retry("0xSafe", mock_safe_tx, ["key1"])
|
|
@@ -108,8 +116,10 @@ def test_execute_handles_bytes_return(executor, mock_chain_interface, mock_safe_
|
|
|
108
116
|
|
|
109
117
|
def test_execute_handles_string_return(executor, mock_chain_interface, mock_safe_tx, mock_safe):
|
|
110
118
|
"""Test that executor handles safe_tx.execute() returning hex string."""
|
|
111
|
-
with patch.object(executor,
|
|
112
|
-
mock_chain_interface.web3.eth.wait_for_transaction_receipt.return_value = MagicMock(
|
|
119
|
+
with patch.object(executor, "_recreate_safe_client", return_value=mock_safe):
|
|
120
|
+
mock_chain_interface.web3.eth.wait_for_transaction_receipt.return_value = MagicMock(
|
|
121
|
+
status=1
|
|
122
|
+
)
|
|
113
123
|
mock_safe_tx.execute.return_value = "0xabcdef1234567890"
|
|
114
124
|
|
|
115
125
|
success, tx_hash, receipt = executor.execute_with_retry("0xSafe", mock_safe_tx, ["key1"])
|
|
@@ -122,11 +132,12 @@ def test_execute_handles_string_return(executor, mock_chain_interface, mock_safe
|
|
|
122
132
|
# Test: Signature validation
|
|
123
133
|
# =============================================================================
|
|
124
134
|
|
|
135
|
+
|
|
125
136
|
def test_execute_fails_on_empty_signatures(executor, mock_chain_interface, mock_safe_tx, mock_safe):
|
|
126
137
|
"""Verify we fail immediately if no signatures exist."""
|
|
127
138
|
mock_safe_tx.signatures = b"" # Empty signatures
|
|
128
139
|
|
|
129
|
-
with patch.object(executor,
|
|
140
|
+
with patch.object(executor, "_recreate_safe_client", return_value=mock_safe):
|
|
130
141
|
with patch("time.sleep"):
|
|
131
142
|
success, error, _ = executor.execute_with_retry("0xSafe", mock_safe_tx, ["key1"])
|
|
132
143
|
|
|
@@ -135,11 +146,13 @@ def test_execute_fails_on_empty_signatures(executor, mock_chain_interface, mock_
|
|
|
135
146
|
assert mock_safe_tx.execute.call_count == 0 # Never tried to execute
|
|
136
147
|
|
|
137
148
|
|
|
138
|
-
def test_execute_fails_on_truncated_signatures(
|
|
149
|
+
def test_execute_fails_on_truncated_signatures(
|
|
150
|
+
executor, mock_chain_interface, mock_safe_tx, mock_safe
|
|
151
|
+
):
|
|
139
152
|
"""Verify we detect signatures shorter than 65 bytes."""
|
|
140
153
|
mock_safe_tx.signatures = b"x" * 30 # Too short (need 65)
|
|
141
154
|
|
|
142
|
-
with patch.object(executor,
|
|
155
|
+
with patch.object(executor, "_recreate_safe_client", return_value=mock_safe):
|
|
143
156
|
with patch("time.sleep"):
|
|
144
157
|
success, error, _ = executor.execute_with_retry("0xSafe", mock_safe_tx, ["key1"])
|
|
145
158
|
|
|
@@ -151,7 +164,7 @@ def test_execute_fails_on_none_signatures(executor, mock_chain_interface, mock_s
|
|
|
151
164
|
"""Verify we handle None signatures gracefully."""
|
|
152
165
|
mock_safe_tx.signatures = None
|
|
153
166
|
|
|
154
|
-
with patch.object(executor,
|
|
167
|
+
with patch.object(executor, "_recreate_safe_client", return_value=mock_safe):
|
|
155
168
|
with patch("time.sleep"):
|
|
156
169
|
success, error, _ = executor.execute_with_retry("0xSafe", mock_safe_tx, ["key1"])
|
|
157
170
|
|
|
@@ -163,9 +176,10 @@ def test_execute_fails_on_none_signatures(executor, mock_chain_interface, mock_s
|
|
|
163
176
|
# Test: Error classification (GS0xx codes)
|
|
164
177
|
# =============================================================================
|
|
165
178
|
|
|
179
|
+
|
|
166
180
|
def test_gs020_fails_fast(executor, mock_chain_interface, mock_safe_tx, mock_safe):
|
|
167
181
|
"""GS020 (signatures too short) should not trigger retries."""
|
|
168
|
-
with patch.object(executor,
|
|
182
|
+
with patch.object(executor, "_recreate_safe_client", return_value=mock_safe):
|
|
169
183
|
mock_safe_tx.call.side_effect = ValueError("execution reverted: GS020")
|
|
170
184
|
|
|
171
185
|
with patch("time.sleep"):
|
|
@@ -178,7 +192,7 @@ def test_gs020_fails_fast(executor, mock_chain_interface, mock_safe_tx, mock_saf
|
|
|
178
192
|
|
|
179
193
|
def test_gs026_fails_fast(executor, mock_chain_interface, mock_safe_tx, mock_safe):
|
|
180
194
|
"""GS026 (invalid owner) should not trigger retries."""
|
|
181
|
-
with patch.object(executor,
|
|
195
|
+
with patch.object(executor, "_recreate_safe_client", return_value=mock_safe):
|
|
182
196
|
mock_safe_tx.call.side_effect = ValueError("execution reverted: GS026")
|
|
183
197
|
|
|
184
198
|
with patch("time.sleep"):
|
|
@@ -188,15 +202,18 @@ def test_gs026_fails_fast(executor, mock_chain_interface, mock_safe_tx, mock_saf
|
|
|
188
202
|
assert "GS026" in error
|
|
189
203
|
|
|
190
204
|
|
|
191
|
-
@pytest.mark.parametrize(
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
205
|
+
@pytest.mark.parametrize(
|
|
206
|
+
"error_code,is_signature_error",
|
|
207
|
+
[
|
|
208
|
+
("GS020", True), # Signatures data too short
|
|
209
|
+
("GS021", True), # Invalid signature data pointer
|
|
210
|
+
("GS024", True), # Invalid contract signature
|
|
211
|
+
("GS026", True), # Invalid owner
|
|
212
|
+
("GS025", False), # Invalid nonce (not a signature error)
|
|
213
|
+
("GS010", False), # Not enough gas
|
|
214
|
+
("GS013", False), # Safe transaction failed
|
|
215
|
+
],
|
|
216
|
+
)
|
|
200
217
|
def test_error_classification(executor, error_code, is_signature_error):
|
|
201
218
|
"""Verify correct classification of Safe error codes."""
|
|
202
219
|
error = ValueError(f"execution reverted: {error_code}")
|
|
@@ -208,18 +225,20 @@ def test_error_classification(executor, error_code, is_signature_error):
|
|
|
208
225
|
# Test: Retry behavior
|
|
209
226
|
# =============================================================================
|
|
210
227
|
|
|
228
|
+
|
|
211
229
|
def test_retry_on_transient_error(executor, mock_chain_interface, mock_safe_tx, mock_safe):
|
|
212
230
|
"""Test that transient errors trigger retries without modifying tx."""
|
|
213
|
-
with patch.object(executor,
|
|
214
|
-
mock_safe_tx.execute.side_effect = [
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
mock_chain_interface.web3.eth.wait_for_transaction_receipt.return_value = MagicMock(status=1)
|
|
231
|
+
with patch.object(executor, "_recreate_safe_client", return_value=mock_safe):
|
|
232
|
+
mock_safe_tx.execute.side_effect = [ConnectionError("Network timeout"), b"success_hash"]
|
|
233
|
+
mock_chain_interface.web3.eth.wait_for_transaction_receipt.return_value = MagicMock(
|
|
234
|
+
status=1
|
|
235
|
+
)
|
|
219
236
|
mock_chain_interface._is_connection_error.return_value = True
|
|
220
237
|
|
|
221
238
|
with patch("time.sleep"):
|
|
222
|
-
success, tx_hash, receipt = executor.execute_with_retry(
|
|
239
|
+
success, tx_hash, receipt = executor.execute_with_retry(
|
|
240
|
+
"0xSafe", mock_safe_tx, ["key1"]
|
|
241
|
+
)
|
|
223
242
|
|
|
224
243
|
assert success is True
|
|
225
244
|
assert mock_safe_tx.execute.call_count == 2
|
|
@@ -229,12 +248,11 @@ def test_retry_on_transient_error(executor, mock_chain_interface, mock_safe_tx,
|
|
|
229
248
|
|
|
230
249
|
def test_retry_on_nonce_error(executor, mock_chain_interface, mock_safe_tx, mock_safe):
|
|
231
250
|
"""Test nonce refresh on GS025 error."""
|
|
232
|
-
with patch.object(executor,
|
|
233
|
-
mock_safe_tx.execute.side_effect = [
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
mock_chain_interface.web3.eth.wait_for_transaction_receipt.return_value = MagicMock(status=1)
|
|
251
|
+
with patch.object(executor, "_recreate_safe_client", return_value=mock_safe):
|
|
252
|
+
mock_safe_tx.execute.side_effect = [ValueError("GS025: invalid nonce"), b"success_hash"]
|
|
253
|
+
mock_chain_interface.web3.eth.wait_for_transaction_receipt.return_value = MagicMock(
|
|
254
|
+
status=1
|
|
255
|
+
)
|
|
238
256
|
|
|
239
257
|
new_tx = MagicMock(spec=SafeTx)
|
|
240
258
|
new_tx.signatures = b"x" * 65
|
|
@@ -249,13 +267,12 @@ def test_retry_on_nonce_error(executor, mock_chain_interface, mock_safe_tx, mock
|
|
|
249
267
|
|
|
250
268
|
def test_retry_on_rpc_error(executor, mock_chain_interface, mock_safe_tx, mock_safe):
|
|
251
269
|
"""Test RPC rotation on rate limit error."""
|
|
252
|
-
with patch.object(executor,
|
|
253
|
-
mock_safe_tx.execute.side_effect = [
|
|
254
|
-
ValueError("Rate limit exceeded"),
|
|
255
|
-
b"success_hash"
|
|
256
|
-
]
|
|
270
|
+
with patch.object(executor, "_recreate_safe_client", return_value=mock_safe):
|
|
271
|
+
mock_safe_tx.execute.side_effect = [ValueError("Rate limit exceeded"), b"success_hash"]
|
|
257
272
|
mock_chain_interface._is_rate_limit_error.return_value = True
|
|
258
|
-
mock_chain_interface.web3.eth.wait_for_transaction_receipt.return_value = MagicMock(
|
|
273
|
+
mock_chain_interface.web3.eth.wait_for_transaction_receipt.return_value = MagicMock(
|
|
274
|
+
status=1
|
|
275
|
+
)
|
|
259
276
|
|
|
260
277
|
with patch("time.sleep"):
|
|
261
278
|
executor.execute_with_retry("0xSafe", mock_safe_tx, ["key1"])
|
|
@@ -267,11 +284,13 @@ def test_retry_on_rpc_error(executor, mock_chain_interface, mock_safe_tx, mock_s
|
|
|
267
284
|
def test_fail_after_max_retries(executor, mock_chain_interface, mock_safe_tx, mock_safe):
|
|
268
285
|
"""Test failure after exhausting all retries."""
|
|
269
286
|
executor.max_retries = 2
|
|
270
|
-
with patch.object(executor,
|
|
287
|
+
with patch.object(executor, "_recreate_safe_client", return_value=mock_safe):
|
|
271
288
|
mock_safe_tx.execute.side_effect = ValueError("Persistent error")
|
|
272
289
|
|
|
273
290
|
with patch("time.sleep"):
|
|
274
|
-
success, tx_hash_or_err, receipt = executor.execute_with_retry(
|
|
291
|
+
success, tx_hash_or_err, receipt = executor.execute_with_retry(
|
|
292
|
+
"0xSafe", mock_safe_tx, ["key1"]
|
|
293
|
+
)
|
|
275
294
|
|
|
276
295
|
assert success is False
|
|
277
296
|
assert mock_safe_tx.execute.call_count == 3 # 1 initial + 2 retries
|
|
@@ -281,7 +300,10 @@ def test_fail_after_max_retries(executor, mock_chain_interface, mock_safe_tx, mo
|
|
|
281
300
|
# Test: State preservation during retries
|
|
282
301
|
# =============================================================================
|
|
283
302
|
|
|
284
|
-
|
|
303
|
+
|
|
304
|
+
def test_retry_preserves_signatures_despite_clearing(
|
|
305
|
+
executor, mock_chain_interface, mock_safe_tx, mock_safe
|
|
306
|
+
):
|
|
285
307
|
"""Verify that retries don't corrupt/lose signatures even if library clears them."""
|
|
286
308
|
original_signatures = mock_safe_tx.signatures
|
|
287
309
|
|
|
@@ -291,21 +313,23 @@ def test_retry_preserves_signatures_despite_clearing(executor, mock_chain_interf
|
|
|
291
313
|
mock_safe_tx.signatures = b""
|
|
292
314
|
return b"hash"
|
|
293
315
|
|
|
294
|
-
with patch.object(executor,
|
|
316
|
+
with patch.object(executor, "_recreate_safe_client", return_value=mock_safe):
|
|
295
317
|
# Scenario:
|
|
296
318
|
# 1. Execute success (sigs cleared) but Receipt not found (triggering retry)
|
|
297
319
|
# 2. Retry: Execute called again (must have restored sigs) -> Success -> Receipt found
|
|
298
320
|
|
|
299
321
|
mock_chain_interface.web3.eth.wait_for_transaction_receipt.side_effect = [
|
|
300
322
|
ValueError("Transaction not found"),
|
|
301
|
-
MagicMock(status=1)
|
|
323
|
+
MagicMock(status=1),
|
|
302
324
|
]
|
|
303
325
|
|
|
304
326
|
mock_safe_tx.execute.side_effect = execute_side_effect
|
|
305
327
|
mock_chain_interface._is_connection_error.return_value = False
|
|
306
328
|
|
|
307
329
|
with patch("time.sleep"):
|
|
308
|
-
success, tx_hash, receipt = executor.execute_with_retry(
|
|
330
|
+
success, tx_hash, receipt = executor.execute_with_retry(
|
|
331
|
+
"0xSafe", mock_safe_tx, ["key1"]
|
|
332
|
+
)
|
|
309
333
|
|
|
310
334
|
assert success is True
|
|
311
335
|
# Signatures should be restored after the loop (or at least valid during 2nd call)
|
|
@@ -324,9 +348,11 @@ def test_retry_preserves_gas(executor, mock_chain_interface, mock_safe_tx, mock_
|
|
|
324
348
|
"""Verify that retries don't modify safe_tx_gas (which would invalidate signatures)."""
|
|
325
349
|
original_gas = mock_safe_tx.safe_tx_gas
|
|
326
350
|
|
|
327
|
-
with patch.object(executor,
|
|
351
|
+
with patch.object(executor, "_recreate_safe_client", return_value=mock_safe):
|
|
328
352
|
mock_safe_tx.execute.side_effect = [ConnectionError("timeout"), b"hash"]
|
|
329
|
-
mock_chain_interface.web3.eth.wait_for_transaction_receipt.return_value = MagicMock(
|
|
353
|
+
mock_chain_interface.web3.eth.wait_for_transaction_receipt.return_value = MagicMock(
|
|
354
|
+
status=1
|
|
355
|
+
)
|
|
330
356
|
mock_chain_interface._is_connection_error.return_value = True
|
|
331
357
|
|
|
332
358
|
with patch("time.sleep"):
|
tests/test_safe_integration.py
CHANGED
|
@@ -47,7 +47,12 @@ def real_safe_tx_mock_eth(mock_chain_interface):
|
|
|
47
47
|
mock_contract = MagicMock()
|
|
48
48
|
# Mock execTransaction function build_transaction
|
|
49
49
|
mock_contract.functions.execTransaction.return_value.build_transaction.return_value = {
|
|
50
|
-
"to": "0xSafe",
|
|
50
|
+
"to": "0xSafe",
|
|
51
|
+
"data": b"",
|
|
52
|
+
"value": 0,
|
|
53
|
+
"gas": 500000,
|
|
54
|
+
"nonce": 5,
|
|
55
|
+
"from": "0xExecutor",
|
|
51
56
|
}
|
|
52
57
|
# Mock nonce call
|
|
53
58
|
mock_contract.functions.nonce().call.return_value = 5
|
|
@@ -64,14 +69,14 @@ def real_safe_tx_mock_eth(mock_chain_interface):
|
|
|
64
69
|
0,
|
|
65
70
|
b"",
|
|
66
71
|
0,
|
|
67
|
-
200000,
|
|
72
|
+
200000, # safe_tx_gas
|
|
68
73
|
0,
|
|
69
74
|
0,
|
|
70
75
|
None,
|
|
71
76
|
None,
|
|
72
77
|
signatures=MOCK_SIGNATURE,
|
|
73
78
|
safe_nonce=5,
|
|
74
|
-
chain_id=1
|
|
79
|
+
chain_id=1,
|
|
75
80
|
)
|
|
76
81
|
|
|
77
82
|
# HACK: Force initialize properties that rely on cached_property + network
|
|
@@ -96,7 +101,7 @@ def test_integration_full_execution_flow(mock_chain_interface, real_safe_tx_mock
|
|
|
96
101
|
# Use a dummy key (needs to be valid hex for account generation if SafeTx uses it)
|
|
97
102
|
dummy_key = "0x" + "1" * 64
|
|
98
103
|
|
|
99
|
-
with patch.object(executor,
|
|
104
|
+
with patch.object(executor, "_recreate_safe_client", return_value=MagicMock()):
|
|
100
105
|
# Pre-execution check
|
|
101
106
|
assert len(safe_tx.signatures) == 65
|
|
102
107
|
|
|
@@ -129,12 +134,12 @@ def test_integration_retry_preserves_signatures(mock_chain_interface, real_safe_
|
|
|
129
134
|
# Second attempt: Success (status 1)
|
|
130
135
|
mock_chain_interface.web3.eth.wait_for_transaction_receipt.side_effect = [
|
|
131
136
|
ValueError("Transaction not found"),
|
|
132
|
-
MagicMock(status=1)
|
|
137
|
+
MagicMock(status=1),
|
|
133
138
|
]
|
|
134
139
|
|
|
135
140
|
dummy_key = "0x" + "1" * 64
|
|
136
141
|
|
|
137
|
-
with patch.object(executor,
|
|
142
|
+
with patch.object(executor, "_recreate_safe_client", return_value=MagicMock()):
|
|
138
143
|
with patch("time.sleep"): # Skip sleep
|
|
139
144
|
success, tx_hash, receipt = executor.execute_with_retry("0xSafe", safe_tx, [dummy_key])
|
|
140
145
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|