ipor-fusion 4.0.0.dev222__tar.gz → 4.0.0.dev231__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.dev222 → ipor_fusion-4.0.0.dev231}/AGENTS.md +5 -0
  2. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/MIGRATION_4.md +25 -0
  3. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/PKG-INFO +1 -1
  4. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/pyproject.toml +1 -1
  5. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/__init__.py +6 -0
  6. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/cli/vault_markets_view.py +11 -0
  7. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/mcp/models.py +25 -0
  8. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/readers/__init__.py +4 -0
  9. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/readers/lending_health.py +104 -24
  10. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/readers/unpriceable.py +33 -7
  11. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/types.py +1 -0
  12. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/validation.py +40 -1
  13. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/vault_data.py +27 -3
  14. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/vault_info.py +21 -0
  15. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/vault_positions.py +10 -2
  16. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/fixtures/vault_info_golden/full.json +1 -0
  17. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/fixtures/vault_info_golden/full.mcp.json +1 -0
  18. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/fixtures/vault_info_golden/rich.json +1 -0
  19. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/fixtures/vault_info_golden/rich.mcp.json +1 -0
  20. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_cli_helpers.py +27 -3
  21. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_cli_lending_health.py +49 -26
  22. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_mcp_models.py +9 -0
  23. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_public_api.py +6 -0
  24. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_unpriceable_tokens.py +11 -2
  25. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_validation.py +145 -2
  26. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_vault_info_fetched_golden.py +1 -0
  27. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/.claude-plugin/marketplace.json +0 -0
  28. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/.claude-plugin/plugin.json +0 -0
  29. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/.env.example +0 -0
  30. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/.gemini/settings.json +0 -0
  31. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/.git-blame-ignore-revs +0 -0
  32. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/.github/workflows/cd.yml +0 -0
  33. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/.github/workflows/ci.yml +0 -0
  34. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/.github/workflows/commit-lint.yml +0 -0
  35. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/.github/workflows/notify-slack.yml +0 -0
  36. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/.github/workflows/python-build.yml +0 -0
  37. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/.github/workflows/python-publish.yml +0 -0
  38. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/.github/workflows/release.yml +0 -0
  39. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/.github/workflows/report-slack.yml +0 -0
  40. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/.gitignore +0 -0
  41. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/.mcp.json +0 -0
  42. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/.pre-commit-config.yaml +0 -0
  43. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/.python-version +0 -0
  44. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/CHANGELOG.md +0 -0
  45. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/CLAUDE.md +0 -0
  46. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/CODEOWNERS +0 -0
  47. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/LICENSE +0 -0
  48. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/README.md +0 -0
  49. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/build.sh +0 -0
  50. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/examples/CONTRIBUTING.md +0 -0
  51. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/examples/README.md +0 -0
  52. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/examples/advanced_euler_v2_credit_market_base.py +0 -0
  53. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/examples/advanced_euler_v2_looping_arbitrum.py +0 -0
  54. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/examples/composed_hypercore_flow_arbitrum_hyperevm.py +0 -0
  55. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/examples/crosschain_ccip_usdc_arbitrum_hyperevm.py +0 -0
  56. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/examples/external_state_margin_leg_base.py +0 -0
  57. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/examples/simple_aave_v3_supply_base.py +0 -0
  58. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/pytest.ini +0 -0
  59. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/scripts/commit-check.sh +0 -0
  60. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/scripts/crosschain_readiness.py +0 -0
  61. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/skills/ipor-deploy-vault/SKILL.md +0 -0
  62. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/about.py +0 -0
  63. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/cache.py +0 -0
  64. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/chains.py +0 -0
  65. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/cli/__init__.py +0 -0
  66. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/cli/changelog_cmd.py +0 -0
  67. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/cli/config_cmd.py +0 -0
  68. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/cli/config_store.py +0 -0
  69. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/cli/main.py +0 -0
  70. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/cli/market_cmd.py +0 -0
  71. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/cli/options.py +0 -0
  72. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/cli/oracle_mapping_view.py +0 -0
  73. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/cli/vault_cmd.py +0 -0
  74. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/cli/vault_health.py +0 -0
  75. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/cli/vault_info_view.py +0 -0
  76. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/cli/vault_rendering.py +0 -0
  77. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/config/__init__.py +0 -0
  78. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/config/roles.py +0 -0
  79. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/core/__init__.py +0 -0
  80. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/core/access.py +0 -0
  81. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/core/address.py +0 -0
  82. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/core/context.py +0 -0
  83. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/core/contract.py +0 -0
  84. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/core/erc20.py +0 -0
  85. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/core/external_state_executor.py +0 -0
  86. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/core/fee_manager.py +0 -0
  87. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/core/fuse.py +0 -0
  88. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/core/fuse_action.py +0 -0
  89. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/core/fusion_factory.py +0 -0
  90. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/core/logs.py +0 -0
  91. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/core/multicall.py +0 -0
  92. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/core/oracle.py +0 -0
  93. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/core/plasma_vault.py +0 -0
  94. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/core/rewards_manager.py +0 -0
  95. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/core/simulation.py +0 -0
  96. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/core/simulation_result.py +0 -0
  97. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/core/withdraw_manager.py +0 -0
  98. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/crosschain/__init__.py +0 -0
  99. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/crosschain/ccip/__init__.py +0 -0
  100. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/crosschain/ccip/chainlink.py +0 -0
  101. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/crosschain/ccip/codec.py +0 -0
  102. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/crosschain/ccip/contracts.py +0 -0
  103. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/crosschain/ccip/events.py +0 -0
  104. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/crosschain/ccip/indexer.py +0 -0
  105. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/crosschain/ccip/lane.py +0 -0
  106. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/crosschain/ccip/quote.py +0 -0
  107. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/crosschain/ccip/transport.py +0 -0
  108. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/crosschain/contracts.py +0 -0
  109. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/crosschain/discovery.py +0 -0
  110. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/crosschain/errors.py +0 -0
  111. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/crosschain/fuses/__init__.py +0 -0
  112. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/crosschain/fuses/base.py +0 -0
  113. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/crosschain/fuses/ccip.py +0 -0
  114. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/crosschain/fuses/stargate.py +0 -0
  115. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/crosschain/lane.py +0 -0
  116. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/crosschain/logs.py +0 -0
  117. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/crosschain/messages.py +0 -0
  118. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/crosschain/simulation.py +0 -0
  119. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/crosschain/stargate/__init__.py +0 -0
  120. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/crosschain/stargate/contracts.py +0 -0
  121. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/crosschain/stargate/lane.py +0 -0
  122. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/crosschain/stargate/layerzero.py +0 -0
  123. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/crosschain/stargate/transport.py +0 -0
  124. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/crosschain/transport.py +0 -0
  125. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/dependency_graph.py +0 -0
  126. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/entry_cli.py +0 -0
  127. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/entry_mcp.py +0 -0
  128. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/errors.py +0 -0
  129. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/explorer.py +0 -0
  130. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/field_docs.py +0 -0
  131. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/fuses/__init__.py +0 -0
  132. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/fuses/aave_v3.py +0 -0
  133. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/fuses/async_action.py +0 -0
  134. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/fuses/base.py +0 -0
  135. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/fuses/compound_v3.py +0 -0
  136. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/fuses/erc4626.py +0 -0
  137. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/fuses/euler_v2.py +0 -0
  138. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/fuses/events.py +0 -0
  139. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/fuses/external_state.py +0 -0
  140. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/fuses/external_state_event_decoding.py +0 -0
  141. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/fuses/fluid_instadapp.py +0 -0
  142. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/fuses/gearbox_v3.py +0 -0
  143. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/fuses/hypercore.py +0 -0
  144. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/fuses/hypercore_event_decoding.py +0 -0
  145. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/fuses/merkl.py +0 -0
  146. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/fuses/morpho.py +0 -0
  147. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/fuses/ramses_v2.py +0 -0
  148. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/fuses/uniswap_v3.py +0 -0
  149. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/fuses/universal.py +0 -0
  150. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/guide/__init__.py +0 -0
  151. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/guide/architecture.md +0 -0
  152. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/guide/glossary.md +0 -0
  153. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/guide/invariants.md +0 -0
  154. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/guide/quickstart.md +0 -0
  155. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/hypercore/__init__.py +0 -0
  156. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/market_ids.py +0 -0
  157. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/market_info.py +0 -0
  158. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/mcp/__init__.py +0 -0
  159. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/mcp/guide.py +0 -0
  160. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/mcp/server.py +0 -0
  161. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/morpho_api.py +0 -0
  162. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/py.typed +0 -0
  163. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/readers/aave_v3.py +0 -0
  164. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/readers/aave_v4.py +0 -0
  165. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/readers/compound_v3.py +0 -0
  166. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/readers/hypercore.py +0 -0
  167. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/readers/hypercore_outcome.py +0 -0
  168. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/readers/morpho.py +0 -0
  169. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/readers/oracle_mapping.py +0 -0
  170. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/readers/oracle_mapping_nodes.py +0 -0
  171. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/readers/oracle_mapping_reader.py +0 -0
  172. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/readers/position_manager.py +0 -0
  173. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/readers/ramses_v2.py +0 -0
  174. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/readers/uniswap_v3.py +0 -0
  175. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/substrates.py +0 -0
  176. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/vault_erc20.py +0 -0
  177. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/vault_managers.py +0 -0
  178. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/vault_reads.py +0 -0
  179. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/UniswapV3UniversalRouter.py +0 -0
  180. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/_crosschain.py +0 -0
  181. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/_crosschain_lifecycle.py +0 -0
  182. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/_crosschain_recovery.py +0 -0
  183. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/_euler_v2.py +0 -0
  184. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/_event_args.py +0 -0
  185. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/_fake_rpc.py +0 -0
  186. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/_foundry.py +0 -0
  187. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/_golden.py +0 -0
  188. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/_guide.py +0 -0
  189. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/_hypercore_model.py +0 -0
  190. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/_hypercore_shadow.py +0 -0
  191. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/_live.py +0 -0
  192. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/_multicall.py +0 -0
  193. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/_oracle_mapping_fake.py +0 -0
  194. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/_simulate.py +0 -0
  195. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/_validation.py +0 -0
  196. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/addresses.py +0 -0
  197. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/conftest.py +0 -0
  198. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/constants.py +0 -0
  199. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/fixtures/hypercore_hip3_flow.json +0 -0
  200. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/fixtures/hypercore_market55.json +0 -0
  201. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/fixtures/market_golden/meta_morpho_v1.json +0 -0
  202. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/fixtures/market_golden/meta_morpho_v1.mcp.json +0 -0
  203. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/fixtures/market_golden/meta_morpho_v2.json +0 -0
  204. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/fixtures/market_golden/meta_morpho_v2.mcp.json +0 -0
  205. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/fixtures/market_golden/morpho_api/market.json +0 -0
  206. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/fixtures/market_golden/morpho_api/vault_v1.json +0 -0
  207. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/fixtures/market_golden/morpho_api/vault_v2.json +0 -0
  208. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/fixtures/market_golden/morpho_api/vault_v2_not_found.json +0 -0
  209. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/fixtures/market_golden/morpho_blue.json +0 -0
  210. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/fixtures/market_golden/morpho_blue.mcp.json +0 -0
  211. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/fixtures/market_golden/morpho_blue_api_error.json +0 -0
  212. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/fixtures/market_golden/morpho_blue_api_error.mcp.json +0 -0
  213. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/fixtures/market_golden/morpho_blue_no_api.json +0 -0
  214. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/fixtures/market_golden/morpho_blue_no_api.mcp.json +0 -0
  215. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/fixtures/oracle_mapping_golden/mapping.json +0 -0
  216. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/fixtures/oracle_mapping_golden/mapping.mcp.json +0 -0
  217. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/fixtures/simulate_v1_request.json +0 -0
  218. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/fixtures/vault_info_golden/clean.json +0 -0
  219. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/fixtures/vault_info_golden/clean.mcp.json +0 -0
  220. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/fixtures/vault_info_golden/clean.txt +0 -0
  221. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/fixtures/vault_info_golden/deployment_error.json +0 -0
  222. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/fixtures/vault_info_golden/deployment_error.mcp.json +0 -0
  223. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/fixtures/vault_info_golden/deployment_error.txt +0 -0
  224. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/fixtures/vault_info_golden/fetched.cli.json +0 -0
  225. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/fixtures/vault_info_golden/fetched.json +0 -0
  226. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/fixtures/vault_info_golden/fetched.mcp.json +0 -0
  227. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/fixtures/vault_info_golden/full.txt +0 -0
  228. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/fixtures/vault_info_golden/rich.txt +0 -0
  229. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_about.py +0 -0
  230. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_access_manager.py +0 -0
  231. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_cache.py +0 -0
  232. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_chains.py +0 -0
  233. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_cli_changelog.py +0 -0
  234. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_cli_commands.py +0 -0
  235. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_cli_config_store.py +0 -0
  236. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_cli_hypercore.py +0 -0
  237. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_cli_market_golden.py +0 -0
  238. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_cli_market_morpho_blue.py +0 -0
  239. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_cli_oracle_mapping.py +0 -0
  240. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_cli_oracle_mapping_golden.py +0 -0
  241. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_cli_pending_requests_scan.py +0 -0
  242. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_cli_role_accounts.py +0 -0
  243. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_cli_vault_info_golden.py +0 -0
  244. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_cli_vault_predates_getters.py +0 -0
  245. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_cli_vault_validate.py +0 -0
  246. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_contract_call.py +0 -0
  247. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_contract_encoder.py +0 -0
  248. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_crosschain_ccip_events.py +0 -0
  249. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_crosschain_ccip_indexer.py +0 -0
  250. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_crosschain_ccip_quote.py +0 -0
  251. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_crosschain_chainlink.py +0 -0
  252. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_crosschain_contracts.py +0 -0
  253. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_crosschain_encoding.py +0 -0
  254. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_crosschain_errors.py +0 -0
  255. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_crosschain_lane.py +0 -0
  256. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_crosschain_layering.py +0 -0
  257. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_crosschain_readiness.py +0 -0
  258. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_crosschain_readiness_report.py +0 -0
  259. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_crosschain_transport.py +0 -0
  260. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_docs_typing.py +0 -0
  261. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_erc20.py +0 -0
  262. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_error_hierarchy.py +0 -0
  263. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_examples_composed_hypercore.py +0 -0
  264. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_examples_crosschain.py +0 -0
  265. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_examples_euler_looping_arbitrum.py +0 -0
  266. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_examples_vaults.py +0 -0
  267. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_explorer.py +0 -0
  268. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_external_state_events.py +0 -0
  269. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_external_state_executor.py +0 -0
  270. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_fee_manager.py +0 -0
  271. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_fuse_encoding.py +0 -0
  272. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_fusion_instance.py +0 -0
  273. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_guide.py +0 -0
  274. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_hypercore_deployment.py +0 -0
  275. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_hypercore_events.py +0 -0
  276. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_hypercore_flow.py +0 -0
  277. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_hypercore_live.py +0 -0
  278. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_hypercore_outcome.py +0 -0
  279. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_hypercore_readers.py +0 -0
  280. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_hypercore_simulation.py +0 -0
  281. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_logs.py +0 -0
  282. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_mcp_hypercore.py +0 -0
  283. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_mcp_server.py +0 -0
  284. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_mcp_vault_validate.py +0 -0
  285. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_multicall.py +0 -0
  286. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_oracle.py +0 -0
  287. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_oracle_mapping.py +0 -0
  288. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_plasma_vault.py +0 -0
  289. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_public_type_reachability.py +0 -0
  290. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_reader_encoding.py +0 -0
  291. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_readiness_selection.py +0 -0
  292. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_revert_decoding.py +0 -0
  293. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_rewards_manager.py +0 -0
  294. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_roles.py +0 -0
  295. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_sdk_layering.py +0 -0
  296. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_simulate_borrow_aave_v3_mainnet.py +0 -0
  297. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_simulate_claim_merkl_wrapper_base.py +0 -0
  298. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_simulate_claim_morpho_rewards.py +0 -0
  299. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_simulate_crosschain_canary.py +0 -0
  300. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_simulate_crosschain_hypercore.py +0 -0
  301. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_simulate_crosschain_hyperevm.py +0 -0
  302. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_simulate_crosschain_lifecycle.py +0 -0
  303. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_simulate_euler_v2_batch_base.py +0 -0
  304. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_simulate_euler_v2_credit_market_base.py +0 -0
  305. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_simulate_euler_v2_swap_deploy_base.py +0 -0
  306. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_simulate_euler_v2_swap_reconfigure_base.py +0 -0
  307. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_simulate_euler_v2_swap_registry_base.py +0 -0
  308. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_simulate_examples_arbitrum.py +0 -0
  309. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_simulate_examples_base.py +0 -0
  310. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_simulate_examples_crosschain.py +0 -0
  311. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_simulate_external_state_executor_base.py +0 -0
  312. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_simulate_fusion_factory_clone_base.py +0 -0
  313. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_simulate_looping_aave_v3_base.py +0 -0
  314. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_simulate_looping_morpho_blue_base.py +0 -0
  315. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_simulate_morpho_blue.py +0 -0
  316. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_simulate_pilot_v3.py +0 -0
  317. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_simulate_pilot_v4_uniswap.py +0 -0
  318. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_simulate_pilot_v5_ramses.py +0 -0
  319. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_simulate_pilot_v5_scheduled.py +0 -0
  320. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_simulate_plasma_vault_setters_base.py +0 -0
  321. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_simulate_readers_integration.py +0 -0
  322. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_simulate_release_funds.py +0 -0
  323. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_simulate_swap_on_base.py +0 -0
  324. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_simulate_vault_from_scratch_base.py +0 -0
  325. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_simulation_errors.py +0 -0
  326. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_simulation_observation.py +0 -0
  327. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_solidity_compiler.py +0 -0
  328. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_solidity_mirrors.py +0 -0
  329. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_substrates.py +0 -0
  330. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_types_and_events.py +0 -0
  331. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_vault_data_block.py +0 -0
  332. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_vault_dep_graph.py +0 -0
  333. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_vault_info_onchain_base.py +0 -0
  334. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_vault_info_onchain_base_no_withdraw_manager.py +0 -0
  335. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_vault_reconciliation_onchain_base.py +0 -0
  336. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_vault_simulator_blocks.py +0 -0
  337. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_web3_context.py +0 -0
  338. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_withdraw_manager.py +0 -0
  339. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/typed_client/consumer.py +0 -0
  340. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/typed_client/rejected.py +0 -0
  341. {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/uv.lock +0 -0
@@ -533,6 +533,11 @@ decoder adds its own fields.
533
533
  - `ZeroBalanceFuse` markets are capabilities (swap, flash-loan,
534
534
  instant-withdrawal, admin) with structurally zero balance, not venues. They
535
535
  go in `zero_balance_fuses` and are not counted as liquidity markets.
536
+ - A validation input that cannot be read is never a silent `ok`: a lending
537
+ position whose reads fail is an `UnreadableLendingMarket` (a `lending_health`
538
+ warning), a failed lending-health or unpriceable-token read is named in
539
+ `VaultData.failed_reads` (an `incomplete_read` warning). Catch the chain and
540
+ transport errors only; degrade-to-`None` is for display-only data.
536
541
  - Reconciliation: `totalAssets = balance_fuses_sum + underlying_on_vault +
537
542
  rewards_manager_balance` (`PlasmaVault._getGrossTotalAssets` adds
538
543
  `IRewardsClaimManager.balanceOf()`, the vested rewards, when a manager is set;
@@ -990,6 +990,31 @@ and `log_index` was passed through as given (a hex string from a raw log); the e
990
990
  These apply to every caller, type-checked or not. Changes described in full above
991
991
  are listed with a link.
992
992
 
993
+ - **A failed lending read is a warning, never a silent `ok`.** 3.x caught every
994
+ exception in the lending-health readers and dropped the position, so a provider
995
+ outage could leave no lending finding and an `ok` verdict. Now:
996
+ - A position whose reads fail with a chain or transport error (revert, RPC,
997
+ timeout, no or malformed data) becomes an `UnreadableLendingMarket` in
998
+ `VaultLendingHealth.unreadable`.
999
+ - Each unreadable position is a `lending_health` warning.
1000
+ - An Aave V3 market whose Pool could not be resolved is listed the same way
1001
+ instead of being skipped.
1002
+ - Any other exception propagates.
1003
+ - When the whole lending-health read or the unpriceable-token check fails with
1004
+ a revert, an RPC error or a timeout, `VaultData.failed_reads` names it and
1005
+ validation reports an `incomplete_read` warning. A transport failure (no
1006
+ connection) still aborts the fetch rather than return a snapshot.
1007
+ - A substrate the unpriceable-token check could not cover is named the same
1008
+ way (`unpriceable_priced_tokens: <what>`). Examples: a Morpho position whose
1009
+ read failed, or a reverting `asset()` / `getSilos()`.
1010
+ `fetch_unpriceable_priced_tokens` returns an `UnpriceableCheck` (`tokens`,
1011
+ `unread`) instead of a bare list.
1012
+
1013
+ The `lending_health` JSON block of `vault info` and `vault validate` carries
1014
+ `unreadable`, and a custom `VaultState` adds the `failed_reads` property. A
1015
+ position missing from the breakdown tables is shown as absent, while the
1016
+ checks it feeds report it.
1017
+
993
1018
  - **More markets decode their substrates.** VELODROME_SUPERCHAIN (31, Gauge /
994
1019
  Pool), AGUA_GLOBAL_CARRY (51, VAULT / ASSET), TERM_FINANCE (52, SERVICER address
995
1020
  or COLLATERAL_TOKEN pair key), META_MORPHO_0001, MERKL and
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: ipor-fusion
3
- Version: 4.0.0.dev222
3
+ Version: 4.0.0.dev231
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.dev222"
3
+ version = "4.0.0.dev231"
4
4
  description = "The IPOR Fusion SDK for Python"
5
5
  authors = [
6
6
  { name = "Adam Mydlarz", email = "amydlarz@gmail.com" },
@@ -275,6 +275,8 @@ from ipor_fusion.readers import (
275
275
  RamsesV2Reader,
276
276
  UniswapV3Position,
277
277
  UniswapV3Reader,
278
+ UnpriceableCheck,
279
+ UnreadableLendingMarket,
278
280
  VaultLendingHealth,
279
281
  build_oracle_mapping,
280
282
  fetch_unpriceable_priced_tokens,
@@ -385,6 +387,7 @@ from ipor_fusion.vault_info import (
385
387
  ReconciliationJson,
386
388
  SharePriceJson,
387
389
  SubstrateEntryJson,
390
+ UnreadableLendingMarketJson,
388
391
  UsdAmountJson,
389
392
  VaultAssetJson,
390
393
  VaultInfoJson,
@@ -631,6 +634,7 @@ __all__ = [
631
634
  "HYPEREVM_CHAIN_ID",
632
635
  "LendingMarketHealth",
633
636
  "VaultLendingHealth",
637
+ "UnreadableLendingMarket",
634
638
  "fetch_vault_lending_health",
635
639
  "OracleAsset",
636
640
  "OracleMapping",
@@ -651,6 +655,7 @@ __all__ = [
651
655
  "build_oracle_mapping",
652
656
  "OracleMappingReader",
653
657
  "MiddlewarePricedToken",
658
+ "UnpriceableCheck",
654
659
  "fetch_unpriceable_priced_tokens",
655
660
  "PositionData",
656
661
  "UniswapV3Reader",
@@ -701,6 +706,7 @@ __all__ = [
701
706
  "HyperCorePerpMarketJson",
702
707
  "HyperCoreSpotLegJson",
703
708
  "LendingHealthJson",
709
+ "UnreadableLendingMarketJson",
704
710
  "LendingMarketJson",
705
711
  "ManagersJson",
706
712
  "MorphoBreakdownJson",
@@ -95,6 +95,17 @@ def _print_lending_health(data: VaultData) -> None: # noqa: C901
95
95
  if m.protocol == "aave_v4":
96
96
  click.echo(f" spoke {m.substrate_id}:")
97
97
  _print_health_lines(m, indent=" ")
98
+ _print_unreadable_health(lh)
99
+
100
+
101
+ def _print_unreadable_health(lh: VaultLendingHealth | None) -> None:
102
+ for u in lh.unreadable if lh is not None else []:
103
+ where = f" {u.substrate_id}" if u.substrate_id else ""
104
+ click.secho(
105
+ f" {format_market_label(u.market_id)}: {u.protocol}{where} "
106
+ f"health unknown — {u.reason}",
107
+ fg="yellow",
108
+ )
98
109
 
99
110
 
100
111
  def _index_lending_health(
@@ -431,8 +431,20 @@ class LendingMarketHealth(_Base):
431
431
  is_critical: bool
432
432
 
433
433
 
434
+ class UnreadableLendingMarket(_Base):
435
+ """A lending position whose health could not be read: unknown, not
436
+ healthy."""
437
+
438
+ protocol: str
439
+ market_id: MarketId
440
+ market_name: str
441
+ substrate_id: str | None
442
+ reason: str
443
+
444
+
434
445
  class LendingHealth(_Base):
435
446
  markets: list[LendingMarketHealth]
447
+ unreadable: list[UnreadableLendingMarket] = Field(default_factory=list)
436
448
  worst_ltv_usage_percent: float | None
437
449
 
438
450
 
@@ -875,12 +887,25 @@ class LendingMarketModel(_Base):
875
887
 
876
888
  class LendingHealthModel(_Base):
877
889
  markets: list[LendingMarketModel]
890
+ unreadable: list[UnreadableLendingMarket] = Field(
891
+ description="Positions whose health could not be read: unknown, not healthy."
892
+ )
878
893
  worst_ltv_usage_percent: float | None
879
894
 
880
895
  @classmethod
881
896
  def from_health(cls, health: VaultLendingHealth) -> LendingHealthModel:
882
897
  return cls(
883
898
  markets=[LendingMarketModel.from_market(m) for m in health.markets],
899
+ unreadable=[
900
+ UnreadableLendingMarket(
901
+ protocol=u.protocol,
902
+ market_id=u.market_id,
903
+ market_name=u.market_name,
904
+ substrate_id=u.substrate_id,
905
+ reason=u.reason,
906
+ )
907
+ for u in health.unreadable
908
+ ],
884
909
  worst_ltv_usage_percent=health.worst_ltv_usage,
885
910
  )
886
911
 
@@ -17,6 +17,7 @@ from ipor_fusion.readers.hypercore import (
17
17
  )
18
18
  from ipor_fusion.readers.lending_health import (
19
19
  LendingMarketHealth,
20
+ UnreadableLendingMarket,
20
21
  VaultLendingHealth,
21
22
  fetch_vault_lending_health,
22
23
  )
@@ -56,6 +57,7 @@ from ipor_fusion.readers.ramses_v2 import RamsesV2Position, RamsesV2Reader
56
57
  from ipor_fusion.readers.uniswap_v3 import UniswapV3Position, UniswapV3Reader
57
58
  from ipor_fusion.readers.unpriceable import (
58
59
  MiddlewarePricedToken,
60
+ UnpriceableCheck,
59
61
  fetch_unpriceable_priced_tokens,
60
62
  )
61
63
 
@@ -80,6 +82,7 @@ __all__ = [
80
82
  "HYPEREVM_CHAIN_ID",
81
83
  "LendingMarketHealth",
82
84
  "VaultLendingHealth",
85
+ "UnreadableLendingMarket",
83
86
  "fetch_vault_lending_health",
84
87
  "OracleAsset",
85
88
  "OracleMapping",
@@ -100,6 +103,7 @@ __all__ = [
100
103
  "build_oracle_mapping",
101
104
  "OracleMappingReader",
102
105
  "MiddlewarePricedToken",
106
+ "UnpriceableCheck",
103
107
  "fetch_unpriceable_priced_tokens",
104
108
  "PositionData",
105
109
  "UniswapV3Reader",
@@ -8,15 +8,25 @@ from __future__ import annotations
8
8
 
9
9
  import logging
10
10
  from concurrent.futures import ThreadPoolExecutor
11
- from dataclasses import dataclass
11
+ from dataclasses import dataclass, field
12
12
 
13
+ import requests
13
14
  from eth_abi import decode
15
+ from eth_abi.exceptions import DecodingError
14
16
  from eth_typing import ChecksumAddress
15
17
  from eth_utils import function_signature_to_4byte_selector
16
18
  from web3 import Web3
19
+ from web3.exceptions import (
20
+ BadFunctionCallOutput,
21
+ ContractLogicError,
22
+ ProviderConnectionError,
23
+ TimeExhausted,
24
+ Web3RPCError,
25
+ )
17
26
 
18
27
  from ipor_fusion.core.context import Web3Context
19
28
  from ipor_fusion.core.multicall import Multicall3
29
+ from ipor_fusion.errors import EmptyCallResultError
20
30
  from ipor_fusion.market_ids import IporFusionMarkets
21
31
  from ipor_fusion.readers.aave_v3 import AaveV3Reader
22
32
  from ipor_fusion.readers.aave_v4 import AaveV4OracleReader, AaveV4SpokeReader
@@ -36,6 +46,19 @@ from ipor_fusion.types import (
36
46
 
37
47
  _logger = logging.getLogger(__name__)
38
48
 
49
+ # What a lending read can fail with that is about the chain, not the code:
50
+ # reverts, RPC and transport errors, a contract answering no or malformed data.
51
+ _READ_ERRORS = (
52
+ ContractLogicError,
53
+ Web3RPCError,
54
+ TimeExhausted,
55
+ BadFunctionCallOutput,
56
+ ProviderConnectionError,
57
+ requests.RequestException,
58
+ EmptyCallResultError,
59
+ DecodingError,
60
+ )
61
+
39
62
  WAD = 10**18
40
63
  RAY = 10**27
41
64
  ORACLE_PRICE_SCALE = 10**36
@@ -121,15 +144,35 @@ class LendingMarketHealth:
121
144
  return self.health_factor <= 1.05
122
145
 
123
146
 
147
+ @dataclass(slots=True)
148
+ class UnreadableLendingMarket:
149
+ """A lending position whose health could not be read: it is unknown, not
150
+ healthy. ``substrate_id`` as in `LendingMarketHealth`."""
151
+
152
+ protocol: str
153
+ market_id: MarketId
154
+ market_name: str
155
+ reason: str
156
+ substrate_id: str | None = None
157
+
158
+
124
159
  @dataclass(slots=True)
125
160
  class VaultLendingHealth:
126
- """Aggregated lending health for all lending markets in a vault."""
161
+ """Aggregated lending health for all lending markets in a vault.
162
+
163
+ ``unreadable`` lists the positions whose reads failed; the health is
164
+ complete only when it is empty."""
127
165
 
128
166
  markets: list[LendingMarketHealth]
167
+ unreadable: list[UnreadableLendingMarket] = field(default_factory=list)
129
168
 
130
169
  @property
131
170
  def has_lending_positions(self) -> bool:
132
- return len(self.markets) > 0
171
+ return len(self.markets) > 0 or len(self.unreadable) > 0
172
+
173
+ @property
174
+ def is_complete(self) -> bool:
175
+ return not self.unreadable
133
176
 
134
177
  @property
135
178
  def worst_ltv_usage(self) -> float | None:
@@ -137,6 +180,12 @@ class VaultLendingHealth:
137
180
  return max(usages) if usages else None
138
181
 
139
182
 
183
+ def _reason(what: str, exc: Exception) -> str:
184
+ """Why a read failed, for the unreadable entry and its finding."""
185
+ _logger.debug("lending %s read failed: %s: %s", what, type(exc).__name__, exc)
186
+ return f"{what} read failed: {type(exc).__name__}"
187
+
188
+
140
189
  def _call_morpho_oracle_price(ctx: Web3Context, oracle_address: ChecksumAddress) -> int:
141
190
  """Call IOracle(oracle).price() -> uint256."""
142
191
  selector = function_signature_to_4byte_selector("price()")
@@ -152,7 +201,7 @@ def _compute_morpho_market_health(
152
201
  vault_address: PlasmaVaultAddress,
153
202
  ipor_market_id: MarketId,
154
203
  market_name: str,
155
- ) -> LendingMarketHealth | None:
204
+ ) -> LendingMarketHealth | UnreadableLendingMarket:
156
205
  """Compute LTV health for a single Morpho Blue market position."""
157
206
  batch = Multicall3(ctx).batch()
158
207
  position_read = batch.add(reader.position(morpho_market_id, vault_address))
@@ -160,9 +209,14 @@ def _compute_morpho_market_health(
160
209
  params_read = batch.add(reader.market_params(morpho_market_id))
161
210
  try:
162
211
  batch.run()
163
- except Exception:
164
- _logger.debug("Failed to read Morpho market data for %s", morpho_market_id)
165
- return None
212
+ except _READ_ERRORS as exc:
213
+ return UnreadableLendingMarket(
214
+ protocol="morpho",
215
+ market_id=ipor_market_id,
216
+ market_name=market_name,
217
+ reason=_reason("market data", exc),
218
+ substrate_id=morpho_market_id,
219
+ )
166
220
  position, market, params = (
167
221
  position_read.value,
168
222
  market_read.value,
@@ -186,9 +240,14 @@ def _compute_morpho_market_health(
186
240
 
187
241
  try:
188
242
  oracle_price = _call_morpho_oracle_price(ctx, params.oracle)
189
- except Exception:
190
- _logger.debug("Failed to read Morpho oracle price for %s", morpho_market_id)
191
- return None
243
+ except _READ_ERRORS as exc:
244
+ return UnreadableLendingMarket(
245
+ protocol="morpho",
246
+ market_id=ipor_market_id,
247
+ market_name=market_name,
248
+ reason=_reason("oracle price", exc),
249
+ substrate_id=morpho_market_id,
250
+ )
192
251
 
193
252
  borrowed = to_assets_up(
194
253
  position.borrow_shares, market.total_borrow_assets, market.total_borrow_shares
@@ -246,13 +305,18 @@ def _compute_aave_market_health(
246
305
  vault_address: PlasmaVaultAddress,
247
306
  ipor_market_id: MarketId,
248
307
  market_name: str,
249
- ) -> LendingMarketHealth | None:
308
+ ) -> LendingMarketHealth | UnreadableLendingMarket:
250
309
  """Compute LTV health for the vault's Aave V3 position."""
251
310
  try:
252
311
  data = reader.get_user_account_data(vault_address).call(ctx)
253
- except Exception:
254
- _logger.debug("Failed to read Aave V3 account data for %s", vault_address)
255
- return None
312
+ except _READ_ERRORS as exc:
313
+ return UnreadableLendingMarket(
314
+ protocol="aave_v3",
315
+ market_id=ipor_market_id,
316
+ market_name=market_name,
317
+ reason=_reason("account data", exc),
318
+ substrate_id=None,
319
+ )
256
320
 
257
321
  # No debt = no risk
258
322
  if data.total_debt_base == 0:
@@ -300,16 +364,21 @@ def _compute_aave_v4_market_health(
300
364
  vault_address: PlasmaVaultAddress,
301
365
  ipor_market_id: MarketId,
302
366
  market_name: str,
303
- ) -> LendingMarketHealth | None:
367
+ ) -> LendingMarketHealth | UnreadableLendingMarket:
304
368
  """Compute LTV health for the vault's account on one Aave V4 Spoke."""
305
369
  reader = AaveV4SpokeReader(spoke)
306
370
  try:
307
371
  data = reader.get_user_account_data(vault_address).call(ctx)
308
372
  oracle_decimals = AaveV4OracleReader(reader.oracle().call(ctx)).decimals()
309
373
  value_scale = 10 ** (oracle_decimals.call(ctx) + 18)
310
- except Exception:
311
- _logger.debug("Failed to read Aave V4 account data on spoke %s", spoke)
312
- return None
374
+ except _READ_ERRORS as exc:
375
+ return UnreadableLendingMarket(
376
+ protocol="aave_v4",
377
+ market_id=ipor_market_id,
378
+ market_name=market_name,
379
+ reason=_reason("account data", exc),
380
+ substrate_id=spoke,
381
+ )
313
382
 
314
383
  max_ltv = data.avg_collateral_factor / WAD
315
384
  current_ltv: float | None
@@ -355,6 +424,7 @@ class _LendingMarkets:
355
424
  morpho: list[tuple[MarketId, str, MorphoBlueMarketId]]
356
425
  aave_v3: list[tuple[MarketId, str, ChecksumAddress]]
357
426
  aave_v4: list[tuple[MarketId, str, ChecksumAddress]]
427
+ unreadable: list[UnreadableLendingMarket]
358
428
 
359
429
 
360
430
  def _collect_lending_markets(
@@ -364,7 +434,7 @@ def _collect_lending_markets(
364
434
  ) -> _LendingMarkets:
365
435
  """Pick the positions to read: Morpho substrates, Aave V3 markets with a Pool,
366
436
  and the Spokes of Aave V4 markets."""
367
- markets = _LendingMarkets(morpho=[], aave_v3=[], aave_v4=[])
437
+ markets = _LendingMarkets(morpho=[], aave_v3=[], aave_v4=[], unreadable=[])
368
438
 
369
439
  for mid in balance_fuse_market_ids:
370
440
  name = market_name(mid)
@@ -377,7 +447,14 @@ def _collect_lending_markets(
377
447
  if aave_pool := aave_pools.get(mid):
378
448
  markets.aave_v3.append((mid, name, aave_pool))
379
449
  else:
380
- _logger.debug("No Aave V3 pool for market %d, skipping", mid)
450
+ markets.unreadable.append(
451
+ UnreadableLendingMarket(
452
+ protocol="aave_v3",
453
+ market_id=mid,
454
+ market_name=name,
455
+ reason="the balance fuse's Aave V3 Pool could not be read",
456
+ )
457
+ )
381
458
  elif mid in AAVE_V4_MARKET_IDS:
382
459
  for spoke in _aave_v4_spokes(market_substrates.get(mid, [])):
383
460
  markets.aave_v4.append((mid, name, spoke))
@@ -402,8 +479,8 @@ def fetch_vault_lending_health(
402
479
  market_substrates: Map of market_id -> list of raw substrate bytes.
403
480
  Morpho market ids and Aave V4 Spokes are read from it.
404
481
  aave_pools: Map of Aave V3 market_id -> the Pool its balance fuse
405
- reads (`AaveV3FuseReader.pool`). Aave V3 markets without an entry
406
- are skipped. Defaults to the chain's Core pool for AAVE_V3 only:
482
+ reads (`AaveV3FuseReader.pool`). An Aave V3 market without an
483
+ entry is listed in ``unreadable``. Defaults to the chain's Core pool for AAVE_V3 only:
407
484
  the Pools of Aave V3 Prime and SparkLend are known only to their
408
485
  fuses.
409
486
  """
@@ -416,6 +493,7 @@ def fetch_vault_lending_health(
416
493
  )
417
494
 
418
495
  results: list[LendingMarketHealth] = []
496
+ unreadable = list(markets.unreadable)
419
497
 
420
498
  with ThreadPoolExecutor() as pool:
421
499
  futures = []
@@ -462,7 +540,9 @@ def fetch_vault_lending_health(
462
540
 
463
541
  for fut in futures:
464
542
  result = fut.result()
465
- if result is not None:
543
+ if isinstance(result, UnreadableLendingMarket):
544
+ unreadable.append(result)
545
+ else:
466
546
  results.append(result)
467
547
 
468
- return VaultLendingHealth(markets=results)
548
+ return VaultLendingHealth(markets=results, unreadable=unreadable)
@@ -11,7 +11,7 @@ triggers it.
11
11
  from __future__ import annotations
12
12
 
13
13
  from collections.abc import Iterable
14
- from dataclasses import dataclass, replace
14
+ from dataclasses import dataclass, field, replace
15
15
 
16
16
  from eth_utils import function_signature_to_4byte_selector
17
17
  from web3 import Web3
@@ -171,8 +171,21 @@ def _morpho_tokens(
171
171
  ]
172
172
 
173
173
 
174
+ @dataclass(frozen=True)
175
+ class UnpriceableCheck:
176
+ """`fetch_unpriceable_priced_tokens`: the flagged ``tokens``, and in
177
+ ``unread`` the granted substrates whose priced token could not be found
178
+ (a reverting ``asset()`` or ``getSilos()``): the check is complete only
179
+ when it is empty."""
180
+
181
+ tokens: list[MiddlewarePricedToken]
182
+ unread: list[str] = field(default_factory=list)
183
+
184
+
174
185
  def _vault_asset_tokens(
175
- ctx: Web3Context, market_substrates: dict[MarketId, list[bytes]]
186
+ ctx: Web3Context,
187
+ market_substrates: dict[MarketId, list[bytes]],
188
+ unread: list[str],
176
189
  ) -> list[MiddlewarePricedToken]:
177
190
  """``asset()`` of granted ERC4626 vaults (Euler V2, generic ERC4626) and
178
191
  of both silos of every granted Silo Config. All three fuses price it
@@ -196,6 +209,11 @@ def _vault_asset_tokens(
196
209
  multicall = Multicall3(ctx)
197
210
  if configs:
198
211
  silo_pairs = multicall.try_aggregate([_silos_call(c) for c in configs])
212
+ unread += [
213
+ f"getSilos() of granted Silo Config {config}"
214
+ for config, silos in zip(configs, silo_pairs, strict=True)
215
+ if silos is None
216
+ ]
199
217
  vaults += [
200
218
  (
201
219
  IporFusionMarkets.SILO_V2,
@@ -211,6 +229,9 @@ def _vault_asset_tokens(
211
229
  assets = multicall.try_aggregate(
212
230
  [_address_call(vault, _ASSET_SELECTOR) for _, vault, _ in vaults]
213
231
  )
232
+ unread += [
233
+ via for (_, _, via), asset in zip(vaults, assets, strict=True) if asset is None
234
+ ]
214
235
  return [
215
236
  MiddlewarePricedToken(
216
237
  market_id,
@@ -293,17 +314,20 @@ def fetch_unpriceable_priced_tokens(
293
314
  balance_fuses: list[BalanceFuse],
294
315
  market_substrates: dict[MarketId, list[bytes]],
295
316
  morpho_positions: dict[MarketId, list[MorphoPositionBreakdown]] | None,
296
- ) -> list[MiddlewarePricedToken]:
317
+ ) -> UnpriceableCheck:
297
318
  """Every middleware-priced token of a granted substrate the middleware
298
- cannot price, in market order."""
319
+ cannot price, in market order, and the substrates whose token could not
320
+ be found. ``morpho_positions`` must hold every Morpho substrate: a
321
+ position left out is a token left unchecked."""
322
+ unread: list[str] = []
299
323
  priced = [
300
324
  *_plain_tokens(market_substrates, Web3.to_checksum_address(underlying)),
301
325
  *_morpho_tokens(morpho_positions),
302
- *_vault_asset_tokens(ctx, market_substrates),
326
+ *_vault_asset_tokens(ctx, market_substrates, unread),
303
327
  *_aave_v3_middleware_tokens(ctx, balance_fuses, market_substrates),
304
328
  ]
305
329
  if not priced:
306
- return []
330
+ return UnpriceableCheck(tokens=[], unread=unread)
307
331
  oracle = price_oracle_middleware(ctx, Web3.to_checksum_address(price_oracle))
308
332
  unpriceable = _unpriceable(ctx, oracle, (p.token for p in priced))
309
333
  flagged = {
@@ -311,4 +335,6 @@ def fetch_unpriceable_priced_tokens(
311
335
  for p in priced
312
336
  if p.token in unpriceable
313
337
  }
314
- return [flagged[key] for key in sorted(flagged)]
338
+ return UnpriceableCheck(
339
+ tokens=[flagged[key] for key in sorted(flagged)], unread=unread
340
+ )
@@ -84,6 +84,7 @@ Check: TypeAlias = Literal[
84
84
  "missing_erc20_dependency",
85
85
  "unpriceable_token",
86
86
  "lending_health",
87
+ "incomplete_read",
87
88
  "total_assets_reconciliation",
88
89
  "pending_withdrawals",
89
90
  "market_storage_drift",
@@ -153,6 +153,12 @@ class VaultState(Protocol):
153
153
  def unpriceable_priced_tokens(self) -> Sequence[UnpriceableToken] | None: ...
154
154
  @property
155
155
  def unimplemented_getters(self) -> Sequence[str]: ...
156
+ @property
157
+ def failed_reads(self) -> Sequence[str]:
158
+ """Validation inputs whose read failed (``lending_health``,
159
+ ``unpriceable_priced_tokens``): their checks did not run."""
160
+ ...
161
+
156
162
  @property
157
163
  def market_balances(self) -> Mapping[MarketId, Amount]: ...
158
164
  @property
@@ -291,6 +297,7 @@ def validate_vault_state(state: VaultState) -> VaultValidation:
291
297
  *_missing_erc20_dependency_findings(state),
292
298
  *_unpriceable_token_findings(state),
293
299
  *_lending_health_findings(state),
300
+ *_failed_read_findings(state),
294
301
  *_reconciliation_findings(state, reconciliation),
295
302
  *_erc20_findings(state, state.erc20),
296
303
  ]
@@ -550,11 +557,43 @@ def _unpriceable_token_finding(token: UnpriceableToken) -> Finding:
550
557
  )
551
558
 
552
559
 
560
+ _FAILED_READ_IMPACT = {
561
+ "lending_health": "the liquidation risk of the lending positions is unknown",
562
+ "unpriceable_priced_tokens": "tokens the price oracle cannot price were not "
563
+ "looked for",
564
+ }
565
+
566
+
567
+ def _failed_read_findings(state: VaultState) -> list[Finding]:
568
+ findings = []
569
+ for read in state.failed_reads:
570
+ name, _, detail = read.partition(": ")
571
+ what = f"{name} ({detail})" if detail else name
572
+ impact = _FAILED_READ_IMPACT.get(name, "its check did not run")
573
+ findings.append(
574
+ Finding(
575
+ "incomplete_read",
576
+ "warning",
577
+ f"WARNING — {what} could not be read: {impact}",
578
+ )
579
+ )
580
+ return findings
581
+
582
+
553
583
  def _lending_health_findings(state: VaultState) -> list[Finding]:
554
584
  health = state.lending_health
555
585
  if not health or not health.has_lending_positions:
556
586
  return []
557
- findings: list[Finding] = []
587
+ findings = [
588
+ Finding(
589
+ "lending_health",
590
+ "warning",
591
+ f"WARNING — {u.protocol} {u.market_name}"
592
+ f"{f' ({u.substrate_id})' if u.substrate_id else ''}: health unknown, "
593
+ f"{u.reason}",
594
+ )
595
+ for u in health.unreadable
596
+ ]
558
597
  for market in health.markets:
559
598
  if market.ltv_usage_percent is None:
560
599
  continue