ipor-fusion 4.2.0.dev16__tar.gz → 4.2.0.dev20__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 (373) hide show
  1. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/PKG-INFO +1 -1
  2. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/examples/advanced_compound_v3_institutional_usdc_ethereum.py +10 -2
  3. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/pyproject.toml +1 -1
  4. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/readers/lending_health.py +16 -6
  5. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/validation.py +3 -2
  6. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_cli_helpers.py +28 -0
  7. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_cli_lending_health.py +125 -1
  8. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_mcp_models.py +31 -0
  9. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/.claude-plugin/marketplace.json +0 -0
  10. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/.claude-plugin/plugin.json +0 -0
  11. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/.env.example +0 -0
  12. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/.gemini/settings.json +0 -0
  13. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/.git-blame-ignore-revs +0 -0
  14. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/.github/workflows/cd.yml +0 -0
  15. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/.github/workflows/ci.yml +0 -0
  16. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/.github/workflows/commit-lint.yml +0 -0
  17. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/.github/workflows/notify-slack.yml +0 -0
  18. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/.github/workflows/python-build.yml +0 -0
  19. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/.github/workflows/python-publish.yml +0 -0
  20. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/.github/workflows/release.yml +0 -0
  21. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/.github/workflows/report-slack.yml +0 -0
  22. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/.gitignore +0 -0
  23. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/.mcp.json +0 -0
  24. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/.pre-commit-config.yaml +0 -0
  25. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/.python-version +0 -0
  26. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/AGENTS.md +0 -0
  27. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/CHANGELOG.md +0 -0
  28. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/CLAUDE.md +0 -0
  29. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/CODEOWNERS +0 -0
  30. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/LICENSE +0 -0
  31. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/MIGRATION_4.md +0 -0
  32. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/README.md +0 -0
  33. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/build.sh +0 -0
  34. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/examples/CONTRIBUTING.md +0 -0
  35. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/examples/README.md +0 -0
  36. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/examples/advanced_euler_v2_credit_market_base.py +0 -0
  37. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/examples/advanced_euler_v2_looping_arbitrum.py +0 -0
  38. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/examples/crosschain_ccip_usdc_arbitrum_hyperevm.py +0 -0
  39. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/examples/external_state_margin_leg_base.py +0 -0
  40. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/examples/hypercore_vault_official_deployment_hyperevm.py +0 -0
  41. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/examples/simple_aave_v3_supply_base.py +0 -0
  42. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/pytest.ini +0 -0
  43. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/scripts/commit-check.sh +0 -0
  44. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/scripts/fusion_error_signatures.py +0 -0
  45. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/skills/ipor-deploy-vault/SKILL.md +0 -0
  46. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/__init__.py +0 -0
  47. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/about.py +0 -0
  48. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/cache.py +0 -0
  49. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/chains.py +0 -0
  50. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/cli/__init__.py +0 -0
  51. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/cli/address_cmd.py +0 -0
  52. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/cli/changelog_cmd.py +0 -0
  53. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/cli/config_cmd.py +0 -0
  54. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/cli/config_store.py +0 -0
  55. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/cli/fuse_cmd.py +0 -0
  56. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/cli/main.py +0 -0
  57. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/cli/market_cmd.py +0 -0
  58. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/cli/options.py +0 -0
  59. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/cli/oracle_mapping_view.py +0 -0
  60. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/cli/preflight_cmd.py +0 -0
  61. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/cli/vault_cmd.py +0 -0
  62. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/cli/vault_health.py +0 -0
  63. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/cli/vault_info_view.py +0 -0
  64. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/cli/vault_markets_view.py +0 -0
  65. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/cli/vault_rendering.py +0 -0
  66. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/config/__init__.py +0 -0
  67. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/config/roles.py +0 -0
  68. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/core/__init__.py +0 -0
  69. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/core/access.py +0 -0
  70. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/core/address.py +0 -0
  71. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/core/context.py +0 -0
  72. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/core/context_manager.py +0 -0
  73. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/core/contract.py +0 -0
  74. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/core/erc20.py +0 -0
  75. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/core/external_state_executor.py +0 -0
  76. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/core/fee_manager.py +0 -0
  77. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/core/fuse.py +0 -0
  78. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/core/fuse_action.py +0 -0
  79. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/core/fuse_decoding.py +0 -0
  80. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/core/fusion_factory.py +0 -0
  81. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/core/logs.py +0 -0
  82. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/core/multicall.py +0 -0
  83. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/core/oracle.py +0 -0
  84. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/core/plasma_vault.py +0 -0
  85. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/core/rewards_manager.py +0 -0
  86. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/core/simulation.py +0 -0
  87. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/core/simulation_result.py +0 -0
  88. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/core/withdraw_manager.py +0 -0
  89. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/crosschain/__init__.py +0 -0
  90. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/crosschain/ccip/__init__.py +0 -0
  91. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/crosschain/ccip/chainlink.py +0 -0
  92. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/crosschain/ccip/codec.py +0 -0
  93. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/crosschain/ccip/contracts.py +0 -0
  94. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/crosschain/ccip/events.py +0 -0
  95. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/crosschain/ccip/indexer.py +0 -0
  96. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/crosschain/ccip/lane.py +0 -0
  97. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/crosschain/ccip/quote.py +0 -0
  98. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/crosschain/ccip/transport.py +0 -0
  99. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/crosschain/contracts.py +0 -0
  100. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/crosschain/discovery.py +0 -0
  101. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/crosschain/errors.py +0 -0
  102. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/crosschain/fuses/__init__.py +0 -0
  103. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/crosschain/fuses/base.py +0 -0
  104. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/crosschain/fuses/ccip.py +0 -0
  105. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/crosschain/fuses/stargate.py +0 -0
  106. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/crosschain/lane.py +0 -0
  107. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/crosschain/logs.py +0 -0
  108. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/crosschain/messages.py +0 -0
  109. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/crosschain/simulation.py +0 -0
  110. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/crosschain/stargate/__init__.py +0 -0
  111. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/crosschain/stargate/contracts.py +0 -0
  112. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/crosschain/stargate/lane.py +0 -0
  113. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/crosschain/stargate/layerzero.py +0 -0
  114. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/crosschain/stargate/transport.py +0 -0
  115. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/crosschain/transport.py +0 -0
  116. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/dependency_graph.py +0 -0
  117. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/entry_cli.py +0 -0
  118. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/entry_mcp.py +0 -0
  119. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/errors.py +0 -0
  120. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/explorer.py +0 -0
  121. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/field_docs.py +0 -0
  122. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/fuse_whitelist.py +0 -0
  123. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/fuses/__init__.py +0 -0
  124. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/fuses/aave_v3.py +0 -0
  125. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/fuses/async_action.py +0 -0
  126. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/fuses/base.py +0 -0
  127. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/fuses/compound_v3.py +0 -0
  128. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/fuses/erc4626.py +0 -0
  129. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/fuses/euler_v2.py +0 -0
  130. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/fuses/events.py +0 -0
  131. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/fuses/external_state.py +0 -0
  132. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/fuses/external_state_event_decoding.py +0 -0
  133. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/fuses/fluid_instadapp.py +0 -0
  134. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/fuses/gearbox_v3.py +0 -0
  135. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/fuses/hypercore.py +0 -0
  136. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/fuses/hypercore_event_decoding.py +0 -0
  137. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/fuses/hypercore_pre_hooks.py +0 -0
  138. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/fuses/merkl.py +0 -0
  139. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/fuses/morpho.py +0 -0
  140. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/fuses/ramses_v2.py +0 -0
  141. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/fuses/uniswap_v3.py +0 -0
  142. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/fuses/universal.py +0 -0
  143. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/fusion_errors.py +0 -0
  144. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/guide/__init__.py +0 -0
  145. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/guide/architecture.md +0 -0
  146. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/guide/glossary.md +0 -0
  147. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/guide/invariants.md +0 -0
  148. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/guide/quickstart.md +0 -0
  149. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/hypercore/__init__.py +0 -0
  150. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/market_ids.py +0 -0
  151. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/market_info.py +0 -0
  152. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/mcp/__init__.py +0 -0
  153. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/mcp/guide.py +0 -0
  154. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/mcp/models.py +0 -0
  155. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/mcp/server.py +0 -0
  156. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/morpho_api.py +0 -0
  157. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/onchain_text.py +0 -0
  158. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/preflight.py +0 -0
  159. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/py.typed +0 -0
  160. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/readers/__init__.py +0 -0
  161. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/readers/aave_v3.py +0 -0
  162. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/readers/aave_v4.py +0 -0
  163. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/readers/compound_v3.py +0 -0
  164. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/readers/euler_v2.py +0 -0
  165. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/readers/hypercore.py +0 -0
  166. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/readers/hypercore_outcome.py +0 -0
  167. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/readers/morpho.py +0 -0
  168. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/readers/oracle_mapping.py +0 -0
  169. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/readers/oracle_mapping_nodes.py +0 -0
  170. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/readers/oracle_mapping_reader.py +0 -0
  171. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/readers/position_manager.py +0 -0
  172. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/readers/ramses_v2.py +0 -0
  173. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/readers/uniswap_v3.py +0 -0
  174. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/readers/unpriceable.py +0 -0
  175. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/registry.py +0 -0
  176. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/substrates.py +0 -0
  177. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/types.py +0 -0
  178. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/vault_data.py +0 -0
  179. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/vault_erc20.py +0 -0
  180. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/vault_info.py +0 -0
  181. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/vault_managers.py +0 -0
  182. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/vault_positions.py +0 -0
  183. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/src/ipor_fusion/vault_reads.py +0 -0
  184. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/UniswapV3UniversalRouter.py +0 -0
  185. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/_addresses.py +0 -0
  186. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/_crosschain.py +0 -0
  187. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/_crosschain_lifecycle.py +0 -0
  188. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/_crosschain_recovery.py +0 -0
  189. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/_euler_v2.py +0 -0
  190. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/_event_args.py +0 -0
  191. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/_fake_rpc.py +0 -0
  192. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/_foundry.py +0 -0
  193. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/_golden.py +0 -0
  194. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/_guide.py +0 -0
  195. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/_hypercore_model.py +0 -0
  196. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/_hypercore_shadow.py +0 -0
  197. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/_live.py +0 -0
  198. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/_multicall.py +0 -0
  199. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/_oracle_mapping_fake.py +0 -0
  200. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/_simulate.py +0 -0
  201. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/_validation.py +0 -0
  202. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/addresses.py +0 -0
  203. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/conftest.py +0 -0
  204. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/constants.py +0 -0
  205. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/fixtures/hypercore_hip3_flow.json +0 -0
  206. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/fixtures/hypercore_market55.json +0 -0
  207. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/fixtures/hypercore_official.json +0 -0
  208. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/fixtures/market_golden/meta_morpho_v1.json +0 -0
  209. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/fixtures/market_golden/meta_morpho_v1.mcp.json +0 -0
  210. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/fixtures/market_golden/meta_morpho_v2.json +0 -0
  211. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/fixtures/market_golden/meta_morpho_v2.mcp.json +0 -0
  212. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/fixtures/market_golden/morpho_api/market.json +0 -0
  213. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/fixtures/market_golden/morpho_api/vault_v1.json +0 -0
  214. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/fixtures/market_golden/morpho_api/vault_v2.json +0 -0
  215. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/fixtures/market_golden/morpho_api/vault_v2_not_found.json +0 -0
  216. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/fixtures/market_golden/morpho_blue.json +0 -0
  217. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/fixtures/market_golden/morpho_blue.mcp.json +0 -0
  218. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/fixtures/market_golden/morpho_blue_api_error.json +0 -0
  219. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/fixtures/market_golden/morpho_blue_api_error.mcp.json +0 -0
  220. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/fixtures/market_golden/morpho_blue_no_api.json +0 -0
  221. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/fixtures/market_golden/morpho_blue_no_api.mcp.json +0 -0
  222. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/fixtures/oracle_mapping_golden/mapping.json +0 -0
  223. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/fixtures/oracle_mapping_golden/mapping.mcp.json +0 -0
  224. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/fixtures/simulate_v1_request.json +0 -0
  225. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/fixtures/vault_info_golden/clean.json +0 -0
  226. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/fixtures/vault_info_golden/clean.mcp.json +0 -0
  227. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/fixtures/vault_info_golden/clean.txt +0 -0
  228. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/fixtures/vault_info_golden/deployment_error.json +0 -0
  229. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/fixtures/vault_info_golden/deployment_error.mcp.json +0 -0
  230. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/fixtures/vault_info_golden/deployment_error.txt +0 -0
  231. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/fixtures/vault_info_golden/fetched.cli.json +0 -0
  232. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/fixtures/vault_info_golden/fetched.json +0 -0
  233. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/fixtures/vault_info_golden/fetched.mcp.json +0 -0
  234. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/fixtures/vault_info_golden/full.json +0 -0
  235. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/fixtures/vault_info_golden/full.mcp.json +0 -0
  236. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/fixtures/vault_info_golden/full.txt +0 -0
  237. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/fixtures/vault_info_golden/rich.json +0 -0
  238. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/fixtures/vault_info_golden/rich.mcp.json +0 -0
  239. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/fixtures/vault_info_golden/rich.txt +0 -0
  240. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_about.py +0 -0
  241. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_access_manager.py +0 -0
  242. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_addresses_helper.py +0 -0
  243. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_cache.py +0 -0
  244. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_ccip_read_disabled.py +0 -0
  245. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_chains.py +0 -0
  246. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_cli_address.py +0 -0
  247. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_cli_changelog.py +0 -0
  248. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_cli_commands.py +0 -0
  249. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_cli_config_store.py +0 -0
  250. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_cli_hypercore.py +0 -0
  251. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_cli_market_golden.py +0 -0
  252. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_cli_market_morpho_blue.py +0 -0
  253. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_cli_mcp_fuse_whitelist.py +0 -0
  254. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_cli_mcp_parity.py +0 -0
  255. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_cli_oracle_mapping.py +0 -0
  256. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_cli_oracle_mapping_golden.py +0 -0
  257. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_cli_pending_requests_scan.py +0 -0
  258. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_cli_preflight.py +0 -0
  259. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_cli_role_accounts.py +0 -0
  260. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_cli_vault_info_golden.py +0 -0
  261. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_cli_vault_predates_getters.py +0 -0
  262. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_cli_vault_validate.py +0 -0
  263. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_contract_call.py +0 -0
  264. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_contract_encoder.py +0 -0
  265. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_crosschain_ccip_events.py +0 -0
  266. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_crosschain_ccip_indexer.py +0 -0
  267. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_crosschain_ccip_quote.py +0 -0
  268. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_crosschain_chainlink.py +0 -0
  269. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_crosschain_contracts.py +0 -0
  270. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_crosschain_encoding.py +0 -0
  271. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_crosschain_errors.py +0 -0
  272. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_crosschain_lane.py +0 -0
  273. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_crosschain_layering.py +0 -0
  274. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_crosschain_transport.py +0 -0
  275. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_docs_typing.py +0 -0
  276. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_erc20.py +0 -0
  277. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_error_hierarchy.py +0 -0
  278. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_examples_compound_v3_ethereum.py +0 -0
  279. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_examples_crosschain.py +0 -0
  280. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_examples_euler_looping_arbitrum.py +0 -0
  281. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_examples_hypercore_hyperevm.py +0 -0
  282. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_examples_vaults.py +0 -0
  283. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_explorer.py +0 -0
  284. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_external_state_events.py +0 -0
  285. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_external_state_executor.py +0 -0
  286. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_fee_manager.py +0 -0
  287. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_fuse_decoding.py +0 -0
  288. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_fuse_encoding.py +0 -0
  289. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_fuse_whitelist.py +0 -0
  290. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_fusion_errors.py +0 -0
  291. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_fusion_instance.py +0 -0
  292. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_governance_wrappers.py +0 -0
  293. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_guide.py +0 -0
  294. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_hypercore_deployment.py +0 -0
  295. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_hypercore_events.py +0 -0
  296. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_hypercore_flow.py +0 -0
  297. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_hypercore_live.py +0 -0
  298. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_hypercore_outcome.py +0 -0
  299. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_hypercore_pre_hooks.py +0 -0
  300. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_hypercore_readers.py +0 -0
  301. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_hypercore_simulation.py +0 -0
  302. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_lending_health_compound_v3_onchain.py +0 -0
  303. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_logs.py +0 -0
  304. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_mcp_address.py +0 -0
  305. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_mcp_hypercore.py +0 -0
  306. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_mcp_preflight.py +0 -0
  307. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_mcp_server.py +0 -0
  308. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_mcp_vault_validate.py +0 -0
  309. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_multicall.py +0 -0
  310. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_onchain_text.py +0 -0
  311. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_oracle.py +0 -0
  312. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_oracle_mapping.py +0 -0
  313. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_plasma_vault.py +0 -0
  314. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_preflight.py +0 -0
  315. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_public_api.py +0 -0
  316. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_public_type_reachability.py +0 -0
  317. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_reader_encoding.py +0 -0
  318. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_registry.py +0 -0
  319. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_revert_decoding.py +0 -0
  320. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_rewards_manager.py +0 -0
  321. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_roles.py +0 -0
  322. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_sdk_layering.py +0 -0
  323. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_simulate_borrow_aave_v3_mainnet.py +0 -0
  324. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_simulate_claim_merkl_wrapper_base.py +0 -0
  325. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_simulate_claim_morpho_rewards.py +0 -0
  326. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_simulate_compound_v3_institutional_usdc.py +0 -0
  327. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_simulate_crosschain_hyperevm.py +0 -0
  328. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_simulate_euler_v2_batch_base.py +0 -0
  329. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_simulate_euler_v2_credit_market_base.py +0 -0
  330. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_simulate_euler_v2_swap_deploy_base.py +0 -0
  331. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_simulate_euler_v2_swap_reconfigure_base.py +0 -0
  332. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_simulate_euler_v2_swap_registry_base.py +0 -0
  333. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_simulate_examples_arbitrum.py +0 -0
  334. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_simulate_examples_base.py +0 -0
  335. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_simulate_examples_crosschain.py +0 -0
  336. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_simulate_examples_ethereum.py +0 -0
  337. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_simulate_examples_hyperevm.py +0 -0
  338. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_simulate_external_state_executor_base.py +0 -0
  339. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_simulate_fusion_factory_clone_base.py +0 -0
  340. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_simulate_looping_aave_v3_base.py +0 -0
  341. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_simulate_looping_morpho_blue_base.py +0 -0
  342. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_simulate_morpho_blue.py +0 -0
  343. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_simulate_pilot_v3.py +0 -0
  344. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_simulate_pilot_v4_uniswap.py +0 -0
  345. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_simulate_pilot_v5_ramses.py +0 -0
  346. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_simulate_pilot_v5_scheduled.py +0 -0
  347. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_simulate_plasma_vault_setters_base.py +0 -0
  348. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_simulate_preflight_base.py +0 -0
  349. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_simulate_readers_integration.py +0 -0
  350. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_simulate_release_funds.py +0 -0
  351. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_simulate_swap_on_base.py +0 -0
  352. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_simulate_timelock_base.py +0 -0
  353. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_simulate_vault_from_scratch_base.py +0 -0
  354. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_simulation_errors.py +0 -0
  355. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_simulation_observation.py +0 -0
  356. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_solidity_compiler.py +0 -0
  357. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_solidity_mirrors.py +0 -0
  358. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_substrates.py +0 -0
  359. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_types_and_events.py +0 -0
  360. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_unpriceable_tokens.py +0 -0
  361. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_validation.py +0 -0
  362. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_vault_data_block.py +0 -0
  363. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_vault_dep_graph.py +0 -0
  364. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_vault_info_fetched_golden.py +0 -0
  365. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_vault_info_onchain_base.py +0 -0
  366. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_vault_info_onchain_base_no_withdraw_manager.py +0 -0
  367. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_vault_reconciliation_onchain_base.py +0 -0
  368. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_vault_simulator_blocks.py +0 -0
  369. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_web3_context.py +0 -0
  370. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/test_withdraw_manager.py +0 -0
  371. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/typed_client/consumer.py +0 -0
  372. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/tests/typed_client/rejected.py +0 -0
  373. {ipor_fusion-4.2.0.dev16 → ipor_fusion-4.2.0.dev20}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: ipor-fusion
3
- Version: 4.2.0.dev16
3
+ Version: 4.2.0.dev20
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
@@ -260,7 +260,11 @@ def run_simulation(web3: Web3) -> SimulationResult:
260
260
  )
261
261
  factory = FusionFactory(ETHEREUM_FUSION_FACTORY)
262
262
 
263
- # Preview the deterministic addresses (eth_call, no gas, no state change).
263
+ # SIMULATION ONLY: the pinned replay has no other clone between this
264
+ # preview and the simulated one, so the predicted stack is the created one.
265
+ # Live, another clone in between moves it: send the clone first, take the
266
+ # stack from factory.decode_clone_receipt(receipt) and build the
267
+ # configuration and operation calldata from those addresses.
264
268
  preview = factory.clone(**clone_args()).call(ctx)
265
269
  log.info("predicted vault=%s", preview.plasma_vault)
266
270
 
@@ -278,6 +282,9 @@ def run_simulation(web3: Web3) -> SimulationResult:
278
282
  # (or X.calldata to a multisig / timelock).
279
283
  # - sim.execute([action]) -> plasma_vault.execute([action]).send(ctx) by the alpha.
280
284
  # - read = sim.observe(X), then read.value -> X.call(ctx), a plain read.
285
+ # SIMULATION ONLY (per-call senders): `from_` lets one process act as every
286
+ # actor, because in simulation they are addresses with no keys. In production
287
+ # they are separate signers on separate systems.
281
288
  sim = VaultSimulator(
282
289
  web3=web3, vault=vault_address, alpha=ALPHA, block=PINNED_BLOCK
283
290
  )
@@ -311,7 +318,8 @@ def run_simulation(web3: Web3) -> SimulationResult:
311
318
  # UPDATE_MARKETS_BALANCES}, so OWNER must self-grant ATOMIST before it can
312
319
  # grant the rest. Each grant uses Period(0) (no execution delay) to keep the
313
320
  # simulation legible; production governance often puts a nonzero delay -- a
314
- # timelock -- on the sensitive roles.
321
+ # timelock -- on the sensitive roles. ALPHA must stay a separate address from
322
+ # OWNER in production: a compromised alpha key must not equal governance.
315
323
  for role, holder in (
316
324
  (Roles.ATOMIST_ROLE, OWNER),
317
325
  (Roles.FUSE_MANAGER_ROLE, OWNER),
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "ipor-fusion"
3
- version = "4.2.0.dev16"
3
+ version = "4.2.0.dev20"
4
4
  description = "The IPOR Fusion SDK for Python"
5
5
  authors = [
6
6
  { name = "Adam Mydlarz", email = "amydlarz@gmail.com" },
@@ -298,6 +298,8 @@ def _compute_morpho_market_health(
298
298
  position.borrow_shares, market.total_borrow_assets, market.total_borrow_shares
299
299
  )
300
300
 
301
+ # Morpho's own check sees a zero borrow limit here, so the position can be
302
+ # liquidated: a known risk, of which only the LTV has no value.
301
303
  if position.collateral == 0 or oracle_price == 0:
302
304
  return LendingMarketHealth(
303
305
  protocol="morpho",
@@ -305,7 +307,7 @@ def _compute_morpho_market_health(
305
307
  market_name=market_name,
306
308
  current_ltv=None,
307
309
  max_ltv=params.lltv / WAD,
308
- health_factor=None,
310
+ health_factor=0.0,
309
311
  total_collateral_usd=None,
310
312
  total_debt_usd=None,
311
313
  ltv_usage_percent=None,
@@ -322,9 +324,9 @@ def _compute_morpho_market_health(
322
324
  max_ltv = params.lltv / WAD
323
325
 
324
326
  # health_factor = max_borrow / borrowed
325
- # max_borrow = collateral * oracle_price / ORACLE_PRICE_SCALE * lltv / WAD
327
+ # Two floors, as in Morpho's _isHealthy.
326
328
  max_borrow = (
327
- position.collateral * oracle_price * params.lltv // (ORACLE_PRICE_SCALE * WAD)
329
+ position.collateral * oracle_price // ORACLE_PRICE_SCALE * params.lltv // WAD
328
330
  )
329
331
  health_factor = max_borrow / borrowed if borrowed > 0 else None
330
332
 
@@ -336,7 +338,7 @@ def _compute_morpho_market_health(
336
338
  market_name=market_name,
337
339
  current_ltv=round(current_ltv, 6),
338
340
  max_ltv=round(max_ltv, 6),
339
- health_factor=round(health_factor, 4) if health_factor else None,
341
+ health_factor=round(health_factor, 4) if health_factor is not None else None,
340
342
  total_collateral_usd=None,
341
343
  total_debt_usd=None,
342
344
  ltv_usage_percent=round(ltv_usage, 2) if ltv_usage is not None else None,
@@ -574,9 +576,17 @@ def _compute_euler_v2_account_health(
574
576
  if ltv > 0
575
577
  )
576
578
  liability = liquidity.liability_value
579
+ # The vault owes, yet its oracle values that at nothing: no ratio means anything.
580
+ if liability == 0:
581
+ return UnreadableLendingMarket(
582
+ protocol="euler_v2",
583
+ market_id=ipor_market_id,
584
+ market_name=market_name,
585
+ reason="debt is valued at zero",
586
+ substrate_id=sub_account,
587
+ )
577
588
  current_ltv = liability / collateral if collateral > 0 else None
578
589
  max_ltv = adjusted / collateral if collateral > 0 else 0.0
579
- health_factor = adjusted / liability if liability > 0 else None
580
590
  ltv_usage = liability / adjusted * 100 if adjusted > 0 else None
581
591
  # The USD unit of account prices in 18 decimals; other units are not USD.
582
592
  in_usd = unit_read.value == EULER_USD_UNIT_OF_ACCOUNT
@@ -587,7 +597,7 @@ def _compute_euler_v2_account_health(
587
597
  market_name=market_name,
588
598
  current_ltv=round(current_ltv, 6) if current_ltv is not None else None,
589
599
  max_ltv=round(max_ltv, 6),
590
- health_factor=round(health_factor, 4) if health_factor is not None else None,
600
+ health_factor=round(adjusted / liability, 4),
591
601
  total_collateral_usd=round(collateral / WAD, 2) if in_usd else None,
592
602
  total_debt_usd=round(liability / WAD, 2) if in_usd else None,
593
603
  ltv_usage_percent=round(ltv_usage, 2) if ltv_usage is not None else None,
@@ -691,8 +691,9 @@ def _lending_health_findings(state: VaultState) -> list[Finding]:
691
691
  for u in health.unreadable
692
692
  ]
693
693
  for market in health.markets:
694
- # A critical row with no usage (debt against a zero limit) is still critical.
695
- if market.ltv_usage_percent is None and not market.is_critical:
694
+ # A row with neither value gives no risk metric to judge. Debt against a zero
695
+ # limit has no usage but a critical health factor.
696
+ if market.ltv_usage_percent is None and market.health_factor is None:
696
697
  continue
697
698
  ltv = f"{market.current_ltv:.4f}" if market.current_ltv is not None else "?"
698
699
  usage = (
@@ -1532,6 +1532,18 @@ class TestPrintLendingHealth:
1532
1532
  _print_lending_health(data)
1533
1533
  assert "N/A" in capsys.readouterr().out
1534
1534
 
1535
+ def test_debt_without_ltv_is_not_shown_as_no_debt(self, capsys):
1536
+ m = _make_lending_market(
1537
+ current_ltv=None, health_factor=0.0, ltv_usage_percent=None
1538
+ )
1539
+ m.protocol = "aave_v3"
1540
+ m.market_id = MarketId(1)
1541
+ data = _make_data(lending_health=VaultLendingHealth(markets=[m]))
1542
+ _print_lending_health(data)
1543
+ out = capsys.readouterr().out
1544
+ assert "CRITICAL" in out
1545
+ assert "no debt" not in out
1546
+
1535
1547
 
1536
1548
  class TestHealthCheckLendingIntegration:
1537
1549
  def test_lending_ok_in_health_check(self, capsys):
@@ -1556,6 +1568,22 @@ class TestHealthCheckLendingIntegration:
1556
1568
  assert "CRITICAL" in captured.out
1557
1569
  assert "NEAR LIQUIDATION" in captured.out
1558
1570
 
1571
+ def test_debt_without_ltv_is_critical_not_ok(self, capsys):
1572
+ m = _make_lending_market(
1573
+ current_ltv=None, ltv_usage_percent=None, health_factor=0.0
1574
+ )
1575
+ data = _make_data(lending_health=VaultLendingHealth(markets=[m]))
1576
+ _print_health_check(validate(data, 100 * 10**18))
1577
+ captured = capsys.readouterr()
1578
+ assert "CRITICAL" in captured.out
1579
+ assert "NEAR LIQUIDATION" in captured.out
1580
+
1581
+ def test_lending_warning_without_usage_is_reported(self, capsys):
1582
+ m = _make_lending_market(ltv_usage_percent=None, health_factor=1.08)
1583
+ data = _make_data(lending_health=VaultLendingHealth(markets=[m]))
1584
+ _print_health_check(validate(data, 100 * 10**18))
1585
+ assert "approaching liquidation" in capsys.readouterr().out
1586
+
1559
1587
  def test_lending_none_usage_skipped(self, capsys):
1560
1588
  m = _make_lending_market(ltv_usage_percent=None, health_factor=None)
1561
1589
  data = _make_data(lending_health=VaultLendingHealth(markets=[m]))
@@ -419,6 +419,115 @@ class TestComputeMorphoMarketHealth:
419
419
  assert isinstance(result, LendingMarketHealth)
420
420
  assert result.current_ltv is None
421
421
  assert result.ltv_usage_percent is None
422
+ assert result.health_factor == 0.0
423
+ assert result.is_critical
424
+
425
+ def test_zero_oracle_price_with_debt_is_critical(self):
426
+ reader, ctx = _make_morpho_reader()
427
+
428
+ ctx.call.side_effect = sequenced(
429
+ [
430
+ encode(["uint256", "uint128", "uint128"], [0, 100, 1000]),
431
+ encode(
432
+ ["uint128", "uint128", "uint128", "uint128", "uint128", "uint128"],
433
+ [2000, 2000, 1000, 1000, 1700000000, 0],
434
+ ),
435
+ encode(
436
+ ["address", "address", "address", "address", "uint256"],
437
+ [TOKEN_A, TOKEN_B, ORACLE_ADDR, IRM_ADDR, 860000000000000000],
438
+ ),
439
+ encode(["uint256"], [0]),
440
+ ]
441
+ )
442
+
443
+ result = _compute_morpho_market_health(
444
+ ctx, reader, MORPHO_MARKET_ID, VAULT_ADDR, 14, "MORPHO"
445
+ )
446
+
447
+ assert isinstance(result, LendingMarketHealth)
448
+ assert result.current_ltv is None
449
+ assert result.health_factor == 0.0
450
+ assert result.is_critical
451
+
452
+ def test_dust_collateral_flooring_the_limit_to_zero_is_critical(self):
453
+ reader, ctx = _make_morpho_reader()
454
+
455
+ ctx.call.side_effect = sequenced(
456
+ [
457
+ encode(["uint256", "uint128", "uint128"], [0, 500 * 10**6, 1]),
458
+ encode(
459
+ ["uint128", "uint128", "uint128", "uint128", "uint128", "uint128"],
460
+ [2000, 2000 * 10**6, 1000, 1000 * 10**6, 1700000000, 0],
461
+ ),
462
+ encode(
463
+ ["address", "address", "address", "address", "uint256"],
464
+ [TOKEN_A, TOKEN_B, ORACLE_ADDR, IRM_ADDR, 860000000000000000],
465
+ ),
466
+ encode(["uint256"], [ORACLE_PRICE_SCALE]),
467
+ ]
468
+ )
469
+
470
+ result = _compute_morpho_market_health(
471
+ ctx, reader, MORPHO_MARKET_ID, VAULT_ADDR, 14, "MORPHO"
472
+ )
473
+
474
+ assert isinstance(result, LendingMarketHealth)
475
+ assert result.current_ltv is not None
476
+ assert result.health_factor == 0.0
477
+ assert result.is_critical
478
+
479
+ def test_limit_floors_twice_as_morpho_does(self):
480
+ # 2 * 0.9 floors to 1, then 1 * 0.86 floors to 0; one combined floor gives 1.
481
+ reader, ctx = _make_morpho_reader()
482
+
483
+ ctx.call.side_effect = sequenced(
484
+ [
485
+ encode(["uint256", "uint128", "uint128"], [0, 10**6, 2]),
486
+ encode(
487
+ ["uint128", "uint128", "uint128", "uint128", "uint128", "uint128"],
488
+ [2000, 2000 * 10**6, 1000, 1000 * 10**6, 1700000000, 0],
489
+ ),
490
+ encode(
491
+ ["address", "address", "address", "address", "uint256"],
492
+ [TOKEN_A, TOKEN_B, ORACLE_ADDR, IRM_ADDR, 860000000000000000],
493
+ ),
494
+ encode(["uint256"], [9 * ORACLE_PRICE_SCALE // 10]),
495
+ ]
496
+ )
497
+
498
+ result = _compute_morpho_market_health(
499
+ ctx, reader, MORPHO_MARKET_ID, VAULT_ADDR, 14, "MORPHO"
500
+ )
501
+
502
+ assert isinstance(result, LendingMarketHealth)
503
+ assert result.health_factor == 0.0
504
+
505
+ def test_debt_on_a_zero_lltv_market_is_critical_without_usage(self):
506
+ reader, ctx = _make_morpho_reader()
507
+
508
+ ctx.call.side_effect = sequenced(
509
+ [
510
+ encode(["uint256", "uint128", "uint128"], [0, 500 * 10**6, 1000]),
511
+ encode(
512
+ ["uint128", "uint128", "uint128", "uint128", "uint128", "uint128"],
513
+ [2000, 2000 * 10**6, 1000, 1000 * 10**6, 1700000000, 0],
514
+ ),
515
+ encode(
516
+ ["address", "address", "address", "address", "uint256"],
517
+ [TOKEN_A, TOKEN_B, ORACLE_ADDR, IRM_ADDR, 0],
518
+ ),
519
+ encode(["uint256"], [ORACLE_PRICE_SCALE]),
520
+ ]
521
+ )
522
+
523
+ result = _compute_morpho_market_health(
524
+ ctx, reader, MORPHO_MARKET_ID, VAULT_ADDR, 14, "MORPHO"
525
+ )
526
+
527
+ assert isinstance(result, LendingMarketHealth)
528
+ assert result.ltv_usage_percent is None
529
+ assert result.health_factor == 0.0
530
+ assert result.is_critical
422
531
 
423
532
  def test_position_read_failure_is_unreadable(self):
424
533
  reader, ctx = _make_morpho_reader()
@@ -1162,6 +1271,8 @@ def _euler_ctx(
1162
1271
  debt: int = EULER_DEBT,
1163
1272
  unit: str = EULER_USD_UNIT_OF_ACCOUNT,
1164
1273
  liquidity_reverts: bool = False,
1274
+ adjusted: int = EULER_ADJUSTED_COLLATERAL,
1275
+ liability: int = EULER_LIABILITY,
1165
1276
  ) -> MagicMock:
1166
1277
  """Answer the TESS sub-account's reads; only the borrow vault is its controller."""
1167
1278
 
@@ -1177,7 +1288,7 @@ def _euler_ctx(
1177
1288
  raise ContractLogicError("E_NoLiability")
1178
1289
  return encode(
1179
1290
  ["address[]", "uint256[]", "uint256"],
1180
- [[EULER_COLLATERAL], [EULER_ADJUSTED_COLLATERAL], EULER_LIABILITY],
1291
+ [[EULER_COLLATERAL], [adjusted], liability],
1181
1292
  )
1182
1293
  if selector == _SELECTOR_UNIT_OF_ACCOUNT:
1183
1294
  return encode(["address"], [unit])
@@ -1241,6 +1352,19 @@ class TestEulerV2Health:
1241
1352
  assert result.total_collateral_usd is None
1242
1353
  assert result.total_debt_usd is None
1243
1354
 
1355
+ @pytest.mark.parametrize("adjusted", [0, EULER_ADJUSTED_COLLATERAL])
1356
+ def test_debt_valued_at_zero_is_unreadable_not_no_debt(self, adjusted):
1357
+ result = _compute_euler_v2_account_health(
1358
+ _euler_ctx(adjusted=adjusted, liability=0),
1359
+ EULER_ACCOUNT,
1360
+ MarketId(11),
1361
+ "EULER_V2",
1362
+ )
1363
+
1364
+ assert isinstance(result, UnreadableLendingMarket)
1365
+ assert result.substrate_id == EULER_SUB_ACCOUNT
1366
+ assert result.reason == "debt is valued at zero"
1367
+
1244
1368
  def test_failed_debt_read_is_unreadable(self):
1245
1369
  ctx = MagicMock()
1246
1370
  ctx.call.side_effect = Web3RPCError("rpc down")
@@ -33,6 +33,7 @@ from ipor_fusion import (
33
33
  ExecutePreflightJson,
34
34
  Fee,
35
35
  FuseAddress,
36
+ IporFusionMarkets,
36
37
  MarketId,
37
38
  MetaMorphoV1Json,
38
39
  MetaMorphoV2Json,
@@ -72,6 +73,12 @@ from ipor_fusion.mcp.models import (
72
73
  VaultValidationResponse,
73
74
  WithdrawManagerDetails,
74
75
  )
76
+ from ipor_fusion.readers.lending_health import (
77
+ LendingMarketHealth as LendingMarketHealthRow,
78
+ )
79
+ from ipor_fusion.readers.lending_health import (
80
+ VaultLendingHealth,
81
+ )
75
82
  from ipor_fusion.readers.oracle_mapping import (
76
83
  OracleAsset,
77
84
  OracleMapping,
@@ -79,6 +86,7 @@ from ipor_fusion.readers.oracle_mapping import (
79
86
  OraclePrice,
80
87
  )
81
88
  from ipor_fusion.types import Period, RoleId
89
+ from ipor_fusion.vault_info import _lending_health_json
82
90
 
83
91
 
84
92
  def _amount(raw: int = 0, formatted: str = "0") -> AmountJson:
@@ -1307,3 +1315,26 @@ class TestModelsImportGraph:
1307
1315
  }
1308
1316
  )
1309
1317
  assert offending == []
1318
+
1319
+
1320
+ def test_debt_without_ltv_passes_through_the_published_model() -> None:
1321
+ row = LendingMarketHealthRow(
1322
+ protocol="morpho",
1323
+ market_id=IporFusionMarkets.MORPHO,
1324
+ market_name="MORPHO",
1325
+ current_ltv=None,
1326
+ max_ltv=0.86,
1327
+ health_factor=0.0,
1328
+ total_collateral_usd=None,
1329
+ total_debt_usd=None,
1330
+ ltv_usage_percent=None,
1331
+ substrate_id="0x" + "ab" * 32,
1332
+ )
1333
+
1334
+ document = _lending_health_json(VaultLendingHealth(markets=[row]))
1335
+ model = mcp_models.LendingHealth.model_validate(document)
1336
+
1337
+ market = model.markets[0]
1338
+ assert market.health_factor == 0.0
1339
+ assert market.current_ltv is None
1340
+ assert market.is_critical