iwa 0.1.2__py3-none-any.whl → 0.1.4__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/contracts/staking.py +135 -0
- iwa/plugins/olas/tests/test_staking_integration.py +500 -0
- iwa/web/dependencies.py +36 -2
- iwa/web/server.py +18 -1
- iwa/web/tests/test_wallet_injection.py +204 -0
- {iwa-0.1.2.dist-info → iwa-0.1.4.dist-info}/METADATA +1 -1
- {iwa-0.1.2.dist-info → iwa-0.1.4.dist-info}/RECORD +11 -10
- {iwa-0.1.2.dist-info → iwa-0.1.4.dist-info}/WHEEL +0 -0
- {iwa-0.1.2.dist-info → iwa-0.1.4.dist-info}/entry_points.txt +0 -0
- {iwa-0.1.2.dist-info → iwa-0.1.4.dist-info}/licenses/LICENSE +0 -0
- {iwa-0.1.2.dist-info → iwa-0.1.4.dist-info}/top_level.txt +0 -0
|
@@ -495,3 +495,138 @@ class StakingContract(ContractInstance):
|
|
|
495
495
|
tx_params={"from": from_address},
|
|
496
496
|
)
|
|
497
497
|
return tx
|
|
498
|
+
|
|
499
|
+
def _fetch_events_chunked(
|
|
500
|
+
self,
|
|
501
|
+
event_type: str,
|
|
502
|
+
from_block: int,
|
|
503
|
+
to_block: int,
|
|
504
|
+
chunk_size: int = 500,
|
|
505
|
+
) -> List:
|
|
506
|
+
"""Fetch events in chunks to handle RPC block range limits.
|
|
507
|
+
|
|
508
|
+
Args:
|
|
509
|
+
event_type: Name of the event (Checkpoint, ServiceInactivityWarning, etc.)
|
|
510
|
+
from_block: Starting block number.
|
|
511
|
+
to_block: Ending block number.
|
|
512
|
+
chunk_size: Max blocks per request (default 500).
|
|
513
|
+
|
|
514
|
+
Returns:
|
|
515
|
+
List of event log entries.
|
|
516
|
+
|
|
517
|
+
"""
|
|
518
|
+
all_logs: List = []
|
|
519
|
+
current_from = from_block
|
|
520
|
+
|
|
521
|
+
event = getattr(self.contract.events, event_type, None)
|
|
522
|
+
if event is None:
|
|
523
|
+
logger.debug(f"Event {event_type} not found in contract ABI")
|
|
524
|
+
return []
|
|
525
|
+
|
|
526
|
+
while current_from <= to_block:
|
|
527
|
+
current_to = min(current_from + chunk_size - 1, to_block)
|
|
528
|
+
|
|
529
|
+
try:
|
|
530
|
+
event_filter = event.create_filter(
|
|
531
|
+
from_block=current_from, to_block=current_to
|
|
532
|
+
)
|
|
533
|
+
logs = event_filter.get_all_entries()
|
|
534
|
+
all_logs.extend(logs)
|
|
535
|
+
except Exception as e:
|
|
536
|
+
error_msg = str(e).lower()
|
|
537
|
+
# If range too large, try smaller chunks
|
|
538
|
+
if "range" in error_msg or "limit" in error_msg or "10000" in error_msg:
|
|
539
|
+
if chunk_size > 100:
|
|
540
|
+
logger.debug(
|
|
541
|
+
"Block range too large, retrying with smaller chunks"
|
|
542
|
+
)
|
|
543
|
+
smaller_logs = self._fetch_events_chunked(
|
|
544
|
+
event_type, current_from, current_to, chunk_size // 2
|
|
545
|
+
)
|
|
546
|
+
all_logs.extend(smaller_logs)
|
|
547
|
+
else:
|
|
548
|
+
logger.warning(
|
|
549
|
+
f"Cannot fetch {event_type} events for blocks "
|
|
550
|
+
f"{current_from}-{current_to}: {e}"
|
|
551
|
+
)
|
|
552
|
+
else:
|
|
553
|
+
logger.debug(f"Error fetching {event_type} events: {e}")
|
|
554
|
+
|
|
555
|
+
current_from = current_to + 1
|
|
556
|
+
|
|
557
|
+
return all_logs
|
|
558
|
+
|
|
559
|
+
def get_checkpoint_events(
|
|
560
|
+
self, from_block: int, to_block: Optional[int] = None
|
|
561
|
+
) -> Dict:
|
|
562
|
+
"""Get checkpoint-related events from a block range.
|
|
563
|
+
|
|
564
|
+
Retrieves Checkpoint, ServiceInactivityWarning, and ServicesEvicted events
|
|
565
|
+
to determine which services received rewards and which got warnings.
|
|
566
|
+
|
|
567
|
+
Uses chunked fetching to handle RPCs that limit block ranges.
|
|
568
|
+
|
|
569
|
+
Args:
|
|
570
|
+
from_block: Starting block number.
|
|
571
|
+
to_block: Ending block number (defaults to latest).
|
|
572
|
+
|
|
573
|
+
Returns:
|
|
574
|
+
Dict with:
|
|
575
|
+
- epoch: int, the new epoch number
|
|
576
|
+
- rewarded_services: Dict[int, int] mapping service_id -> reward (wei)
|
|
577
|
+
- inactivity_warnings: List[int] of service IDs with warnings
|
|
578
|
+
- evicted_services: List[int] of evicted service IDs
|
|
579
|
+
- checkpoint_block: int, block where checkpoint occurred
|
|
580
|
+
|
|
581
|
+
"""
|
|
582
|
+
if to_block is None:
|
|
583
|
+
to_block = self.chain_interface.web3.eth.block_number
|
|
584
|
+
|
|
585
|
+
result: Dict = {
|
|
586
|
+
"epoch": None,
|
|
587
|
+
"rewarded_services": {},
|
|
588
|
+
"inactivity_warnings": [],
|
|
589
|
+
"evicted_services": [],
|
|
590
|
+
"checkpoint_block": None,
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
try:
|
|
594
|
+
# Get Checkpoint events
|
|
595
|
+
checkpoint_logs = self._fetch_events_chunked(
|
|
596
|
+
"Checkpoint", from_block, to_block
|
|
597
|
+
)
|
|
598
|
+
|
|
599
|
+
if checkpoint_logs:
|
|
600
|
+
# Take the most recent checkpoint
|
|
601
|
+
latest = checkpoint_logs[-1]
|
|
602
|
+
result["epoch"] = latest.args.get("epoch")
|
|
603
|
+
result["checkpoint_block"] = latest.blockNumber
|
|
604
|
+
|
|
605
|
+
# Parse serviceIds and rewards arrays
|
|
606
|
+
service_ids = latest.args.get("serviceIds", [])
|
|
607
|
+
rewards = latest.args.get("rewards", [])
|
|
608
|
+
|
|
609
|
+
for sid, reward in zip(service_ids, rewards, strict=True):
|
|
610
|
+
result["rewarded_services"][sid] = reward
|
|
611
|
+
|
|
612
|
+
# Get ServiceInactivityWarning events
|
|
613
|
+
warning_logs = self._fetch_events_chunked(
|
|
614
|
+
"ServiceInactivityWarning", from_block, to_block
|
|
615
|
+
)
|
|
616
|
+
for log in warning_logs:
|
|
617
|
+
service_id = log.args.get("serviceId")
|
|
618
|
+
if service_id is not None:
|
|
619
|
+
result["inactivity_warnings"].append(service_id)
|
|
620
|
+
|
|
621
|
+
# Get ServicesEvicted events
|
|
622
|
+
evicted_logs = self._fetch_events_chunked(
|
|
623
|
+
"ServicesEvicted", from_block, to_block
|
|
624
|
+
)
|
|
625
|
+
for log in evicted_logs:
|
|
626
|
+
evicted_ids = log.args.get("serviceIds", [])
|
|
627
|
+
result["evicted_services"].extend(evicted_ids)
|
|
628
|
+
|
|
629
|
+
except Exception as e:
|
|
630
|
+
logger.error(f"Error fetching checkpoint events: {e}")
|
|
631
|
+
|
|
632
|
+
return result
|
|
@@ -271,3 +271,503 @@ def test_staking_contract(tmp_path): # noqa: C901
|
|
|
271
271
|
# Verify new nonces fields
|
|
272
272
|
assert info["current_safe_nonce"] == 5
|
|
273
273
|
assert info["current_mech_requests"] == 3
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def test_get_checkpoint_events():
|
|
277
|
+
"""Test get_checkpoint_events method."""
|
|
278
|
+
with patch("builtins.open", side_effect=side_effect_open):
|
|
279
|
+
with patch("iwa.core.contracts.contract.ChainInterfaces") as mock_interfaces:
|
|
280
|
+
mock_chain = MagicMock()
|
|
281
|
+
mock_interfaces.return_value.get.return_value = mock_chain
|
|
282
|
+
|
|
283
|
+
mock_web3 = MagicMock()
|
|
284
|
+
mock_chain.web3 = mock_web3
|
|
285
|
+
mock_chain.web3._web3 = mock_web3
|
|
286
|
+
mock_web3.eth.block_number = 1000
|
|
287
|
+
|
|
288
|
+
mock_contract = MagicMock()
|
|
289
|
+
mock_web3.eth.contract.return_value = mock_contract
|
|
290
|
+
|
|
291
|
+
# Mock ActivityChecker calls
|
|
292
|
+
mock_contract.functions.agentMech.return_value.call.return_value = VALID_ADDR_2
|
|
293
|
+
mock_contract.functions.livenessRatio.return_value.call.return_value = 10**18
|
|
294
|
+
|
|
295
|
+
with patch(
|
|
296
|
+
"iwa.plugins.olas.contracts.staking.ContractInstance.call"
|
|
297
|
+
) as mock_call_base:
|
|
298
|
+
|
|
299
|
+
def init_side_effect(method, *args):
|
|
300
|
+
if method == "activityChecker":
|
|
301
|
+
return VALID_ADDR_4
|
|
302
|
+
if method == "stakingToken":
|
|
303
|
+
return VALID_ADDR_2
|
|
304
|
+
return 0
|
|
305
|
+
|
|
306
|
+
mock_call_base.side_effect = init_side_effect
|
|
307
|
+
|
|
308
|
+
staking = StakingContract(VALID_ADDR_1)
|
|
309
|
+
|
|
310
|
+
# Test 1: Checkpoint event with rewarded services
|
|
311
|
+
mock_checkpoint_log = MagicMock()
|
|
312
|
+
mock_checkpoint_log.args = {
|
|
313
|
+
"epoch": 42,
|
|
314
|
+
"serviceIds": [101, 102, 103],
|
|
315
|
+
"rewards": [1000, 2000, 3000],
|
|
316
|
+
}
|
|
317
|
+
mock_checkpoint_log.blockNumber = 999
|
|
318
|
+
|
|
319
|
+
mock_checkpoint_filter = MagicMock()
|
|
320
|
+
mock_checkpoint_filter.get_all_entries.return_value = [mock_checkpoint_log]
|
|
321
|
+
mock_contract.events.Checkpoint.create_filter.return_value = (
|
|
322
|
+
mock_checkpoint_filter
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
# No warnings or evictions
|
|
326
|
+
mock_warning_filter = MagicMock()
|
|
327
|
+
mock_warning_filter.get_all_entries.return_value = []
|
|
328
|
+
mock_contract.events.ServiceInactivityWarning.create_filter.return_value = (
|
|
329
|
+
mock_warning_filter
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
mock_evicted_filter = MagicMock()
|
|
333
|
+
mock_evicted_filter.get_all_entries.return_value = []
|
|
334
|
+
mock_contract.events.ServicesEvicted.create_filter.return_value = (
|
|
335
|
+
mock_evicted_filter
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
result = staking.get_checkpoint_events(from_block=900, to_block=1000)
|
|
339
|
+
|
|
340
|
+
assert result["epoch"] == 42
|
|
341
|
+
assert result["checkpoint_block"] == 999
|
|
342
|
+
assert result["rewarded_services"] == {101: 1000, 102: 2000, 103: 3000}
|
|
343
|
+
assert result["inactivity_warnings"] == []
|
|
344
|
+
assert result["evicted_services"] == []
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
def test_get_checkpoint_events_with_warnings():
|
|
348
|
+
"""Test get_checkpoint_events with inactivity warnings."""
|
|
349
|
+
with patch("builtins.open", side_effect=side_effect_open):
|
|
350
|
+
with patch("iwa.core.contracts.contract.ChainInterfaces") as mock_interfaces:
|
|
351
|
+
mock_chain = MagicMock()
|
|
352
|
+
mock_interfaces.return_value.get.return_value = mock_chain
|
|
353
|
+
|
|
354
|
+
mock_web3 = MagicMock()
|
|
355
|
+
mock_chain.web3 = mock_web3
|
|
356
|
+
mock_chain.web3._web3 = mock_web3
|
|
357
|
+
mock_web3.eth.block_number = 1000
|
|
358
|
+
|
|
359
|
+
mock_contract = MagicMock()
|
|
360
|
+
mock_web3.eth.contract.return_value = mock_contract
|
|
361
|
+
|
|
362
|
+
mock_contract.functions.agentMech.return_value.call.return_value = VALID_ADDR_2
|
|
363
|
+
mock_contract.functions.livenessRatio.return_value.call.return_value = 10**18
|
|
364
|
+
|
|
365
|
+
with patch(
|
|
366
|
+
"iwa.plugins.olas.contracts.staking.ContractInstance.call"
|
|
367
|
+
) as mock_call_base:
|
|
368
|
+
|
|
369
|
+
def init_side_effect(method, *args):
|
|
370
|
+
if method == "activityChecker":
|
|
371
|
+
return VALID_ADDR_4
|
|
372
|
+
if method == "stakingToken":
|
|
373
|
+
return VALID_ADDR_2
|
|
374
|
+
return 0
|
|
375
|
+
|
|
376
|
+
mock_call_base.side_effect = init_side_effect
|
|
377
|
+
|
|
378
|
+
staking = StakingContract(VALID_ADDR_1)
|
|
379
|
+
|
|
380
|
+
# Checkpoint event
|
|
381
|
+
mock_checkpoint_log = MagicMock()
|
|
382
|
+
mock_checkpoint_log.args = {
|
|
383
|
+
"epoch": 10,
|
|
384
|
+
"serviceIds": [101, 102],
|
|
385
|
+
"rewards": [1000, 2000],
|
|
386
|
+
}
|
|
387
|
+
mock_checkpoint_log.blockNumber = 999
|
|
388
|
+
|
|
389
|
+
mock_checkpoint_filter = MagicMock()
|
|
390
|
+
mock_checkpoint_filter.get_all_entries.return_value = [mock_checkpoint_log]
|
|
391
|
+
mock_contract.events.Checkpoint.create_filter.return_value = (
|
|
392
|
+
mock_checkpoint_filter
|
|
393
|
+
)
|
|
394
|
+
|
|
395
|
+
# Inactivity warnings
|
|
396
|
+
mock_warning_log_1 = MagicMock()
|
|
397
|
+
mock_warning_log_1.args = {"serviceId": 101}
|
|
398
|
+
mock_warning_log_2 = MagicMock()
|
|
399
|
+
mock_warning_log_2.args = {"serviceId": 103}
|
|
400
|
+
|
|
401
|
+
mock_warning_filter = MagicMock()
|
|
402
|
+
mock_warning_filter.get_all_entries.return_value = [
|
|
403
|
+
mock_warning_log_1,
|
|
404
|
+
mock_warning_log_2,
|
|
405
|
+
]
|
|
406
|
+
mock_contract.events.ServiceInactivityWarning.create_filter.return_value = (
|
|
407
|
+
mock_warning_filter
|
|
408
|
+
)
|
|
409
|
+
|
|
410
|
+
mock_evicted_filter = MagicMock()
|
|
411
|
+
mock_evicted_filter.get_all_entries.return_value = []
|
|
412
|
+
mock_contract.events.ServicesEvicted.create_filter.return_value = (
|
|
413
|
+
mock_evicted_filter
|
|
414
|
+
)
|
|
415
|
+
|
|
416
|
+
result = staking.get_checkpoint_events(from_block=900)
|
|
417
|
+
|
|
418
|
+
assert result["epoch"] == 10
|
|
419
|
+
assert result["inactivity_warnings"] == [101, 103]
|
|
420
|
+
assert result["evicted_services"] == []
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
def test_get_checkpoint_events_with_evictions():
|
|
424
|
+
"""Test get_checkpoint_events with evicted services."""
|
|
425
|
+
with patch("builtins.open", side_effect=side_effect_open):
|
|
426
|
+
with patch("iwa.core.contracts.contract.ChainInterfaces") as mock_interfaces:
|
|
427
|
+
mock_chain = MagicMock()
|
|
428
|
+
mock_interfaces.return_value.get.return_value = mock_chain
|
|
429
|
+
|
|
430
|
+
mock_web3 = MagicMock()
|
|
431
|
+
mock_chain.web3 = mock_web3
|
|
432
|
+
mock_chain.web3._web3 = mock_web3
|
|
433
|
+
mock_web3.eth.block_number = 1000
|
|
434
|
+
|
|
435
|
+
mock_contract = MagicMock()
|
|
436
|
+
mock_web3.eth.contract.return_value = mock_contract
|
|
437
|
+
|
|
438
|
+
mock_contract.functions.agentMech.return_value.call.return_value = VALID_ADDR_2
|
|
439
|
+
mock_contract.functions.livenessRatio.return_value.call.return_value = 10**18
|
|
440
|
+
|
|
441
|
+
with patch(
|
|
442
|
+
"iwa.plugins.olas.contracts.staking.ContractInstance.call"
|
|
443
|
+
) as mock_call_base:
|
|
444
|
+
|
|
445
|
+
def init_side_effect(method, *args):
|
|
446
|
+
if method == "activityChecker":
|
|
447
|
+
return VALID_ADDR_4
|
|
448
|
+
if method == "stakingToken":
|
|
449
|
+
return VALID_ADDR_2
|
|
450
|
+
return 0
|
|
451
|
+
|
|
452
|
+
mock_call_base.side_effect = init_side_effect
|
|
453
|
+
|
|
454
|
+
staking = StakingContract(VALID_ADDR_1)
|
|
455
|
+
|
|
456
|
+
# Checkpoint event
|
|
457
|
+
mock_checkpoint_log = MagicMock()
|
|
458
|
+
mock_checkpoint_log.args = {
|
|
459
|
+
"epoch": 5,
|
|
460
|
+
"serviceIds": [102],
|
|
461
|
+
"rewards": [5000],
|
|
462
|
+
}
|
|
463
|
+
mock_checkpoint_log.blockNumber = 888
|
|
464
|
+
|
|
465
|
+
mock_checkpoint_filter = MagicMock()
|
|
466
|
+
mock_checkpoint_filter.get_all_entries.return_value = [mock_checkpoint_log]
|
|
467
|
+
mock_contract.events.Checkpoint.create_filter.return_value = (
|
|
468
|
+
mock_checkpoint_filter
|
|
469
|
+
)
|
|
470
|
+
|
|
471
|
+
# No warnings
|
|
472
|
+
mock_warning_filter = MagicMock()
|
|
473
|
+
mock_warning_filter.get_all_entries.return_value = []
|
|
474
|
+
mock_contract.events.ServiceInactivityWarning.create_filter.return_value = (
|
|
475
|
+
mock_warning_filter
|
|
476
|
+
)
|
|
477
|
+
|
|
478
|
+
# Evictions
|
|
479
|
+
mock_evicted_log = MagicMock()
|
|
480
|
+
mock_evicted_log.args = {"serviceIds": [101, 104]}
|
|
481
|
+
|
|
482
|
+
mock_evicted_filter = MagicMock()
|
|
483
|
+
mock_evicted_filter.get_all_entries.return_value = [mock_evicted_log]
|
|
484
|
+
mock_contract.events.ServicesEvicted.create_filter.return_value = (
|
|
485
|
+
mock_evicted_filter
|
|
486
|
+
)
|
|
487
|
+
|
|
488
|
+
result = staking.get_checkpoint_events(from_block=800)
|
|
489
|
+
|
|
490
|
+
assert result["epoch"] == 5
|
|
491
|
+
assert result["checkpoint_block"] == 888
|
|
492
|
+
assert result["rewarded_services"] == {102: 5000}
|
|
493
|
+
assert result["evicted_services"] == [101, 104]
|
|
494
|
+
|
|
495
|
+
|
|
496
|
+
def test_get_checkpoint_events_no_events():
|
|
497
|
+
"""Test get_checkpoint_events when no checkpoint events found."""
|
|
498
|
+
with patch("builtins.open", side_effect=side_effect_open):
|
|
499
|
+
with patch("iwa.core.contracts.contract.ChainInterfaces") as mock_interfaces:
|
|
500
|
+
mock_chain = MagicMock()
|
|
501
|
+
mock_interfaces.return_value.get.return_value = mock_chain
|
|
502
|
+
|
|
503
|
+
mock_web3 = MagicMock()
|
|
504
|
+
mock_chain.web3 = mock_web3
|
|
505
|
+
mock_chain.web3._web3 = mock_web3
|
|
506
|
+
mock_web3.eth.block_number = 1000
|
|
507
|
+
|
|
508
|
+
mock_contract = MagicMock()
|
|
509
|
+
mock_web3.eth.contract.return_value = mock_contract
|
|
510
|
+
|
|
511
|
+
mock_contract.functions.agentMech.return_value.call.return_value = VALID_ADDR_2
|
|
512
|
+
mock_contract.functions.livenessRatio.return_value.call.return_value = 10**18
|
|
513
|
+
|
|
514
|
+
with patch(
|
|
515
|
+
"iwa.plugins.olas.contracts.staking.ContractInstance.call"
|
|
516
|
+
) as mock_call_base:
|
|
517
|
+
|
|
518
|
+
def init_side_effect(method, *args):
|
|
519
|
+
if method == "activityChecker":
|
|
520
|
+
return VALID_ADDR_4
|
|
521
|
+
if method == "stakingToken":
|
|
522
|
+
return VALID_ADDR_2
|
|
523
|
+
return 0
|
|
524
|
+
|
|
525
|
+
mock_call_base.side_effect = init_side_effect
|
|
526
|
+
|
|
527
|
+
staking = StakingContract(VALID_ADDR_1)
|
|
528
|
+
|
|
529
|
+
# No checkpoint events
|
|
530
|
+
mock_checkpoint_filter = MagicMock()
|
|
531
|
+
mock_checkpoint_filter.get_all_entries.return_value = []
|
|
532
|
+
mock_contract.events.Checkpoint.create_filter.return_value = (
|
|
533
|
+
mock_checkpoint_filter
|
|
534
|
+
)
|
|
535
|
+
|
|
536
|
+
mock_warning_filter = MagicMock()
|
|
537
|
+
mock_warning_filter.get_all_entries.return_value = []
|
|
538
|
+
mock_contract.events.ServiceInactivityWarning.create_filter.return_value = (
|
|
539
|
+
mock_warning_filter
|
|
540
|
+
)
|
|
541
|
+
|
|
542
|
+
mock_evicted_filter = MagicMock()
|
|
543
|
+
mock_evicted_filter.get_all_entries.return_value = []
|
|
544
|
+
mock_contract.events.ServicesEvicted.create_filter.return_value = (
|
|
545
|
+
mock_evicted_filter
|
|
546
|
+
)
|
|
547
|
+
|
|
548
|
+
result = staking.get_checkpoint_events(from_block=900)
|
|
549
|
+
|
|
550
|
+
assert result["epoch"] is None
|
|
551
|
+
assert result["checkpoint_block"] is None
|
|
552
|
+
assert result["rewarded_services"] == {}
|
|
553
|
+
assert result["inactivity_warnings"] == []
|
|
554
|
+
assert result["evicted_services"] == []
|
|
555
|
+
|
|
556
|
+
|
|
557
|
+
def test_get_checkpoint_events_handles_exceptions():
|
|
558
|
+
"""Test get_checkpoint_events handles exceptions gracefully."""
|
|
559
|
+
with patch("builtins.open", side_effect=side_effect_open):
|
|
560
|
+
with patch("iwa.core.contracts.contract.ChainInterfaces") as mock_interfaces:
|
|
561
|
+
mock_chain = MagicMock()
|
|
562
|
+
mock_interfaces.return_value.get.return_value = mock_chain
|
|
563
|
+
|
|
564
|
+
mock_web3 = MagicMock()
|
|
565
|
+
mock_chain.web3 = mock_web3
|
|
566
|
+
mock_chain.web3._web3 = mock_web3
|
|
567
|
+
mock_web3.eth.block_number = 1000
|
|
568
|
+
|
|
569
|
+
mock_contract = MagicMock()
|
|
570
|
+
mock_web3.eth.contract.return_value = mock_contract
|
|
571
|
+
|
|
572
|
+
mock_contract.functions.agentMech.return_value.call.return_value = VALID_ADDR_2
|
|
573
|
+
mock_contract.functions.livenessRatio.return_value.call.return_value = 10**18
|
|
574
|
+
|
|
575
|
+
with patch(
|
|
576
|
+
"iwa.plugins.olas.contracts.staking.ContractInstance.call"
|
|
577
|
+
) as mock_call_base:
|
|
578
|
+
|
|
579
|
+
def init_side_effect(method, *args):
|
|
580
|
+
if method == "activityChecker":
|
|
581
|
+
return VALID_ADDR_4
|
|
582
|
+
if method == "stakingToken":
|
|
583
|
+
return VALID_ADDR_2
|
|
584
|
+
return 0
|
|
585
|
+
|
|
586
|
+
mock_call_base.side_effect = init_side_effect
|
|
587
|
+
|
|
588
|
+
staking = StakingContract(VALID_ADDR_1)
|
|
589
|
+
|
|
590
|
+
# Checkpoint filter raises an exception
|
|
591
|
+
mock_contract.events.Checkpoint.create_filter.side_effect = Exception(
|
|
592
|
+
"RPC error"
|
|
593
|
+
)
|
|
594
|
+
|
|
595
|
+
result = staking.get_checkpoint_events(from_block=900)
|
|
596
|
+
|
|
597
|
+
# Should return default empty result
|
|
598
|
+
assert result["epoch"] is None
|
|
599
|
+
assert result["rewarded_services"] == {}
|
|
600
|
+
assert result["inactivity_warnings"] == []
|
|
601
|
+
assert result["evicted_services"] == []
|
|
602
|
+
|
|
603
|
+
|
|
604
|
+
def test_fetch_events_chunked_splits_large_range():
|
|
605
|
+
"""Test that _fetch_events_chunked splits large block ranges into chunks."""
|
|
606
|
+
with patch("builtins.open", side_effect=side_effect_open):
|
|
607
|
+
with patch("iwa.core.contracts.contract.ChainInterfaces") as mock_interfaces:
|
|
608
|
+
mock_chain = MagicMock()
|
|
609
|
+
mock_interfaces.return_value.get.return_value = mock_chain
|
|
610
|
+
|
|
611
|
+
mock_web3 = MagicMock()
|
|
612
|
+
mock_chain.web3 = mock_web3
|
|
613
|
+
mock_chain.web3._web3 = mock_web3
|
|
614
|
+
mock_web3.eth.block_number = 2000
|
|
615
|
+
|
|
616
|
+
mock_contract = MagicMock()
|
|
617
|
+
mock_web3.eth.contract.return_value = mock_contract
|
|
618
|
+
|
|
619
|
+
mock_contract.functions.agentMech.return_value.call.return_value = VALID_ADDR_2
|
|
620
|
+
mock_contract.functions.livenessRatio.return_value.call.return_value = 10**18
|
|
621
|
+
|
|
622
|
+
with patch(
|
|
623
|
+
"iwa.plugins.olas.contracts.staking.ContractInstance.call"
|
|
624
|
+
) as mock_call_base:
|
|
625
|
+
|
|
626
|
+
def init_side_effect(method, *args):
|
|
627
|
+
if method == "activityChecker":
|
|
628
|
+
return VALID_ADDR_4
|
|
629
|
+
if method == "stakingToken":
|
|
630
|
+
return VALID_ADDR_2
|
|
631
|
+
return 0
|
|
632
|
+
|
|
633
|
+
mock_call_base.side_effect = init_side_effect
|
|
634
|
+
|
|
635
|
+
staking = StakingContract(VALID_ADDR_1)
|
|
636
|
+
|
|
637
|
+
# Track calls to create_filter
|
|
638
|
+
call_ranges = []
|
|
639
|
+
|
|
640
|
+
def track_filter_calls(from_block, to_block):
|
|
641
|
+
call_ranges.append((from_block, to_block))
|
|
642
|
+
mock_filter = MagicMock()
|
|
643
|
+
mock_filter.get_all_entries.return_value = []
|
|
644
|
+
return mock_filter
|
|
645
|
+
|
|
646
|
+
mock_contract.events.Checkpoint.create_filter.side_effect = (
|
|
647
|
+
track_filter_calls
|
|
648
|
+
)
|
|
649
|
+
|
|
650
|
+
# Fetch 1200 blocks with chunk_size=500 -> should make 3 calls
|
|
651
|
+
staking._fetch_events_chunked("Checkpoint", 0, 1199, chunk_size=500)
|
|
652
|
+
|
|
653
|
+
assert len(call_ranges) == 3
|
|
654
|
+
assert call_ranges[0] == (0, 499)
|
|
655
|
+
assert call_ranges[1] == (500, 999)
|
|
656
|
+
assert call_ranges[2] == (1000, 1199)
|
|
657
|
+
|
|
658
|
+
|
|
659
|
+
def test_fetch_events_chunked_retries_with_smaller_chunks():
|
|
660
|
+
"""Test that _fetch_events_chunked retries with smaller chunks on range error."""
|
|
661
|
+
with patch("builtins.open", side_effect=side_effect_open):
|
|
662
|
+
with patch("iwa.core.contracts.contract.ChainInterfaces") as mock_interfaces:
|
|
663
|
+
mock_chain = MagicMock()
|
|
664
|
+
mock_interfaces.return_value.get.return_value = mock_chain
|
|
665
|
+
|
|
666
|
+
mock_web3 = MagicMock()
|
|
667
|
+
mock_chain.web3 = mock_web3
|
|
668
|
+
mock_chain.web3._web3 = mock_web3
|
|
669
|
+
mock_web3.eth.block_number = 1000
|
|
670
|
+
|
|
671
|
+
mock_contract = MagicMock()
|
|
672
|
+
mock_web3.eth.contract.return_value = mock_contract
|
|
673
|
+
|
|
674
|
+
mock_contract.functions.agentMech.return_value.call.return_value = VALID_ADDR_2
|
|
675
|
+
mock_contract.functions.livenessRatio.return_value.call.return_value = 10**18
|
|
676
|
+
|
|
677
|
+
with patch(
|
|
678
|
+
"iwa.plugins.olas.contracts.staking.ContractInstance.call"
|
|
679
|
+
) as mock_call_base:
|
|
680
|
+
|
|
681
|
+
def init_side_effect(method, *args):
|
|
682
|
+
if method == "activityChecker":
|
|
683
|
+
return VALID_ADDR_4
|
|
684
|
+
if method == "stakingToken":
|
|
685
|
+
return VALID_ADDR_2
|
|
686
|
+
return 0
|
|
687
|
+
|
|
688
|
+
mock_call_base.side_effect = init_side_effect
|
|
689
|
+
|
|
690
|
+
staking = StakingContract(VALID_ADDR_1)
|
|
691
|
+
|
|
692
|
+
call_count = [0]
|
|
693
|
+
|
|
694
|
+
def fail_then_succeed(from_block, to_block):
|
|
695
|
+
call_count[0] += 1
|
|
696
|
+
# First call fails with range error, subsequent calls succeed
|
|
697
|
+
if call_count[0] == 1:
|
|
698
|
+
raise Exception("block range too large")
|
|
699
|
+
mock_filter = MagicMock()
|
|
700
|
+
mock_filter.get_all_entries.return_value = []
|
|
701
|
+
return mock_filter
|
|
702
|
+
|
|
703
|
+
mock_contract.events.Checkpoint.create_filter.side_effect = (
|
|
704
|
+
fail_then_succeed
|
|
705
|
+
)
|
|
706
|
+
|
|
707
|
+
# Should retry with smaller chunks
|
|
708
|
+
result = staking._fetch_events_chunked(
|
|
709
|
+
"Checkpoint", 0, 499, chunk_size=500
|
|
710
|
+
)
|
|
711
|
+
|
|
712
|
+
# First call fails, then retries with chunk_size=250 (2 calls)
|
|
713
|
+
assert call_count[0] >= 2
|
|
714
|
+
assert result == []
|
|
715
|
+
|
|
716
|
+
|
|
717
|
+
def test_fetch_events_chunked_aggregates_results():
|
|
718
|
+
"""Test that _fetch_events_chunked aggregates events from all chunks."""
|
|
719
|
+
with patch("builtins.open", side_effect=side_effect_open):
|
|
720
|
+
with patch("iwa.core.contracts.contract.ChainInterfaces") as mock_interfaces:
|
|
721
|
+
mock_chain = MagicMock()
|
|
722
|
+
mock_interfaces.return_value.get.return_value = mock_chain
|
|
723
|
+
|
|
724
|
+
mock_web3 = MagicMock()
|
|
725
|
+
mock_chain.web3 = mock_web3
|
|
726
|
+
mock_chain.web3._web3 = mock_web3
|
|
727
|
+
mock_web3.eth.block_number = 1000
|
|
728
|
+
|
|
729
|
+
mock_contract = MagicMock()
|
|
730
|
+
mock_web3.eth.contract.return_value = mock_contract
|
|
731
|
+
|
|
732
|
+
mock_contract.functions.agentMech.return_value.call.return_value = VALID_ADDR_2
|
|
733
|
+
mock_contract.functions.livenessRatio.return_value.call.return_value = 10**18
|
|
734
|
+
|
|
735
|
+
with patch(
|
|
736
|
+
"iwa.plugins.olas.contracts.staking.ContractInstance.call"
|
|
737
|
+
) as mock_call_base:
|
|
738
|
+
|
|
739
|
+
def init_side_effect(method, *args):
|
|
740
|
+
if method == "activityChecker":
|
|
741
|
+
return VALID_ADDR_4
|
|
742
|
+
if method == "stakingToken":
|
|
743
|
+
return VALID_ADDR_2
|
|
744
|
+
return 0
|
|
745
|
+
|
|
746
|
+
mock_call_base.side_effect = init_side_effect
|
|
747
|
+
|
|
748
|
+
staking = StakingContract(VALID_ADDR_1)
|
|
749
|
+
|
|
750
|
+
chunk_num = [0]
|
|
751
|
+
|
|
752
|
+
def return_different_events(from_block, to_block):
|
|
753
|
+
chunk_num[0] += 1
|
|
754
|
+
mock_filter = MagicMock()
|
|
755
|
+
# Each chunk returns a different event
|
|
756
|
+
event = MagicMock()
|
|
757
|
+
event.args = {"serviceId": 100 + chunk_num[0]}
|
|
758
|
+
mock_filter.get_all_entries.return_value = [event]
|
|
759
|
+
return mock_filter
|
|
760
|
+
|
|
761
|
+
mock_contract.events.ServiceInactivityWarning.create_filter.side_effect = (
|
|
762
|
+
return_different_events
|
|
763
|
+
)
|
|
764
|
+
|
|
765
|
+
# Fetch 600 blocks with chunk_size=200 -> 3 chunks
|
|
766
|
+
result = staking._fetch_events_chunked(
|
|
767
|
+
"ServiceInactivityWarning", 0, 599, chunk_size=200
|
|
768
|
+
)
|
|
769
|
+
|
|
770
|
+
# Should have 3 events aggregated
|
|
771
|
+
assert len(result) == 3
|
|
772
|
+
service_ids = [e.args["serviceId"] for e in result]
|
|
773
|
+
assert service_ids == [101, 102, 103]
|
iwa/web/dependencies.py
CHANGED
|
@@ -9,8 +9,42 @@ from loguru import logger
|
|
|
9
9
|
|
|
10
10
|
from iwa.core.wallet import Wallet
|
|
11
11
|
|
|
12
|
-
# Singleton wallet instance
|
|
13
|
-
|
|
12
|
+
# Singleton wallet instance (lazy-initialized or injected)
|
|
13
|
+
_wallet: Optional[Wallet] = None
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def get_wallet() -> Wallet:
|
|
17
|
+
"""Get the wallet instance (lazy initialization or injected).
|
|
18
|
+
|
|
19
|
+
Returns the injected wallet if set_wallet() was called,
|
|
20
|
+
otherwise creates a new Wallet on first access.
|
|
21
|
+
"""
|
|
22
|
+
global _wallet
|
|
23
|
+
if _wallet is None:
|
|
24
|
+
_wallet = Wallet()
|
|
25
|
+
return _wallet
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def set_wallet(wallet: Wallet) -> None:
|
|
29
|
+
"""Inject an external wallet instance.
|
|
30
|
+
|
|
31
|
+
Call this BEFORE importing routers to share a wallet
|
|
32
|
+
with an external application (e.g., Triton).
|
|
33
|
+
"""
|
|
34
|
+
global _wallet
|
|
35
|
+
_wallet = wallet
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
# Backwards compatibility: module-level wallet property
|
|
39
|
+
# Deprecated: use get_wallet() instead
|
|
40
|
+
class _WalletProxy:
|
|
41
|
+
"""Proxy that redirects to get_wallet() for backwards compatibility."""
|
|
42
|
+
|
|
43
|
+
def __getattr__(self, name: str):
|
|
44
|
+
return getattr(get_wallet(), name)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
wallet = _WalletProxy()
|
|
14
48
|
|
|
15
49
|
# Authentication
|
|
16
50
|
api_key_header = APIKeyHeader(name="X-API-Key", auto_error=False)
|
iwa/web/server.py
CHANGED
|
@@ -147,11 +147,28 @@ async def root():
|
|
|
147
147
|
|
|
148
148
|
|
|
149
149
|
def run_server(host: str = "127.0.0.1", port: int = 8000):
|
|
150
|
-
"""Run the web server using uvicorn."""
|
|
150
|
+
"""Run the web server using uvicorn (blocking)."""
|
|
151
151
|
import uvicorn
|
|
152
152
|
|
|
153
153
|
uvicorn.run(app, host=host, port=port)
|
|
154
154
|
|
|
155
155
|
|
|
156
|
+
async def run_server_async(host: str = "127.0.0.1", port: int = 8000):
|
|
157
|
+
"""Run the web server in an async context (non-blocking).
|
|
158
|
+
|
|
159
|
+
Use this to embed the web server in another async application.
|
|
160
|
+
The server runs until cancelled.
|
|
161
|
+
|
|
162
|
+
Args:
|
|
163
|
+
host: Bind address. Default "127.0.0.1" (localhost only for security).
|
|
164
|
+
port: Port number. Default 8000.
|
|
165
|
+
"""
|
|
166
|
+
import uvicorn
|
|
167
|
+
|
|
168
|
+
config = uvicorn.Config(app, host=host, port=port, log_level="warning")
|
|
169
|
+
server = uvicorn.Server(config)
|
|
170
|
+
await server.serve()
|
|
171
|
+
|
|
172
|
+
|
|
156
173
|
if __name__ == "__main__":
|
|
157
174
|
run_server()
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
"""Tests for wallet injection in web dependencies."""
|
|
2
|
+
|
|
3
|
+
from unittest.mock import MagicMock, patch
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class TestWalletInjection:
|
|
7
|
+
"""Tests for get_wallet, set_wallet, and _WalletProxy."""
|
|
8
|
+
|
|
9
|
+
def setup_method(self):
|
|
10
|
+
"""Reset the global wallet state before each test."""
|
|
11
|
+
# Import fresh to reset state
|
|
12
|
+
import iwa.web.dependencies as deps
|
|
13
|
+
|
|
14
|
+
deps._wallet = None
|
|
15
|
+
|
|
16
|
+
def test_get_wallet_lazy_initialization(self):
|
|
17
|
+
"""get_wallet() creates a Wallet on first call if none injected."""
|
|
18
|
+
import iwa.web.dependencies as deps
|
|
19
|
+
|
|
20
|
+
deps._wallet = None
|
|
21
|
+
|
|
22
|
+
with patch.object(deps, "Wallet") as mock_wallet_cls:
|
|
23
|
+
mock_wallet_cls.return_value = MagicMock(name="LazyWallet")
|
|
24
|
+
|
|
25
|
+
result = deps.get_wallet()
|
|
26
|
+
|
|
27
|
+
mock_wallet_cls.assert_called_once()
|
|
28
|
+
assert result == mock_wallet_cls.return_value
|
|
29
|
+
|
|
30
|
+
def test_get_wallet_returns_same_instance(self):
|
|
31
|
+
"""get_wallet() returns the same instance on subsequent calls."""
|
|
32
|
+
import iwa.web.dependencies as deps
|
|
33
|
+
|
|
34
|
+
deps._wallet = None
|
|
35
|
+
|
|
36
|
+
with patch.object(deps, "Wallet") as mock_wallet_cls:
|
|
37
|
+
mock_wallet_cls.return_value = MagicMock(name="SingletonWallet")
|
|
38
|
+
|
|
39
|
+
first = deps.get_wallet()
|
|
40
|
+
second = deps.get_wallet()
|
|
41
|
+
|
|
42
|
+
# Should only create once
|
|
43
|
+
mock_wallet_cls.assert_called_once()
|
|
44
|
+
assert first is second
|
|
45
|
+
|
|
46
|
+
def test_set_wallet_injects_external_wallet(self):
|
|
47
|
+
"""set_wallet() allows injecting an external wallet instance."""
|
|
48
|
+
import iwa.web.dependencies as deps
|
|
49
|
+
|
|
50
|
+
deps._wallet = None
|
|
51
|
+
|
|
52
|
+
external_wallet = MagicMock(name="ExternalWallet")
|
|
53
|
+
deps.set_wallet(external_wallet)
|
|
54
|
+
|
|
55
|
+
result = deps.get_wallet()
|
|
56
|
+
|
|
57
|
+
assert result is external_wallet
|
|
58
|
+
|
|
59
|
+
def test_set_wallet_prevents_lazy_init(self):
|
|
60
|
+
"""set_wallet() prevents Wallet() from being called."""
|
|
61
|
+
import iwa.web.dependencies as deps
|
|
62
|
+
|
|
63
|
+
deps._wallet = None
|
|
64
|
+
|
|
65
|
+
with patch.object(deps, "Wallet") as mock_wallet_cls:
|
|
66
|
+
external_wallet = MagicMock(name="InjectedWallet")
|
|
67
|
+
deps.set_wallet(external_wallet)
|
|
68
|
+
|
|
69
|
+
result = deps.get_wallet()
|
|
70
|
+
|
|
71
|
+
# Wallet() should NOT be called
|
|
72
|
+
mock_wallet_cls.assert_not_called()
|
|
73
|
+
assert result is external_wallet
|
|
74
|
+
|
|
75
|
+
def test_set_wallet_overrides_existing(self):
|
|
76
|
+
"""set_wallet() can override a previously set wallet."""
|
|
77
|
+
import iwa.web.dependencies as deps
|
|
78
|
+
|
|
79
|
+
deps._wallet = None
|
|
80
|
+
|
|
81
|
+
wallet1 = MagicMock(name="Wallet1")
|
|
82
|
+
wallet2 = MagicMock(name="Wallet2")
|
|
83
|
+
|
|
84
|
+
deps.set_wallet(wallet1)
|
|
85
|
+
assert deps.get_wallet() is wallet1
|
|
86
|
+
|
|
87
|
+
deps.set_wallet(wallet2)
|
|
88
|
+
assert deps.get_wallet() is wallet2
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class TestWalletProxy:
|
|
92
|
+
"""Tests for _WalletProxy backwards compatibility."""
|
|
93
|
+
|
|
94
|
+
def setup_method(self):
|
|
95
|
+
"""Reset the global wallet state before each test."""
|
|
96
|
+
import iwa.web.dependencies as deps
|
|
97
|
+
|
|
98
|
+
deps._wallet = None
|
|
99
|
+
|
|
100
|
+
def test_wallet_proxy_forwards_attribute_access(self):
|
|
101
|
+
"""wallet.attribute forwards to get_wallet().attribute."""
|
|
102
|
+
import iwa.web.dependencies as deps
|
|
103
|
+
|
|
104
|
+
deps._wallet = None
|
|
105
|
+
|
|
106
|
+
mock_wallet = MagicMock()
|
|
107
|
+
mock_wallet.balance_service = MagicMock(name="BalanceService")
|
|
108
|
+
deps.set_wallet(mock_wallet)
|
|
109
|
+
|
|
110
|
+
# Access through the proxy
|
|
111
|
+
result = deps.wallet.balance_service
|
|
112
|
+
|
|
113
|
+
assert result is mock_wallet.balance_service
|
|
114
|
+
|
|
115
|
+
def test_wallet_proxy_forwards_method_calls(self):
|
|
116
|
+
"""wallet.method() forwards to get_wallet().method()."""
|
|
117
|
+
import iwa.web.dependencies as deps
|
|
118
|
+
|
|
119
|
+
deps._wallet = None
|
|
120
|
+
|
|
121
|
+
mock_wallet = MagicMock()
|
|
122
|
+
mock_wallet.get_accounts_balances.return_value = ({"0x1": {}}, {"0x1": {}})
|
|
123
|
+
deps.set_wallet(mock_wallet)
|
|
124
|
+
|
|
125
|
+
# Call method through proxy
|
|
126
|
+
result = deps.wallet.get_accounts_balances("gnosis", ["native"])
|
|
127
|
+
|
|
128
|
+
mock_wallet.get_accounts_balances.assert_called_once_with("gnosis", ["native"])
|
|
129
|
+
assert result == ({"0x1": {}}, {"0x1": {}})
|
|
130
|
+
|
|
131
|
+
def test_wallet_proxy_triggers_lazy_init(self):
|
|
132
|
+
"""Accessing wallet.attr when no wallet set triggers lazy init."""
|
|
133
|
+
import iwa.web.dependencies as deps
|
|
134
|
+
|
|
135
|
+
deps._wallet = None
|
|
136
|
+
|
|
137
|
+
with patch.object(deps, "Wallet") as mock_wallet_cls:
|
|
138
|
+
mock_instance = MagicMock()
|
|
139
|
+
mock_instance.key_storage = MagicMock(name="KeyStorage")
|
|
140
|
+
mock_wallet_cls.return_value = mock_instance
|
|
141
|
+
|
|
142
|
+
# Access through proxy should trigger lazy init
|
|
143
|
+
result = deps.wallet.key_storage
|
|
144
|
+
|
|
145
|
+
mock_wallet_cls.assert_called_once()
|
|
146
|
+
assert result is mock_instance.key_storage
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
class TestIntegrationWithRouters:
|
|
150
|
+
"""Integration tests for wallet injection with routers."""
|
|
151
|
+
|
|
152
|
+
def setup_method(self):
|
|
153
|
+
"""Reset the global wallet state before each test."""
|
|
154
|
+
import iwa.web.dependencies as deps
|
|
155
|
+
|
|
156
|
+
deps._wallet = None
|
|
157
|
+
|
|
158
|
+
def test_injected_wallet_used_by_routers(self):
|
|
159
|
+
"""Routers using 'wallet' get the injected instance."""
|
|
160
|
+
import iwa.web.dependencies as deps
|
|
161
|
+
|
|
162
|
+
# Inject a mock wallet BEFORE routers access it
|
|
163
|
+
mock_wallet = MagicMock()
|
|
164
|
+
mock_wallet.key_storage = MagicMock()
|
|
165
|
+
mock_wallet.key_storage.accounts = {}
|
|
166
|
+
deps.set_wallet(mock_wallet)
|
|
167
|
+
|
|
168
|
+
# Simulate what a router does
|
|
169
|
+
from iwa.web.dependencies import wallet
|
|
170
|
+
|
|
171
|
+
# Access through the module-level wallet (proxy)
|
|
172
|
+
accounts = wallet.key_storage.accounts
|
|
173
|
+
|
|
174
|
+
assert accounts == {}
|
|
175
|
+
# Verify it's using our injected wallet
|
|
176
|
+
assert deps.get_wallet() is mock_wallet
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
class TestServerAsync:
|
|
180
|
+
"""Tests for run_server_async function."""
|
|
181
|
+
|
|
182
|
+
def test_run_server_async_source_has_localhost_default(self):
|
|
183
|
+
"""run_server_async defaults to localhost for security (source check)."""
|
|
184
|
+
import pathlib
|
|
185
|
+
|
|
186
|
+
# Read the source file directly to verify default
|
|
187
|
+
server_path = pathlib.Path(__file__).parent.parent / "server.py"
|
|
188
|
+
source = server_path.read_text()
|
|
189
|
+
|
|
190
|
+
# Verify the function signature has localhost default
|
|
191
|
+
assert 'async def run_server_async(host: str = "127.0.0.1"' in source
|
|
192
|
+
|
|
193
|
+
def test_run_server_source_has_async_function(self):
|
|
194
|
+
"""run_server_async function exists in server.py source."""
|
|
195
|
+
import pathlib
|
|
196
|
+
|
|
197
|
+
server_path = pathlib.Path(__file__).parent.parent / "server.py"
|
|
198
|
+
source = server_path.read_text()
|
|
199
|
+
|
|
200
|
+
# Verify the async function exists
|
|
201
|
+
assert "async def run_server_async" in source
|
|
202
|
+
assert "uvicorn.Config" in source
|
|
203
|
+
assert "uvicorn.Server" in source
|
|
204
|
+
assert "await server.serve()" in source
|
|
@@ -78,7 +78,7 @@ iwa/plugins/olas/contracts/mech.py,sha256=dXYtyORc-oiu9ga5PtTquOFkoakb6BLGKvlUst
|
|
|
78
78
|
iwa/plugins/olas/contracts/mech_marketplace.py,sha256=hMADl5MQGvT2wLRKu4vHGe4RrAZVq8Y2M_EvXWWz528,1554
|
|
79
79
|
iwa/plugins/olas/contracts/mech_marketplace_v1.py,sha256=ooF5uw1wxwYsoriGUGGxXxmaD8DtWZtK4TJBCUNTGtI,2501
|
|
80
80
|
iwa/plugins/olas/contracts/service.py,sha256=BDQKeCTCnBNrwKD1a8rrlLytpKG3CAdjr-s0ec-dsFY,8243
|
|
81
|
-
iwa/plugins/olas/contracts/staking.py,sha256=
|
|
81
|
+
iwa/plugins/olas/contracts/staking.py,sha256=Qkr6yS3an-yZe560Dwer_g1wyOL3e1VIAGaeSZLCgfs,23512
|
|
82
82
|
iwa/plugins/olas/contracts/abis/activity_checker.json,sha256=HT0IMbyTLMO71ITBKwoS950rHe772suPP4b8eDAodJ0,2230
|
|
83
83
|
iwa/plugins/olas/contracts/abis/mech.json,sha256=bMMCXInjE_2PTPnc_sIyS_H8pod5Sm_e-xTbKgZppKc,16369
|
|
84
84
|
iwa/plugins/olas/contracts/abis/mech_marketplace.json,sha256=KPnF-H_UATb3wdb_7o6ky_hSp5xwgvckD-QqylsWJLg,32468
|
|
@@ -118,7 +118,7 @@ iwa/plugins/olas/tests/test_service_manager_mech.py,sha256=qG6qu5IPRNypXUsblU2OE
|
|
|
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
120
|
iwa/plugins/olas/tests/test_service_staking.py,sha256=exxWsile_wG_0rz_cGbCPG-_Ubq01Ofl4D_pi0plj5Y,18332
|
|
121
|
-
iwa/plugins/olas/tests/test_staking_integration.py,sha256=
|
|
121
|
+
iwa/plugins/olas/tests/test_staking_integration.py,sha256=q7zLQLrUyhtcnZf6MMymx2cX0Gmqaa7i1mRh1clnyj4,30198
|
|
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
|
|
124
124
|
iwa/plugins/olas/tui/olas_view.py,sha256=dgZjfXCWsRRdHpygHfSOCJZFWZrgrVyieq-iYgDkK3w,37404
|
|
@@ -146,9 +146,9 @@ iwa/tui/tests/test_wallets_refactor.py,sha256=71G3HLbhTtgDy3ffVbYv0MFYRgdYd-NWGB
|
|
|
146
146
|
iwa/tui/tests/test_widgets.py,sha256=C9UgIGeWRaQ459JygFEQx-7hOi9mWrSUDDIMZH1ge50,3994
|
|
147
147
|
iwa/tui/widgets/__init__.py,sha256=UzD6nJbwv9hOtkWl9I7faXm1a-rcu4xFRxrf4KBwwY4,161
|
|
148
148
|
iwa/tui/widgets/base.py,sha256=Z8FigMhsfD76PkFVERqMaotd-xwXfuFZm_8TmCMOsl4,3381
|
|
149
|
-
iwa/web/dependencies.py,sha256=
|
|
149
|
+
iwa/web/dependencies.py,sha256=iTvdCSuETFLeQPtFydi21s1EKA_a80rbA57s-994fMQ,2980
|
|
150
150
|
iwa/web/models.py,sha256=MSD9WPy_Nz_amWgoo2KSDTn4ZLv_AV0o0amuNtSf-68,3035
|
|
151
|
-
iwa/web/server.py,sha256=
|
|
151
|
+
iwa/web/server.py,sha256=BcaLtmyPSsVehUtbWf4fbEGJ4E0bDlY_PzVq479HoZM,5786
|
|
152
152
|
iwa/web/routers/accounts.py,sha256=VhCHrwzRWqZcSW-tTEqFWT5hFl-IYEWpqXeuN8xM3-4,3922
|
|
153
153
|
iwa/web/routers/state.py,sha256=wsBAOIWZeMWjMwLiiWVhuEXHAceI0IIq6CPpmh7SbIc,2469
|
|
154
154
|
iwa/web/routers/swap.py,sha256=8xycAytquR29ELxW3vx428W8s9bI_w_x2kpRhhJ0KXY,22630
|
|
@@ -162,11 +162,12 @@ iwa/web/routers/olas/staking.py,sha256=jktJ2C1Q9X4aC0tWJByN3sHpEXY0EIvr3rr4N0MtX
|
|
|
162
162
|
iwa/web/static/app.js,sha256=VCm9zPJERb9pX6zbFQ_7D47UnztGdibrkZ1dmwhsvdc,114949
|
|
163
163
|
iwa/web/static/index.html,sha256=Qg06MFK__2KxwWTr8hhjX_GwsoN53QsLCTypu4fBR-Q,28516
|
|
164
164
|
iwa/web/static/style.css,sha256=7i6T96pS7gXSLDZfyp_87gRlyB9rpsFWJEHJ-dRY1ug,24371
|
|
165
|
+
iwa/web/tests/test_wallet_injection.py,sha256=YjoQh1FMwroswF6O_XZOwYXzanv-JYElNTDNKxAS77g,6751
|
|
165
166
|
iwa/web/tests/test_web_endpoints.py,sha256=vA25YghHNB23sbmhD4ciesn_f_okSq0tjlkrSiKZ0rs,24007
|
|
166
167
|
iwa/web/tests/test_web_olas.py,sha256=GunKEAzcbzL7FoUGMtEl8wqiqwYwA5lB9sOhfCNj0TA,16312
|
|
167
168
|
iwa/web/tests/test_web_swap.py,sha256=7A4gBJFL01kIXPtW1E1J17SCsVc_0DmUn-R8kKrnnVA,2974
|
|
168
169
|
iwa/web/tests/test_web_swap_coverage.py,sha256=zGNrzlhZ_vWDCvWmLcoUwFgqxnrp_ACbo49AtWBS_Kw,5584
|
|
169
|
-
iwa-0.1.
|
|
170
|
+
iwa-0.1.4.dist-info/licenses/LICENSE,sha256=eIubm_IlBHPYRQlLNZKbBNKhJUUP3JH0A2miZUhAVfI,1078
|
|
170
171
|
tests/legacy_cow.py,sha256=oOkZvIxL70ReEoD9oHQbOD5GpjIr6AGNHcOCgfPlerU,8389
|
|
171
172
|
tests/legacy_safe.py,sha256=AssM2g13E74dNGODu_H0Q0y412lgqsrYnEzI97nm_Ts,2972
|
|
172
173
|
tests/legacy_transaction_retry_logic.py,sha256=D9RqZ7DBu61Xr2djBAodU2p9UE939LL-DnQXswX5iQk,1497
|
|
@@ -224,8 +225,8 @@ tests/test_utils.py,sha256=vkP49rYNI8BRzLpWR3WnKdDr8upeZjZcs7Rx0pjbQMo,1292
|
|
|
224
225
|
tests/test_workers.py,sha256=MInwdkFY5LdmFB3o1odIaSD7AQZb3263hNafO1De5PE,2793
|
|
225
226
|
tools/create_and_stake_service.py,sha256=1xwy_bJQI1j9yIQ968Oc9Db_F6mk1659LuuZntTASDE,3742
|
|
226
227
|
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.
|
|
228
|
+
iwa-0.1.4.dist-info/METADATA,sha256=a0L36sHev8jqTBgB7MfGHSz46nAvG9J3OrkHNE1FpvM,7336
|
|
229
|
+
iwa-0.1.4.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
230
|
+
iwa-0.1.4.dist-info/entry_points.txt,sha256=nwB6kscrfA7M00pYmL2j-sBH6eF6h2ga9IK1BZxdiyQ,241
|
|
231
|
+
iwa-0.1.4.dist-info/top_level.txt,sha256=kedS9cRUbm4JE2wYeabIXilhHjN8KCw0IGbqqqsw0Bs,16
|
|
232
|
+
iwa-0.1.4.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|