ipor-fusion 4.0.0.dev203__tar.gz → 4.0.0.dev210__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (341) hide show
  1. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/.github/workflows/cd.yml +2 -1
  2. ipor_fusion-4.0.0.dev210/.github/workflows/ci.yml +55 -0
  3. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/.github/workflows/python-build.yml +15 -4
  4. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/AGENTS.md +17 -4
  5. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/MIGRATION_4.md +7 -0
  6. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/PKG-INFO +1 -1
  7. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/pyproject.toml +2 -4
  8. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/crosschain/ccip/lane.py +32 -3
  9. ipor_fusion-4.0.0.dev210/tests/_live.py +67 -0
  10. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/conftest.py +11 -13
  11. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_crosschain_lane.py +58 -3
  12. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_readiness_selection.py +2 -1
  13. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_solidity_mirrors.py +3 -2
  14. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_vault_info_onchain_base.py +11 -23
  15. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_vault_info_onchain_base_no_withdraw_manager.py +4 -19
  16. ipor_fusion-4.0.0.dev203/.github/workflows/ci.yml +0 -32
  17. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/.claude-plugin/marketplace.json +0 -0
  18. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/.claude-plugin/plugin.json +0 -0
  19. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/.env.example +0 -0
  20. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/.gemini/settings.json +0 -0
  21. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/.git-blame-ignore-revs +0 -0
  22. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/.github/workflows/commit-lint.yml +0 -0
  23. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/.github/workflows/notify-slack.yml +0 -0
  24. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/.github/workflows/python-publish.yml +0 -0
  25. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/.github/workflows/release.yml +0 -0
  26. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/.github/workflows/report-slack.yml +0 -0
  27. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/.gitignore +0 -0
  28. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/.mcp.json +0 -0
  29. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/.pre-commit-config.yaml +0 -0
  30. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/.python-version +0 -0
  31. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/CHANGELOG.md +0 -0
  32. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/CLAUDE.md +0 -0
  33. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/CODEOWNERS +0 -0
  34. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/LICENSE +0 -0
  35. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/README.md +0 -0
  36. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/build.sh +0 -0
  37. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/examples/CONTRIBUTING.md +0 -0
  38. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/examples/README.md +0 -0
  39. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/examples/advanced_euler_v2_credit_market_base.py +0 -0
  40. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/examples/advanced_euler_v2_looping_arbitrum.py +0 -0
  41. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/examples/composed_hypercore_flow_arbitrum_hyperevm.py +0 -0
  42. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/examples/crosschain_ccip_usdc_arbitrum_hyperevm.py +0 -0
  43. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/examples/external_state_margin_leg_base.py +0 -0
  44. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/examples/simple_aave_v3_supply_base.py +0 -0
  45. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/pytest.ini +0 -0
  46. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/scripts/commit-check.sh +0 -0
  47. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/scripts/crosschain_readiness.py +0 -0
  48. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/skills/ipor-deploy-vault/SKILL.md +0 -0
  49. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/__init__.py +0 -0
  50. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/about.py +0 -0
  51. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/cache.py +0 -0
  52. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/chains.py +0 -0
  53. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/cli/__init__.py +0 -0
  54. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/cli/changelog_cmd.py +0 -0
  55. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/cli/config_cmd.py +0 -0
  56. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/cli/config_store.py +0 -0
  57. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/cli/main.py +0 -0
  58. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/cli/market_cmd.py +0 -0
  59. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/cli/options.py +0 -0
  60. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/cli/oracle_mapping_view.py +0 -0
  61. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/cli/vault_cmd.py +0 -0
  62. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/cli/vault_health.py +0 -0
  63. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/cli/vault_info_view.py +0 -0
  64. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/cli/vault_markets_view.py +0 -0
  65. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/cli/vault_rendering.py +0 -0
  66. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/config/__init__.py +0 -0
  67. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/config/roles.py +0 -0
  68. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/core/__init__.py +0 -0
  69. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/core/access.py +0 -0
  70. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/core/address.py +0 -0
  71. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/core/context.py +0 -0
  72. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/core/contract.py +0 -0
  73. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/core/erc20.py +0 -0
  74. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/core/external_state_executor.py +0 -0
  75. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/core/fee_manager.py +0 -0
  76. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/core/fuse.py +0 -0
  77. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/core/fuse_action.py +0 -0
  78. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/core/fusion_factory.py +0 -0
  79. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/core/logs.py +0 -0
  80. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/core/multicall.py +0 -0
  81. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/core/oracle.py +0 -0
  82. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/core/plasma_vault.py +0 -0
  83. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/core/rewards_manager.py +0 -0
  84. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/core/simulation.py +0 -0
  85. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/core/simulation_result.py +0 -0
  86. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/core/withdraw_manager.py +0 -0
  87. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/crosschain/__init__.py +0 -0
  88. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/crosschain/ccip/__init__.py +0 -0
  89. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/crosschain/ccip/chainlink.py +0 -0
  90. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/crosschain/ccip/codec.py +0 -0
  91. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/crosschain/ccip/contracts.py +0 -0
  92. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/crosschain/ccip/events.py +0 -0
  93. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/crosschain/ccip/indexer.py +0 -0
  94. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/crosschain/ccip/quote.py +0 -0
  95. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/crosschain/ccip/transport.py +0 -0
  96. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/crosschain/contracts.py +0 -0
  97. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/crosschain/discovery.py +0 -0
  98. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/crosschain/errors.py +0 -0
  99. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/crosschain/fuses/__init__.py +0 -0
  100. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/crosschain/fuses/base.py +0 -0
  101. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/crosschain/fuses/ccip.py +0 -0
  102. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/crosschain/fuses/stargate.py +0 -0
  103. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/crosschain/lane.py +0 -0
  104. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/crosschain/logs.py +0 -0
  105. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/crosschain/messages.py +0 -0
  106. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/crosschain/simulation.py +0 -0
  107. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/crosschain/stargate/__init__.py +0 -0
  108. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/crosschain/stargate/contracts.py +0 -0
  109. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/crosschain/stargate/lane.py +0 -0
  110. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/crosschain/stargate/layerzero.py +0 -0
  111. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/crosschain/stargate/transport.py +0 -0
  112. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/crosschain/transport.py +0 -0
  113. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/dependency_graph.py +0 -0
  114. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/entry_cli.py +0 -0
  115. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/entry_mcp.py +0 -0
  116. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/errors.py +0 -0
  117. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/explorer.py +0 -0
  118. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/field_docs.py +0 -0
  119. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/fuses/__init__.py +0 -0
  120. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/fuses/aave_v3.py +0 -0
  121. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/fuses/async_action.py +0 -0
  122. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/fuses/base.py +0 -0
  123. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/fuses/compound_v3.py +0 -0
  124. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/fuses/erc4626.py +0 -0
  125. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/fuses/euler_v2.py +0 -0
  126. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/fuses/events.py +0 -0
  127. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/fuses/external_state.py +0 -0
  128. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/fuses/external_state_event_decoding.py +0 -0
  129. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/fuses/fluid_instadapp.py +0 -0
  130. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/fuses/gearbox_v3.py +0 -0
  131. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/fuses/hypercore.py +0 -0
  132. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/fuses/hypercore_event_decoding.py +0 -0
  133. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/fuses/merkl.py +0 -0
  134. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/fuses/morpho.py +0 -0
  135. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/fuses/ramses_v2.py +0 -0
  136. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/fuses/uniswap_v3.py +0 -0
  137. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/fuses/universal.py +0 -0
  138. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/guide/__init__.py +0 -0
  139. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/guide/architecture.md +0 -0
  140. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/guide/glossary.md +0 -0
  141. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/guide/invariants.md +0 -0
  142. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/guide/quickstart.md +0 -0
  143. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/market_ids.py +0 -0
  144. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/market_info.py +0 -0
  145. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/mcp/__init__.py +0 -0
  146. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/mcp/guide.py +0 -0
  147. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/mcp/models.py +0 -0
  148. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/mcp/server.py +0 -0
  149. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/morpho_api.py +0 -0
  150. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/py.typed +0 -0
  151. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/readers/__init__.py +0 -0
  152. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/readers/aave_v3.py +0 -0
  153. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/readers/aave_v4.py +0 -0
  154. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/readers/compound_v3.py +0 -0
  155. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/readers/hypercore.py +0 -0
  156. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/readers/hypercore_outcome.py +0 -0
  157. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/readers/lending_health.py +0 -0
  158. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/readers/morpho.py +0 -0
  159. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/readers/oracle_mapping.py +0 -0
  160. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/readers/oracle_mapping_nodes.py +0 -0
  161. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/readers/oracle_mapping_reader.py +0 -0
  162. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/readers/position_manager.py +0 -0
  163. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/readers/ramses_v2.py +0 -0
  164. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/readers/uniswap_v3.py +0 -0
  165. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/readers/unpriceable.py +0 -0
  166. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/substrates.py +0 -0
  167. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/types.py +0 -0
  168. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/validation.py +0 -0
  169. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/vault_data.py +0 -0
  170. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/vault_erc20.py +0 -0
  171. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/vault_info.py +0 -0
  172. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/vault_managers.py +0 -0
  173. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/vault_positions.py +0 -0
  174. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/vault_reads.py +0 -0
  175. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/UniswapV3UniversalRouter.py +0 -0
  176. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/_crosschain.py +0 -0
  177. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/_crosschain_lifecycle.py +0 -0
  178. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/_crosschain_recovery.py +0 -0
  179. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/_euler_v2.py +0 -0
  180. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/_event_args.py +0 -0
  181. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/_fake_rpc.py +0 -0
  182. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/_foundry.py +0 -0
  183. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/_golden.py +0 -0
  184. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/_guide.py +0 -0
  185. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/_hypercore_model.py +0 -0
  186. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/_hypercore_shadow.py +0 -0
  187. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/_multicall.py +0 -0
  188. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/_oracle_mapping_fake.py +0 -0
  189. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/_simulate.py +0 -0
  190. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/_validation.py +0 -0
  191. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/addresses.py +0 -0
  192. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/constants.py +0 -0
  193. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/fixtures/hypercore_hip3_flow.json +0 -0
  194. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/fixtures/hypercore_market55.json +0 -0
  195. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/fixtures/market_golden/meta_morpho_v1.json +0 -0
  196. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/fixtures/market_golden/meta_morpho_v1.mcp.json +0 -0
  197. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/fixtures/market_golden/meta_morpho_v2.json +0 -0
  198. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/fixtures/market_golden/meta_morpho_v2.mcp.json +0 -0
  199. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/fixtures/market_golden/morpho_api/market.json +0 -0
  200. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/fixtures/market_golden/morpho_api/vault_v1.json +0 -0
  201. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/fixtures/market_golden/morpho_api/vault_v2.json +0 -0
  202. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/fixtures/market_golden/morpho_api/vault_v2_not_found.json +0 -0
  203. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/fixtures/market_golden/morpho_blue.json +0 -0
  204. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/fixtures/market_golden/morpho_blue.mcp.json +0 -0
  205. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/fixtures/market_golden/morpho_blue_api_error.json +0 -0
  206. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/fixtures/market_golden/morpho_blue_api_error.mcp.json +0 -0
  207. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/fixtures/market_golden/morpho_blue_no_api.json +0 -0
  208. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/fixtures/market_golden/morpho_blue_no_api.mcp.json +0 -0
  209. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/fixtures/oracle_mapping_golden/mapping.json +0 -0
  210. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/fixtures/oracle_mapping_golden/mapping.mcp.json +0 -0
  211. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/fixtures/simulate_v1_request.json +0 -0
  212. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/fixtures/vault_info_golden/clean.json +0 -0
  213. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/fixtures/vault_info_golden/clean.mcp.json +0 -0
  214. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/fixtures/vault_info_golden/clean.txt +0 -0
  215. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/fixtures/vault_info_golden/deployment_error.json +0 -0
  216. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/fixtures/vault_info_golden/deployment_error.mcp.json +0 -0
  217. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/fixtures/vault_info_golden/deployment_error.txt +0 -0
  218. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/fixtures/vault_info_golden/fetched.cli.json +0 -0
  219. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/fixtures/vault_info_golden/fetched.json +0 -0
  220. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/fixtures/vault_info_golden/fetched.mcp.json +0 -0
  221. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/fixtures/vault_info_golden/full.json +0 -0
  222. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/fixtures/vault_info_golden/full.mcp.json +0 -0
  223. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/fixtures/vault_info_golden/full.txt +0 -0
  224. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/fixtures/vault_info_golden/rich.json +0 -0
  225. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/fixtures/vault_info_golden/rich.mcp.json +0 -0
  226. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/fixtures/vault_info_golden/rich.txt +0 -0
  227. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_about.py +0 -0
  228. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_access_manager.py +0 -0
  229. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_cache.py +0 -0
  230. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_chains.py +0 -0
  231. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_cli_changelog.py +0 -0
  232. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_cli_commands.py +0 -0
  233. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_cli_config_store.py +0 -0
  234. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_cli_helpers.py +0 -0
  235. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_cli_hypercore.py +0 -0
  236. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_cli_lending_health.py +0 -0
  237. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_cli_market_golden.py +0 -0
  238. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_cli_market_morpho_blue.py +0 -0
  239. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_cli_oracle_mapping.py +0 -0
  240. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_cli_oracle_mapping_golden.py +0 -0
  241. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_cli_pending_requests_scan.py +0 -0
  242. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_cli_role_accounts.py +0 -0
  243. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_cli_vault_info_golden.py +0 -0
  244. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_cli_vault_predates_getters.py +0 -0
  245. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_cli_vault_validate.py +0 -0
  246. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_contract_call.py +0 -0
  247. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_contract_encoder.py +0 -0
  248. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_crosschain_ccip_events.py +0 -0
  249. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_crosschain_ccip_indexer.py +0 -0
  250. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_crosschain_ccip_quote.py +0 -0
  251. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_crosschain_chainlink.py +0 -0
  252. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_crosschain_contracts.py +0 -0
  253. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_crosschain_encoding.py +0 -0
  254. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_crosschain_errors.py +0 -0
  255. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_crosschain_layering.py +0 -0
  256. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_crosschain_readiness.py +0 -0
  257. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_crosschain_readiness_report.py +0 -0
  258. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_crosschain_transport.py +0 -0
  259. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_docs_typing.py +0 -0
  260. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_erc20.py +0 -0
  261. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_error_hierarchy.py +0 -0
  262. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_examples_composed_hypercore.py +0 -0
  263. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_examples_crosschain.py +0 -0
  264. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_examples_euler_looping_arbitrum.py +0 -0
  265. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_examples_vaults.py +0 -0
  266. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_explorer.py +0 -0
  267. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_external_state_events.py +0 -0
  268. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_external_state_executor.py +0 -0
  269. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_fee_manager.py +0 -0
  270. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_fuse_encoding.py +0 -0
  271. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_fusion_instance.py +0 -0
  272. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_guide.py +0 -0
  273. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_hypercore_deployment.py +0 -0
  274. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_hypercore_events.py +0 -0
  275. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_hypercore_flow.py +0 -0
  276. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_hypercore_live.py +0 -0
  277. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_hypercore_outcome.py +0 -0
  278. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_hypercore_readers.py +0 -0
  279. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_hypercore_simulation.py +0 -0
  280. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_logs.py +0 -0
  281. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_mcp_hypercore.py +0 -0
  282. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_mcp_models.py +0 -0
  283. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_mcp_server.py +0 -0
  284. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_mcp_vault_validate.py +0 -0
  285. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_multicall.py +0 -0
  286. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_oracle.py +0 -0
  287. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_oracle_mapping.py +0 -0
  288. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_plasma_vault.py +0 -0
  289. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_public_api.py +0 -0
  290. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_public_type_reachability.py +0 -0
  291. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_reader_encoding.py +0 -0
  292. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_revert_decoding.py +0 -0
  293. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_rewards_manager.py +0 -0
  294. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_roles.py +0 -0
  295. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_sdk_layering.py +0 -0
  296. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_simulate_borrow_aave_v3_mainnet.py +0 -0
  297. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_simulate_claim_merkl_wrapper_base.py +0 -0
  298. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_simulate_claim_morpho_rewards.py +0 -0
  299. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_simulate_crosschain_canary.py +0 -0
  300. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_simulate_crosschain_hypercore.py +0 -0
  301. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_simulate_crosschain_hyperevm.py +0 -0
  302. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_simulate_crosschain_lifecycle.py +0 -0
  303. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_simulate_euler_v2_batch_base.py +0 -0
  304. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_simulate_euler_v2_credit_market_base.py +0 -0
  305. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_simulate_euler_v2_swap_deploy_base.py +0 -0
  306. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_simulate_euler_v2_swap_reconfigure_base.py +0 -0
  307. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_simulate_euler_v2_swap_registry_base.py +0 -0
  308. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_simulate_examples_arbitrum.py +0 -0
  309. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_simulate_examples_base.py +0 -0
  310. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_simulate_examples_crosschain.py +0 -0
  311. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_simulate_external_state_executor_base.py +0 -0
  312. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_simulate_fusion_factory_clone_base.py +0 -0
  313. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_simulate_looping_aave_v3_base.py +0 -0
  314. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_simulate_looping_morpho_blue_base.py +0 -0
  315. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_simulate_morpho_blue.py +0 -0
  316. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_simulate_pilot_v3.py +0 -0
  317. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_simulate_pilot_v4_uniswap.py +0 -0
  318. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_simulate_pilot_v5_ramses.py +0 -0
  319. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_simulate_pilot_v5_scheduled.py +0 -0
  320. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_simulate_plasma_vault_setters_base.py +0 -0
  321. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_simulate_readers_integration.py +0 -0
  322. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_simulate_release_funds.py +0 -0
  323. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_simulate_swap_on_base.py +0 -0
  324. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_simulate_vault_from_scratch_base.py +0 -0
  325. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_simulation_errors.py +0 -0
  326. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_simulation_observation.py +0 -0
  327. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_solidity_compiler.py +0 -0
  328. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_substrates.py +0 -0
  329. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_types_and_events.py +0 -0
  330. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_unpriceable_tokens.py +0 -0
  331. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_validation.py +0 -0
  332. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_vault_data_block.py +0 -0
  333. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_vault_dep_graph.py +0 -0
  334. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_vault_info_fetched_golden.py +0 -0
  335. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_vault_reconciliation_onchain_base.py +0 -0
  336. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_vault_simulator_blocks.py +0 -0
  337. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_web3_context.py +0 -0
  338. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_withdraw_manager.py +0 -0
  339. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/typed_client/consumer.py +0 -0
  340. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/typed_client/rejected.py +0 -0
  341. {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/uv.lock +0 -0
@@ -12,12 +12,13 @@ jobs:
12
12
  SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
13
13
 
14
14
  build:
15
- needs: notify
16
15
  uses: ./.github/workflows/python-build.yml
17
16
  secrets:
18
17
  ETHEREUM_PROVIDER_URL: ${{ secrets.ETHEREUM_PROVIDER_URL }}
19
18
  ARBITRUM_PROVIDER_URL: ${{ secrets.ARBITRUM_PROVIDER_URL }}
20
19
  BASE_PROVIDER_URL: ${{ secrets.BASE_PROVIDER_URL }}
20
+ with:
21
+ require-live: ETHEREUM_PROVIDER_URL,ARBITRUM_PROVIDER_URL,BASE_PROVIDER_URL,GITHUB
21
22
 
22
23
  report:
23
24
  if: ${{ always() }}
@@ -0,0 +1,55 @@
1
+ name: CI
2
+
3
+ on:
4
+ pull_request:
5
+ branches:
6
+ - "**"
7
+
8
+ jobs:
9
+ notify:
10
+ uses: ./.github/workflows/notify-slack.yml
11
+ secrets:
12
+ SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
13
+
14
+ # No secrets: what the code proves offline, on every supported Python.
15
+ offline:
16
+ strategy:
17
+ fail-fast: false
18
+ matrix:
19
+ python-version: ["3.11", "3.12", "3.13"]
20
+ uses: ./.github/workflows/python-build.yml
21
+ with:
22
+ python-version: ${{ matrix.python-version }}
23
+ format-code-enabled: ${{ matrix.python-version == '3.12' }}
24
+ static-code-analysis-enabled: ${{ matrix.python-version == '3.12' }}
25
+ build-enabled: ${{ matrix.python-version == '3.12' }}
26
+ coverage-artifact-name: coverage-offline-${{ matrix.python-version }}
27
+
28
+ # Live providers, for pull requests from this repository only (a fork gets
29
+ # no secrets); a provider or GitHub outage fails it instead of skipping.
30
+ live:
31
+ if: ${{ github.event.pull_request.head.repo.full_name == github.repository }}
32
+ uses: ./.github/workflows/python-build.yml
33
+ secrets:
34
+ ETHEREUM_PROVIDER_URL: ${{ secrets.ETHEREUM_PROVIDER_URL }}
35
+ ARBITRUM_PROVIDER_URL: ${{ secrets.ARBITRUM_PROVIDER_URL }}
36
+ BASE_PROVIDER_URL: ${{ secrets.BASE_PROVIDER_URL }}
37
+ with:
38
+ format-code-enabled: false
39
+ static-code-analysis-enabled: false
40
+ build-enabled: false
41
+ coverage-artifact-name: coverage-live
42
+ require-live: ETHEREUM_PROVIDER_URL,ARBITRUM_PROVIDER_URL,BASE_PROVIDER_URL,GITHUB
43
+
44
+ report:
45
+ if: ${{ always() }}
46
+ needs:
47
+ - notify
48
+ - offline
49
+ - live
50
+ uses: ./.github/workflows/report-slack.yml
51
+ secrets:
52
+ SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
53
+ with:
54
+ success: ${{ needs.offline.result == 'success' && contains(fromJSON('["success", "skipped"]'), needs.live.result) }}
55
+ slack-status-msg-id: ${{ needs.notify.outputs.slack-status-msg-id }}
@@ -5,11 +5,11 @@ on:
5
5
 
6
6
  secrets:
7
7
  ARBITRUM_PROVIDER_URL:
8
- required: true
8
+ required: false
9
9
  BASE_PROVIDER_URL:
10
- required: true
10
+ required: false
11
11
  ETHEREUM_PROVIDER_URL:
12
- required: true
12
+ required: false
13
13
 
14
14
  inputs:
15
15
  python-version:
@@ -42,6 +42,16 @@ on:
42
42
  type: boolean
43
43
  default: true
44
44
  required: false
45
+ coverage-artifact-name:
46
+ description: "Name of the uploaded coverage artifact (unique per call in a run)"
47
+ type: string
48
+ default: coverage-report
49
+ required: false
50
+ require-live:
51
+ description: "Comma-separated live sources whose tests must run, not skip (tests/_live.py)"
52
+ type: string
53
+ default: ""
54
+ required: false
45
55
 
46
56
  jobs:
47
57
  python:
@@ -93,6 +103,7 @@ jobs:
93
103
  ETHEREUM_PROVIDER_URL: ${{ secrets.ETHEREUM_PROVIDER_URL }}
94
104
  ARBITRUM_PROVIDER_URL: ${{ secrets.ARBITRUM_PROVIDER_URL }}
95
105
  BASE_PROVIDER_URL: ${{ secrets.BASE_PROVIDER_URL }}
106
+ IPOR_FUSION_REQUIRE_LIVE: ${{ inputs.require-live }}
96
107
  id: tests
97
108
 
98
109
  - name: Coverage summary
@@ -104,6 +115,6 @@ jobs:
104
115
  if: inputs.test-enabled && always() && steps.tests.outcome != 'skipped'
105
116
  uses: actions/upload-artifact@v4
106
117
  with:
107
- name: coverage-report
118
+ name: ${{ inputs.coverage-artifact-name }}
108
119
  path: coverage.xml
109
120
 
@@ -27,7 +27,7 @@ uv sync --all-extras --locked # install, same as
27
27
  uv run ruff format ./ # format (CI runs --check)
28
28
  uv run ruff check ./ # lint: bandit S, C901 max-complexity 10, PLR limits
29
29
  uv run pyright # types, basic mode, src + tests + examples + scripts
30
- uv run pytest # all tests + coverage gate (fail_under = 95)
30
+ uv run pytest # all tests + coverage gate (fail_under = 98, mcp/ included)
31
31
  uv run pytest -m "cli or mcp" --no-cov # fast offline subset
32
32
  uv run pytest --run-readiness -m readiness --no-cov -rA # opt-in live Ethereum-hub POC checks
33
33
  uv run python scripts/crosschain_readiness.py --output /tmp/crosschain-readiness.json # live Arbitrum/HyperEVM report
@@ -54,13 +54,26 @@ the sequence in `.github/workflows/python-build.yml`. When ruff `C901` trips,
54
54
  extract helpers, never suppress.
55
55
 
56
56
  **Offline vs network.** `test_cli_*` and `test_mcp_*` need no network
57
- (auto-tagged `cli` / `mcp` in `conftest.py`; everything else is `sdk`).
57
+ (auto-tagged `cli` / `mcp` in `conftest.py`, `test_examples_*` `examples`;
58
+ everything else is `sdk`). Most `sdk` tests are offline too: the suite with no
59
+ provider set still clears the coverage gate.
58
60
  Integration tests (`test_simulate_*`, `test_vault_info_onchain_*`) run against
59
61
  live RPCs via `eth_simulateV1`, no Docker or Anvil (`ANVIL_WALLET` is just a
60
62
  named address constant). They auto-skip unless `ETHEREUM_PROVIDER_URL`,
61
63
  `BASE_PROVIDER_URL` and `ARBITRUM_PROVIDER_URL` are set (`.env` is loaded via
62
- python-dotenv) and the provider supports `eth_simulateV1`. CI has all three as
63
- secrets. Never print `.env` or a provider URL: they embed API keys.
64
+ python-dotenv) and the provider supports `eth_simulateV1`. Every such gate goes
65
+ through `tests/_live.py` (`provider_url`, `archive_context`, `unavailable`): it
66
+ skips locally and fails when the source is named in `IPOR_FUSION_REQUIRE_LIVE`
67
+ (comma-separated env var names, `GITHUB` for the Solidity mirrors). CI
68
+ (`ci.yml`) runs an offline job without secrets on Python 3.11, 3.12 and 3.13
69
+ and, for pull requests from this repository, a live job with the three
70
+ provider secrets that requires all of them and GitHub: a skip for one of those
71
+ sources being unavailable is a failure there (state-dependent skips such as a
72
+ pilot vault's preconditions remain skips, and the offline job still reads the
73
+ public Solidity sources from GitHub). Skip only for an unavailable source; an
74
+ exception from the
75
+ SDK is a test failure, never a skip. Never print `.env` or a provider URL: they
76
+ embed API keys, so skip and fail messages carry the exception type only.
64
77
  `HYPEREVM_PROVIDER_URL` is used by the HyperEVM lifecycle/deployment tests and
65
78
  the readiness script; general PR CI does not supply it.
66
79
  `test_simulate_crosschain_hyperevm.py` rehearses Arbitrum → HyperEVM on pinned blocks,
@@ -950,6 +950,13 @@ and `log_index` was passed through as given (a hex string from a raw log); the e
950
950
  These apply to every caller, type-checked or not. Changes described in full above
951
951
  are listed with a link.
952
952
 
953
+ - **`CcipLane.open` refuses a spoke it cannot reach.** The executor's
954
+ `ccipRoute(spoke)` must be registered and point at the dispatcher at the
955
+ executor's own address; otherwise `open` (and `open_lane` / `open_lanes`)
956
+ raises `CrosschainDiscoveryError`, where 3.x built a lane whose default send
957
+ parameters carried a zero fee cap and gas limit and failed only on chain. A
958
+ registered but disabled route still opens, but `default_send` raises.
959
+
953
960
  - **The reconciliation counts the rewards claim manager.** `PlasmaVault.totalAssets`
954
961
  adds `RewardsClaimManager.balanceOf()` (vested rewards) to the vault's idle
955
962
  underlying and its markets; 3.x left it out, so a vault with vested rewards showed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: ipor-fusion
3
- Version: 4.0.0.dev203
3
+ Version: 4.0.0.dev210
4
4
  Summary: The IPOR Fusion SDK for Python
5
5
  Project-URL: homepage, https://ipor.io
6
6
  Project-URL: repository, https://github.com/IPOR-Labs/ipor-fusion.py
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "ipor-fusion"
3
- version = "4.0.0.dev203"
3
+ version = "4.0.0.dev210"
4
4
  description = "The IPOR Fusion SDK for Python"
5
5
  authors = [
6
6
  { name = "Adam Mydlarz", email = "amydlarz@gmail.com" },
@@ -123,12 +123,10 @@ executionEnvironments = [
123
123
  [tool.coverage.run]
124
124
  source = ["src/ipor_fusion"]
125
125
  data_file = ".coverage_data/.coverage"
126
- omit = ["src/ipor_fusion/mcp/*"]
127
126
 
128
127
  [tool.coverage.report]
129
128
  show_missing = true
130
- fail_under = 95
131
- omit = ["src/ipor_fusion/mcp/*"]
129
+ fail_under = 98
132
130
 
133
131
  [tool.semantic_release]
134
132
  # Runs inside the python-semantic-release Docker container, not on the runner
@@ -22,7 +22,8 @@ from ipor_fusion.crosschain.fuses.ccip import (
22
22
  )
23
23
  from ipor_fusion.crosschain.lane import CrosschainLane, LaneFuses, LaneObservation
24
24
  from ipor_fusion.crosschain.messages import CommandStatus, CrosschainTransportKind
25
- from ipor_fusion.types import Amount, Period, ProposalId
25
+ from ipor_fusion.errors import CrosschainDiscoveryError
26
+ from ipor_fusion.types import Amount, ChainId, Period, ProposalId
26
27
 
27
28
 
28
29
  class CcipLane(CrosschainLane):
@@ -30,7 +31,12 @@ class CcipLane(CrosschainLane):
30
31
  send parameters come from the executor's registered ``route`` (fee
31
32
  ceiling, fee token, token or message gas limit). ``decimal_conversion_rate``
32
33
  is the executor's ``DECIMAL_CONVERSION_RATE`` (local per shared unit), used
33
- to report the dispatcher observation in local decimals."""
34
+ to report the dispatcher observation in local decimals.
35
+
36
+ `open` refuses a spoke the executor has no route to, or whose route names
37
+ another peer than the dispatcher at the executor's address; a registered but
38
+ disabled route opens (state can still be read) and refuses the default send
39
+ parameters."""
34
40
 
35
41
  transport_kind = CrosschainTransportKind.CHAINLINK_CCIP
36
42
  enforces_min_received = False
@@ -77,13 +83,15 @@ class CcipLane(CrosschainLane):
77
83
  fuses: LaneFuses,
78
84
  ) -> CcipLane:
79
85
  hub_executor = CcipCrosschainExecutor(executor)
86
+ route = hub_executor.ccip_route(spoke_ctx.chain_id).call(hub_ctx)
87
+ _require_route_to_dispatcher(route, hub_executor.address, spoke_ctx.chain_id)
80
88
  return cls(
81
89
  hub_ctx=hub_ctx,
82
90
  spoke_ctx=spoke_ctx,
83
91
  executor=hub_executor,
84
92
  dispatcher=CcipCrosschainDispatcher(executor),
85
93
  fuses=fuses,
86
- route=hub_executor.ccip_route(spoke_ctx.chain_id).call(hub_ctx),
94
+ route=route,
87
95
  decimal_conversion_rate=hub_executor.decimal_conversion_rate().call(
88
96
  hub_ctx
89
97
  ),
@@ -93,6 +101,11 @@ class CcipLane(CrosschainLane):
93
101
  return self.executor.balance_staleness_max()
94
102
 
95
103
  def default_send(self, *, token: bool) -> CcipSendParams:
104
+ if not self.route.enabled:
105
+ raise CrosschainDiscoveryError(
106
+ f"CCIP route of executor {self.executor.address} to chain "
107
+ f"{self.spoke_chain_id} is disabled"
108
+ )
96
109
  return CcipSendParams.from_route(self.route, token=token)
97
110
 
98
111
  def default_command_send(self) -> CcipSendParams:
@@ -156,3 +169,19 @@ def _to_lane(observation: CcipObservation, rate: int) -> LaneObservation:
156
169
  command_config_epoch=observation.command_config_epoch,
157
170
  tracked_position_set_hash=observation.tracked_position_set_hash,
158
171
  )
172
+
173
+
174
+ def _require_route_to_dispatcher(
175
+ route: CcipRouteConfig, executor: ChecksumAddress, spoke: ChainId
176
+ ) -> None:
177
+ """The executor sends to the dispatcher at its own address on the spoke; an
178
+ unregistered route reads back as all zeros."""
179
+ if route.chain_selector == 0 or int(route.peer, 16) == 0:
180
+ raise CrosschainDiscoveryError(
181
+ f"CCIP executor {executor} has no route to chain {spoke}"
182
+ )
183
+ if route.peer != executor:
184
+ raise CrosschainDiscoveryError(
185
+ f"CCIP executor {executor} routes chain {spoke} to {route.peer}, "
186
+ "not to the dispatcher at its own address"
187
+ )
@@ -0,0 +1,67 @@
1
+ """Gates of the tests that need a live provider or network source.
2
+
3
+ Locally a missing or unreachable source skips the test. A run that names the
4
+ source in ``IPOR_FUSION_REQUIRE_LIVE`` (comma-separated: provider env var names
5
+ such as ``BASE_PROVIDER_URL``, or ``GITHUB`` for the Solidity mirrors) fails
6
+ instead, so a green CI run means the live checks ran rather than skipped.
7
+ Messages carry the exception type only: provider URLs embed API keys.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import os
13
+ from typing import NoReturn
14
+
15
+ import pytest
16
+ import requests
17
+ from web3 import Web3
18
+ from web3.exceptions import Web3Exception
19
+
20
+ from ipor_fusion import Web3Context
21
+
22
+
23
+ def required() -> frozenset[str]:
24
+ return frozenset(
25
+ name.strip()
26
+ for name in os.environ.get("IPOR_FUSION_REQUIRE_LIVE", "").split(",")
27
+ if name.strip()
28
+ )
29
+
30
+
31
+ def unavailable(source: str, reason: str) -> NoReturn:
32
+ """Skip, or fail when this run requires ``source``."""
33
+ if source in required():
34
+ pytest.fail(f"{source} is required in this run: {reason}", pytrace=False)
35
+ pytest.skip(reason)
36
+
37
+
38
+ def provider_url(env_var: str) -> str:
39
+ url = os.environ.get(env_var)
40
+ if not url:
41
+ unavailable(env_var, f"{env_var} not set")
42
+ return url
43
+
44
+
45
+ # A provider that cannot be reached, refuses the key, or has pruned the state.
46
+ _INFRA_ERRORS = (Web3Exception, requests.RequestException, OSError)
47
+
48
+
49
+ def archive_context(env_var: str, block: int, contract: str) -> Web3Context:
50
+ """A context pinned to ``block`` on the provider in ``env_var``, once it
51
+ has connected and served ``contract``'s code at that block (historical
52
+ state, not just the header); later SDK errors are test failures, not
53
+ infrastructure."""
54
+ url = provider_url(env_var)
55
+ # The failure is reported outside the except block: a chained traceback
56
+ # would print the request URL, which embeds the API key.
57
+ failure: str | None = None
58
+ try:
59
+ ctx = Web3Context.from_url(url)
60
+ code = ctx.web3.eth.get_code(Web3.to_checksum_address(contract), block)
61
+ except _INFRA_ERRORS as exc:
62
+ failure = type(exc).__name__
63
+ if failure is not None:
64
+ unavailable(env_var, f"{env_var} cannot serve state at {block}: {failure}")
65
+ if not code:
66
+ unavailable(env_var, f"{env_var} returned no code for {contract} at {block}")
67
+ return ctx.at_block(block)
@@ -1,11 +1,11 @@
1
1
  import importlib.util
2
- import os
3
2
  from collections.abc import Callable
4
3
  from pathlib import Path
5
4
  from types import ModuleType
6
5
  from unittest.mock import patch
7
6
 
8
7
  import pytest
8
+ from _live import provider_url, unavailable
9
9
  from _multicall import SequentialMulticall
10
10
  from dotenv import load_dotenv
11
11
  from web3 import Web3
@@ -72,40 +72,38 @@ def pytest_collection_modifyitems(config, items):
72
72
 
73
73
 
74
74
  def _connected_web3(env_var: str) -> Web3:
75
- """Build a Web3 client from `env_var`; skip the test if missing/unreachable."""
76
- url = os.environ.get(env_var)
77
- if not url:
78
- pytest.skip(f"{env_var} not set")
79
- w3 = Web3(Web3.HTTPProvider(url))
75
+ """Build a Web3 client from `env_var`; skip the test if missing/unreachable
76
+ (fail when the run requires it, see `_live`)."""
77
+ w3 = Web3(Web3.HTTPProvider(provider_url(env_var)))
80
78
  if not w3.is_connected():
81
- pytest.skip(f"cannot reach RPC at {env_var}")
79
+ unavailable(env_var, f"cannot reach RPC at {env_var}")
82
80
  return w3
83
81
 
84
82
 
85
- def _ensure_simulate_v1(web3: Web3) -> None:
83
+ def _ensure_simulate_v1(web3: Web3, env_var: str) -> None:
86
84
  """Skip if the provider does not implement `eth_simulateV1`."""
87
85
  if not is_simulate_v1_supported(web3):
88
- pytest.skip("provider does not support eth_simulateV1")
86
+ unavailable(env_var, f"{env_var} does not support eth_simulateV1")
89
87
 
90
88
 
91
89
  @pytest.fixture(scope="session")
92
90
  def web3_eth() -> Web3:
93
91
  w3 = _connected_web3("ETHEREUM_PROVIDER_URL")
94
- _ensure_simulate_v1(w3)
92
+ _ensure_simulate_v1(w3, "ETHEREUM_PROVIDER_URL")
95
93
  return w3
96
94
 
97
95
 
98
96
  @pytest.fixture(scope="session")
99
97
  def web3_base() -> Web3:
100
98
  w3 = _connected_web3("BASE_PROVIDER_URL")
101
- _ensure_simulate_v1(w3)
99
+ _ensure_simulate_v1(w3, "BASE_PROVIDER_URL")
102
100
  return w3
103
101
 
104
102
 
105
103
  @pytest.fixture(scope="session")
106
104
  def web3_arb() -> Web3:
107
105
  w3 = _connected_web3("ARBITRUM_PROVIDER_URL")
108
- _ensure_simulate_v1(w3)
106
+ _ensure_simulate_v1(w3, "ARBITRUM_PROVIDER_URL")
109
107
  return w3
110
108
 
111
109
 
@@ -113,5 +111,5 @@ def web3_arb() -> Web3:
113
111
  def web3_hyperevm() -> Web3:
114
112
  """HyperEVM simulation fixture; general PR CI does not supply its RPC secret."""
115
113
  w3 = _connected_web3("HYPEREVM_PROVIDER_URL")
116
- _ensure_simulate_v1(w3)
114
+ _ensure_simulate_v1(w3, "HYPEREVM_PROVIDER_URL")
117
115
  return w3
@@ -3,7 +3,7 @@ the per-transport fuses encode, normalizes reads, and finds its own parts."""
3
3
 
4
4
  from __future__ import annotations
5
5
 
6
- from dataclasses import astuple
6
+ from dataclasses import astuple, replace
7
7
  from unittest.mock import MagicMock
8
8
 
9
9
  import pytest
@@ -662,7 +662,8 @@ def _deployment_ctx() -> MagicMock:
662
662
  probe_vault = PlasmaVault(VAULT)
663
663
  probe_exec = StargateCrosschainExecutor(EXECUTOR)
664
664
  route_raw = encode(
665
- ["(uint64,address,address,uint96,uint96,uint256,bool)"], [ROUTE.as_tuple()]
665
+ ["(uint64,address,address,uint96,uint96,uint256,bool)"],
666
+ [replace(ROUTE, peer=CCIP_EXECUTOR).as_tuple()],
666
667
  )
667
668
  by_selector: dict[tuple[str, bytes], bytes] = {
668
669
  (VAULT, _sel(probe_vault.get_market_substrates(MARKET))): encode(
@@ -845,7 +846,10 @@ class TestDeploymentDiscovery:
845
846
  CCIP_EXECUTOR,
846
847
  ]
847
848
  ccip = next(lane for lane in lanes if isinstance(lane, CcipLane))
848
- assert ccip.route == ROUTE and ccip.fuses == CCIP_FUSES
849
+ assert (
850
+ ccip.route == replace(ROUTE, peer=CCIP_EXECUTOR)
851
+ and ccip.fuses == CCIP_FUSES
852
+ )
849
853
  assert ccip.decimal_conversion_rate == DECIMAL_CONVERSION_RATE
850
854
 
851
855
 
@@ -1036,6 +1040,57 @@ class TestContextOwnership:
1036
1040
  assert observation.accounted_balance == 7
1037
1041
  hub.call.assert_not_called()
1038
1042
 
1043
+ def _ccip_hub(self, route: CcipRouteConfig) -> MagicMock:
1044
+ return _ctx_answering(
1045
+ {
1046
+ selector("ccipRoute(uint256)"): encode(
1047
+ ["(uint64,address,address,uint96,uint96,uint256,bool)"],
1048
+ [route.as_tuple()],
1049
+ ),
1050
+ selector("DECIMAL_CONVERSION_RATE()"): encode(
1051
+ ["uint256"], [DECIMAL_CONVERSION_RATE]
1052
+ ),
1053
+ }
1054
+ )
1055
+
1056
+ def test_ccip_open_refuses_a_spoke_without_a_route(self):
1057
+ unregistered = CcipRouteConfig(
1058
+ chain_selector=0,
1059
+ peer=ZERO_ADDRESS, # type: ignore[arg-type]
1060
+ fee_token=ZERO_ADDRESS, # type: ignore[arg-type]
1061
+ message_gas_limit=0,
1062
+ token_gas_limit=0,
1063
+ max_fee=0,
1064
+ enabled=False,
1065
+ )
1066
+ with pytest.raises(CrosschainDiscoveryError, match="has no route to chain"):
1067
+ CcipLane.open(
1068
+ self._ccip_hub(unregistered),
1069
+ self._wrong_chain(SPOKE),
1070
+ executor=EXECUTOR,
1071
+ fuses=FUSES,
1072
+ )
1073
+
1074
+ def test_ccip_open_refuses_a_route_to_another_peer(self):
1075
+ foreign = replace(ROUTE, peer=REMOTE_VAULT)
1076
+ with pytest.raises(CrosschainDiscoveryError, match="not to the dispatcher"):
1077
+ CcipLane.open(
1078
+ self._ccip_hub(foreign),
1079
+ self._wrong_chain(SPOKE),
1080
+ executor=EXECUTOR,
1081
+ fuses=FUSES,
1082
+ )
1083
+
1084
+ def test_a_disabled_route_opens_but_refuses_default_send(self):
1085
+ lane = CcipLane.open(
1086
+ self._ccip_hub(replace(ROUTE, enabled=False)),
1087
+ self._wrong_chain(SPOKE),
1088
+ executor=EXECUTOR,
1089
+ fuses=FUSES,
1090
+ )
1091
+ with pytest.raises(CrosschainDiscoveryError, match="is disabled"):
1092
+ lane.default_send(token=True)
1093
+
1039
1094
  def test_ccip_open_reads_the_route_on_the_hub(self):
1040
1095
  route_raw = encode(
1041
1096
  ["(uint64,address,address,uint96,uint96,uint256,bool)"], [ROUTE.as_tuple()]
@@ -22,7 +22,8 @@ pytest_plugins = ["pytester"]
22
22
  def test_readiness_requires_explicit_opt_in(pytester, args, passed, deselected):
23
23
  pytester.makeconftest(Path(__file__).with_name("conftest.py").read_text())
24
24
  pytester.makepyfile(
25
- _multicall=Path(__file__).with_name("_multicall.py").read_text()
25
+ _multicall=Path(__file__).with_name("_multicall.py").read_text(),
26
+ _live=Path(__file__).with_name("_live.py").read_text(),
26
27
  )
27
28
  pytester.makeini("[pytest]\nmarkers =\n sdk\n cli\n mcp\n readiness\n")
28
29
  pytester.makepyfile(
@@ -14,6 +14,7 @@ from functools import cache
14
14
 
15
15
  import pytest
16
16
  import requests
17
+ from _live import unavailable
17
18
  from eth_utils import function_signature_to_4byte_selector
18
19
 
19
20
  from ipor_fusion.config.roles import Roles
@@ -58,11 +59,11 @@ def _solidity_source(path: str) -> str:
58
59
  try:
59
60
  resp = requests.get(url, timeout=15)
60
61
  except requests.RequestException as exc:
61
- pytest.skip(f"cannot fetch {path} from GitHub: {exc}")
62
+ unavailable("GITHUB", f"cannot fetch {path} from GitHub: {type(exc).__name__}")
62
63
  if resp.status_code == 404:
63
64
  pytest.fail(f"{url} not found — wrong pinned ref or moved file")
64
65
  if resp.status_code != 200:
65
- pytest.skip(f"GitHub returned HTTP {resp.status_code} for {path}")
66
+ unavailable("GITHUB", f"GitHub returned HTTP {resp.status_code} for {path}")
66
67
  return resp.text
67
68
 
68
69
 
@@ -17,11 +17,10 @@ historical block requires an archive node.
17
17
 
18
18
  from __future__ import annotations
19
19
 
20
- import os
21
20
  from collections import Counter
22
21
  from unittest.mock import patch
23
22
 
24
- import pytest
23
+ from _live import archive_context, provider_url
25
24
  from web3 import Web3
26
25
  from web3.providers.rpc import HTTPProvider
27
26
 
@@ -35,15 +34,8 @@ EXPECTED_IDLE_USDC = 10 * 10**6 # 10 USDC (6 decimals) sitting idle on the vaul
35
34
 
36
35
 
37
36
  def test_idle_underlying_reconciles_on_real_base_vault():
38
- url = os.environ.get("BASE_PROVIDER_URL")
39
- if not url:
40
- pytest.skip("BASE_PROVIDER_URL not set")
41
-
42
- try: # pylint: disable=broad-except
43
- validation = validate_vault(Web3Context.from_url(url), VAULT, block=BLOCK)
44
- except Exception as exc: # pylint: disable=broad-except
45
- # Infra problem (provider unreachable or non-archive) — skip, don't fail.
46
- pytest.skip(f"BASE on-chain read failed (provider missing/non-archive?): {exc}")
37
+ ctx = archive_context("BASE_PROVIDER_URL", BLOCK, VAULT)
38
+ validation = validate_vault(ctx, VAULT, block=BLOCK)
47
39
 
48
40
  # Sanity: confirm we are reading the expected vault/chain state at this block.
49
41
  recon = validation.reconciliation
@@ -64,9 +56,8 @@ MAX_ETH_CALLS = 20
64
56
 
65
57
 
66
58
  def test_fetch_stays_within_round_trip_budget():
67
- url = os.environ.get("BASE_PROVIDER_URL")
68
- if not url:
69
- pytest.skip("BASE_PROVIDER_URL not set")
59
+ archive_context("BASE_PROVIDER_URL", BLOCK, VAULT) # skips without an archive
60
+ url = provider_url("BASE_PROVIDER_URL")
70
61
 
71
62
  methods: Counter[str] = Counter()
72
63
  make_request = HTTPProvider.make_request
@@ -75,15 +66,12 @@ def test_fetch_stays_within_round_trip_budget():
75
66
  methods[method] += 1
76
67
  return make_request(provider, method, params)
77
68
 
78
- try: # pylint: disable=broad-except
79
- # web3 binds make_request when the provider is built, so the context
80
- # must be created under the patch for any request to be counted.
81
- with patch.object(HTTPProvider, "make_request", counting):
82
- ctx = Web3Context.from_url(url)
83
- methods.clear()
84
- data = fetch_vault_data(ctx, VAULT, block=BLOCK)
85
- except Exception as exc: # pylint: disable=broad-except
86
- pytest.skip(f"BASE on-chain read failed (provider missing/non-archive?): {exc}")
69
+ # web3 binds make_request when the provider is built, so the context
70
+ # must be created under the patch for any request to be counted.
71
+ with patch.object(HTTPProvider, "make_request", counting):
72
+ ctx = Web3Context.from_url(url)
73
+ methods.clear()
74
+ data = fetch_vault_data(ctx, VAULT, block=BLOCK)
87
75
 
88
76
  assert data.total_assets == EXPECTED_IDLE_USDC
89
77
  assert methods["eth_call"] > 0, "requests were not counted"
@@ -16,12 +16,10 @@ code under test runs unguarded, so a regression fails instead of skipping.
16
16
 
17
17
  from __future__ import annotations
18
18
 
19
- import os
20
-
21
- import pytest
19
+ from _live import archive_context
22
20
  from web3 import Web3
23
21
 
24
- from ipor_fusion import PlasmaVault, Web3Context
22
+ from ipor_fusion import PlasmaVault
25
23
  from ipor_fusion.vault_data import fetch_vault_data
26
24
 
27
25
  VAULT = Web3.to_checksum_address("0x45aa96f0b3188D47a1DaFdbefCE1db6B37f58216")
@@ -30,22 +28,9 @@ CHAIN_ID = 8453
30
28
  BLOCK = 50788912
31
29
 
32
30
 
33
- def _connect(url: str) -> tuple[Web3Context, PlasmaVault]:
34
- """Skip on infra problems only (unreachable or non-archive provider)."""
35
- try:
36
- ctx = Web3Context.from_url(url).at_block(BLOCK)
37
- pv = PlasmaVault(VAULT)
38
- pv.underlying_asset_address().call(ctx) # state read at BLOCK needs archive
39
- except Exception as exc: # noqa: BLE001
40
- pytest.skip(f"BASE on-chain read failed (provider missing/non-archive?): {exc}")
41
- return ctx, pv
42
-
43
-
44
31
  def test_vault_without_withdraw_manager_fetches_on_real_base_vault():
45
- url = os.environ.get("BASE_PROVIDER_URL")
46
- if not url:
47
- pytest.skip("BASE_PROVIDER_URL not set")
48
- ctx, pv = _connect(url)
32
+ ctx = archive_context("BASE_PROVIDER_URL", BLOCK, VAULT)
33
+ pv = PlasmaVault(VAULT)
49
34
 
50
35
  withdraw_manager = pv.read_withdraw_manager_address(ctx)
51
36
  data = fetch_vault_data(ctx, pv.address, block=BLOCK)