iwa 0.1.0__py3-none-any.whl → 0.1.1__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/plugins/olas/service_manager/staking.py +20 -19
- iwa/plugins/olas/tests/test_service_staking.py +54 -0
- {iwa-0.1.0.dist-info → iwa-0.1.1.dist-info}/METADATA +1 -1
- {iwa-0.1.0.dist-info → iwa-0.1.1.dist-info}/RECORD +8 -8
- {iwa-0.1.0.dist-info → iwa-0.1.1.dist-info}/WHEEL +0 -0
- {iwa-0.1.0.dist-info → iwa-0.1.1.dist-info}/entry_points.txt +0 -0
- {iwa-0.1.0.dist-info → iwa-0.1.1.dist-info}/licenses/LICENSE +0 -0
- {iwa-0.1.0.dist-info → iwa-0.1.1.dist-info}/top_level.txt +0 -0
|
@@ -547,12 +547,12 @@ class StakingManagerMixin:
|
|
|
547
547
|
f"Preparing to unstake service {self.service.service_id} from {self._get_label(staking_contract.address)}"
|
|
548
548
|
)
|
|
549
549
|
|
|
550
|
-
# Check that the service is staked
|
|
550
|
+
# Check that the service is staked or evicted (both can be unstaked)
|
|
551
551
|
try:
|
|
552
552
|
staking_state = staking_contract.get_staking_state(self.service.service_id)
|
|
553
553
|
logger.info(f"Current staking state: {staking_state}")
|
|
554
554
|
|
|
555
|
-
if staking_state
|
|
555
|
+
if staking_state not in (StakingState.STAKED, StakingState.EVICTED):
|
|
556
556
|
logger.error(
|
|
557
557
|
f"Service {self.service.service_id} is not staked (state={staking_state}), cannot unstake"
|
|
558
558
|
)
|
|
@@ -561,23 +561,24 @@ class StakingManagerMixin:
|
|
|
561
561
|
logger.error(f"Failed to get staking state: {e}")
|
|
562
562
|
return False
|
|
563
563
|
|
|
564
|
-
# Check that enough time has passed since staking
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
564
|
+
# Check that enough time has passed since staking (skip for EVICTED - already expired)
|
|
565
|
+
if staking_state == StakingState.STAKED:
|
|
566
|
+
try:
|
|
567
|
+
service_info = staking_contract.get_service_info(self.service.service_id)
|
|
568
|
+
ts_start = service_info.get("ts_start", 0)
|
|
569
|
+
if ts_start > 0:
|
|
570
|
+
min_duration = staking_contract.min_staking_duration
|
|
571
|
+
unlock_ts = ts_start + min_duration
|
|
572
|
+
now_ts = datetime.now(timezone.utc).timestamp()
|
|
573
|
+
|
|
574
|
+
if now_ts < unlock_ts:
|
|
575
|
+
diff = int(unlock_ts - now_ts)
|
|
576
|
+
logger.error(
|
|
577
|
+
f"Cannot unstake yet. Minimum staking duration not met. Unlocks in {diff} seconds."
|
|
578
|
+
)
|
|
579
|
+
return False
|
|
580
|
+
except Exception as e:
|
|
581
|
+
logger.warning(f"Could not verify staking duration: {e}. Proceeding with caution.")
|
|
581
582
|
|
|
582
583
|
# Use service owner which holds the NFT (not necessarily master)
|
|
583
584
|
owner_address = self.service.service_owner_address or self.wallet.master_account.address
|
|
@@ -51,6 +51,60 @@ def test_sm_unstake_not_staked(mock_wallet):
|
|
|
51
51
|
assert result is False
|
|
52
52
|
|
|
53
53
|
|
|
54
|
+
def test_sm_unstake_evicted_succeeds(mock_wallet):
|
|
55
|
+
"""Cover unstake when service is EVICTED - should succeed."""
|
|
56
|
+
with (
|
|
57
|
+
patch("iwa.core.models.Config"),
|
|
58
|
+
patch("iwa.plugins.olas.service_manager.base.ContractCache") as mock_cache,
|
|
59
|
+
patch("iwa.plugins.olas.service_manager.staking.ContractCache", mock_cache),
|
|
60
|
+
):
|
|
61
|
+
mock_cache.return_value.get_contract.side_effect = lambda cls, *a, **k: cls(*a, **k)
|
|
62
|
+
sm = ServiceManager(mock_wallet)
|
|
63
|
+
sm.service = Service(
|
|
64
|
+
service_name="t", chain_name="gnosis", service_id=1, multisig_address=VALID_ADDR
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
mock_staking = MagicMock()
|
|
68
|
+
mock_staking.get_staking_state.return_value = StakingState.EVICTED
|
|
69
|
+
mock_staking.prepare_unstake_tx.return_value = {"to": VALID_ADDR}
|
|
70
|
+
mock_staking.extract_events.return_value = [{"name": "ServiceUnstaked"}]
|
|
71
|
+
|
|
72
|
+
mock_wallet.sign_and_send_transaction.return_value = (True, {"status": 1})
|
|
73
|
+
|
|
74
|
+
result = sm.unstake(mock_staking)
|
|
75
|
+
assert result is True
|
|
76
|
+
# Verify staking_contract_address is cleared
|
|
77
|
+
assert sm.service.staking_contract_address is None
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def test_sm_unstake_evicted_skips_duration_check(mock_wallet):
|
|
81
|
+
"""Cover that EVICTED services skip the min_staking_duration check."""
|
|
82
|
+
with (
|
|
83
|
+
patch("iwa.core.models.Config"),
|
|
84
|
+
patch("iwa.plugins.olas.service_manager.base.ContractCache") as mock_cache,
|
|
85
|
+
patch("iwa.plugins.olas.service_manager.staking.ContractCache", mock_cache),
|
|
86
|
+
):
|
|
87
|
+
mock_cache.return_value.get_contract.side_effect = lambda cls, *a, **k: cls(*a, **k)
|
|
88
|
+
sm = ServiceManager(mock_wallet)
|
|
89
|
+
sm.service = Service(
|
|
90
|
+
service_name="t", chain_name="gnosis", service_id=1, multisig_address=VALID_ADDR
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
mock_staking = MagicMock()
|
|
94
|
+
mock_staking.get_staking_state.return_value = StakingState.EVICTED
|
|
95
|
+
mock_staking.prepare_unstake_tx.return_value = {"to": VALID_ADDR}
|
|
96
|
+
mock_staking.extract_events.return_value = [{"name": "ServiceUnstaked"}]
|
|
97
|
+
# get_service_info should NOT be called for EVICTED services
|
|
98
|
+
mock_staking.get_service_info.side_effect = AssertionError(
|
|
99
|
+
"get_service_info should not be called for EVICTED"
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
mock_wallet.sign_and_send_transaction.return_value = (True, {"status": 1})
|
|
103
|
+
|
|
104
|
+
result = sm.unstake(mock_staking)
|
|
105
|
+
assert result is True
|
|
106
|
+
|
|
107
|
+
|
|
54
108
|
def test_sm_unstake_tx_fails(mock_wallet):
|
|
55
109
|
"""Cover unstake transaction failure (lines 766-768)."""
|
|
56
110
|
with (
|
|
@@ -96,7 +96,7 @@ iwa/plugins/olas/service_manager/base.py,sha256=EBPg0ymqgtAb7ZvVSfTt31QYgv_6gp4U
|
|
|
96
96
|
iwa/plugins/olas/service_manager/drain.py,sha256=tJLqvMEPOymxukFuxt-AdgOB4bIIciKcXoxOPx9j-LM,13983
|
|
97
97
|
iwa/plugins/olas/service_manager/lifecycle.py,sha256=Jz2WTsBE_kGjFnsnpQlUBmMM2csOuqEfYdvj4cLrNuU,50586
|
|
98
98
|
iwa/plugins/olas/service_manager/mech.py,sha256=NVzVbEmyOe3wK92VEzCCOSuy3HDkEP1MSoVt7Av8Psk,27949
|
|
99
|
-
iwa/plugins/olas/service_manager/staking.py,sha256=
|
|
99
|
+
iwa/plugins/olas/service_manager/staking.py,sha256=Vl7NGzxfNhlajec1kUvqODiq-DBXyd86at5jKrwHDuc,29808
|
|
100
100
|
iwa/plugins/olas/tests/conftest.py,sha256=4vM7EI00SrTGyeP0hNzsGSQHEj2-iznVgzlNh2_OGfo,739
|
|
101
101
|
iwa/plugins/olas/tests/test_importer.py,sha256=i9LKov7kNRECB3hmRnhKBwcfx3uxtjWe4BB77bOOpeo,4282
|
|
102
102
|
iwa/plugins/olas/tests/test_importer_error_handling.py,sha256=GeXu4Par3_FAUL9hT6Sn5PdRg2_EU2gf3iaL73atoYo,12256
|
|
@@ -117,7 +117,7 @@ iwa/plugins/olas/tests/test_service_manager_flows.py,sha256=ZSmBJNa18d_MyAaLQRoP
|
|
|
117
117
|
iwa/plugins/olas/tests/test_service_manager_mech.py,sha256=qG6qu5IPRNypXUsblU2OEkuiuwDJ0TH8RXZbibmTFcQ,4937
|
|
118
118
|
iwa/plugins/olas/tests/test_service_manager_rewards.py,sha256=2YCrXBU5bEkPuhBoGBhjnO1nA2qwHxn5Ivrror18FHM,12248
|
|
119
119
|
iwa/plugins/olas/tests/test_service_manager_validation.py,sha256=ajlfH5uc4mAHf8A7GLE5cW7X8utM2vUilM0JdGDdlVg,5382
|
|
120
|
-
iwa/plugins/olas/tests/test_service_staking.py,sha256=
|
|
120
|
+
iwa/plugins/olas/tests/test_service_staking.py,sha256=exxWsile_wG_0rz_cGbCPG-_Ubq01Ofl4D_pi0plj5Y,18332
|
|
121
121
|
iwa/plugins/olas/tests/test_staking_integration.py,sha256=QCBQf6P2ZmmsEGt2k8W2r53lG2aVRuoMJE-aFxVDLss,9701
|
|
122
122
|
iwa/plugins/olas/tests/test_staking_validation.py,sha256=uug64jFcXYJ3Nw_lNa3O4fnhNr5wAWHHIrchSbR2MVE,4020
|
|
123
123
|
iwa/plugins/olas/tui/__init__.py,sha256=5ZRsbC7J3z1xfkZRiwr4bLEklf78rNVjdswe2p7SlS8,28
|
|
@@ -166,7 +166,7 @@ iwa/web/tests/test_web_endpoints.py,sha256=vA25YghHNB23sbmhD4ciesn_f_okSq0tjlkrS
|
|
|
166
166
|
iwa/web/tests/test_web_olas.py,sha256=GunKEAzcbzL7FoUGMtEl8wqiqwYwA5lB9sOhfCNj0TA,16312
|
|
167
167
|
iwa/web/tests/test_web_swap.py,sha256=7A4gBJFL01kIXPtW1E1J17SCsVc_0DmUn-R8kKrnnVA,2974
|
|
168
168
|
iwa/web/tests/test_web_swap_coverage.py,sha256=zGNrzlhZ_vWDCvWmLcoUwFgqxnrp_ACbo49AtWBS_Kw,5584
|
|
169
|
-
iwa-0.1.
|
|
169
|
+
iwa-0.1.1.dist-info/licenses/LICENSE,sha256=eIubm_IlBHPYRQlLNZKbBNKhJUUP3JH0A2miZUhAVfI,1078
|
|
170
170
|
tests/legacy_cow.py,sha256=oOkZvIxL70ReEoD9oHQbOD5GpjIr6AGNHcOCgfPlerU,8389
|
|
171
171
|
tests/legacy_safe.py,sha256=AssM2g13E74dNGODu_H0Q0y412lgqsrYnEzI97nm_Ts,2972
|
|
172
172
|
tests/legacy_transaction_retry_logic.py,sha256=D9RqZ7DBu61Xr2djBAodU2p9UE939LL-DnQXswX5iQk,1497
|
|
@@ -224,8 +224,8 @@ tests/test_utils.py,sha256=vkP49rYNI8BRzLpWR3WnKdDr8upeZjZcs7Rx0pjbQMo,1292
|
|
|
224
224
|
tests/test_workers.py,sha256=MInwdkFY5LdmFB3o1odIaSD7AQZb3263hNafO1De5PE,2793
|
|
225
225
|
tools/create_and_stake_service.py,sha256=1xwy_bJQI1j9yIQ968Oc9Db_F6mk1659LuuZntTASDE,3742
|
|
226
226
|
tools/verify_drain.py,sha256=PkMjblyOOAuQge88FwfEzRtCYeEtJxXhPBmtQYCoQ-8,6743
|
|
227
|
-
iwa-0.1.
|
|
228
|
-
iwa-0.1.
|
|
229
|
-
iwa-0.1.
|
|
230
|
-
iwa-0.1.
|
|
231
|
-
iwa-0.1.
|
|
227
|
+
iwa-0.1.1.dist-info/METADATA,sha256=jQxFBAjZ8GWJqgloxJkCLzHmgC7rO2YmkwZOpb0EIIM,7336
|
|
228
|
+
iwa-0.1.1.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
229
|
+
iwa-0.1.1.dist-info/entry_points.txt,sha256=nwB6kscrfA7M00pYmL2j-sBH6eF6h2ga9IK1BZxdiyQ,241
|
|
230
|
+
iwa-0.1.1.dist-info/top_level.txt,sha256=kedS9cRUbm4JE2wYeabIXilhHjN8KCw0IGbqqqsw0Bs,16
|
|
231
|
+
iwa-0.1.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|