ipor-fusion 4.0.0.dev83__tar.gz → 4.0.0.dev86__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 (294) hide show
  1. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/MIGRATION_4.md +51 -9
  2. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/PKG-INFO +1 -1
  3. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/pyproject.toml +1 -1
  4. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/__init__.py +8 -0
  5. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/core/external_state_executor.py +66 -37
  6. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/core/simulation.py +9 -6
  7. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/crosschain/ccip/chainlink.py +5 -4
  8. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/crosschain/ccip/indexer.py +51 -16
  9. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/crosschain/ccip/quote.py +7 -4
  10. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/errors.py +51 -11
  11. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/mcp/server.py +1 -4
  12. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/readers/hypercore.py +19 -8
  13. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/readers/hypercore_outcome.py +4 -1
  14. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_crosschain_ccip_indexer.py +52 -0
  15. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_crosschain_ccip_quote.py +6 -3
  16. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_crosschain_chainlink.py +2 -1
  17. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_error_hierarchy.py +8 -0
  18. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_external_state_executor.py +32 -28
  19. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_hypercore_outcome.py +12 -1
  20. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_hypercore_readers.py +17 -3
  21. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_mcp_server.py +1 -5
  22. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_public_api.py +4 -0
  23. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_vault_simulator_blocks.py +3 -3
  24. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/.claude-plugin/marketplace.json +0 -0
  25. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/.claude-plugin/plugin.json +0 -0
  26. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/.env.example +0 -0
  27. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/.gemini/settings.json +0 -0
  28. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/.git-blame-ignore-revs +0 -0
  29. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/.github/workflows/cd.yml +0 -0
  30. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/.github/workflows/ci.yml +0 -0
  31. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/.github/workflows/commit-lint.yml +0 -0
  32. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/.github/workflows/notify-slack.yml +0 -0
  33. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/.github/workflows/python-build.yml +0 -0
  34. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/.github/workflows/python-publish.yml +0 -0
  35. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/.github/workflows/release.yml +0 -0
  36. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/.github/workflows/report-slack.yml +0 -0
  37. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/.gitignore +0 -0
  38. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/.mcp.json +0 -0
  39. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/.pre-commit-config.yaml +0 -0
  40. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/.python-version +0 -0
  41. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/AGENTS.md +0 -0
  42. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/CHANGELOG.md +0 -0
  43. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/CLAUDE.md +0 -0
  44. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/CODEOWNERS +0 -0
  45. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/LICENSE +0 -0
  46. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/README.md +0 -0
  47. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/build.sh +0 -0
  48. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/examples/CONTRIBUTING.md +0 -0
  49. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/examples/README.md +0 -0
  50. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/examples/advanced_euler_v2_credit_market_base.py +0 -0
  51. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/examples/advanced_euler_v2_looping_arbitrum.py +0 -0
  52. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/examples/composed_hypercore_flow_arbitrum_hyperevm.py +0 -0
  53. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/examples/crosschain_ccip_usdc_arbitrum_hyperevm.py +0 -0
  54. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/examples/external_state_margin_leg_base.py +0 -0
  55. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/examples/simple_aave_v3_supply_base.py +0 -0
  56. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/pytest.ini +0 -0
  57. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/scripts/commit-check.sh +0 -0
  58. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/scripts/crosschain_readiness.py +0 -0
  59. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/skills/ipor-deploy-vault/SKILL.md +0 -0
  60. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/about.py +0 -0
  61. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/cache.py +0 -0
  62. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/chains.py +0 -0
  63. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/cli/__init__.py +0 -0
  64. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/cli/changelog_cmd.py +0 -0
  65. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/cli/config_cmd.py +0 -0
  66. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/cli/config_store.py +0 -0
  67. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/cli/main.py +0 -0
  68. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/cli/market_cmd.py +0 -0
  69. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/cli/options.py +0 -0
  70. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/cli/oracle_mapping_view.py +0 -0
  71. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/cli/vault_cmd.py +0 -0
  72. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/cli/vault_health.py +0 -0
  73. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/cli/vault_info_view.py +0 -0
  74. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/cli/vault_markets_view.py +0 -0
  75. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/cli/vault_rendering.py +0 -0
  76. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/config/__init__.py +0 -0
  77. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/config/roles.py +0 -0
  78. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/core/__init__.py +0 -0
  79. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/core/access.py +0 -0
  80. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/core/address.py +0 -0
  81. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/core/context.py +0 -0
  82. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/core/contract.py +0 -0
  83. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/core/erc20.py +0 -0
  84. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/core/fee_manager.py +0 -0
  85. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/core/fuse_action.py +0 -0
  86. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/core/fusion_factory.py +0 -0
  87. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/core/logs.py +0 -0
  88. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/core/multicall.py +0 -0
  89. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/core/oracle.py +0 -0
  90. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/core/plasma_vault.py +0 -0
  91. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/core/rewards_manager.py +0 -0
  92. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/core/withdraw_manager.py +0 -0
  93. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/crosschain/__init__.py +0 -0
  94. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/crosschain/ccip/__init__.py +0 -0
  95. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/crosschain/ccip/codec.py +0 -0
  96. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/crosschain/ccip/contracts.py +0 -0
  97. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/crosschain/ccip/events.py +0 -0
  98. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/crosschain/ccip/lane.py +0 -0
  99. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/crosschain/ccip/transport.py +0 -0
  100. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/crosschain/contracts.py +0 -0
  101. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/crosschain/discovery.py +0 -0
  102. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/crosschain/errors.py +0 -0
  103. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/crosschain/lane.py +0 -0
  104. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/crosschain/logs.py +0 -0
  105. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/crosschain/messages.py +0 -0
  106. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/crosschain/simulation.py +0 -0
  107. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/crosschain/stargate/__init__.py +0 -0
  108. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/crosschain/stargate/contracts.py +0 -0
  109. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/crosschain/stargate/lane.py +0 -0
  110. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/crosschain/stargate/layerzero.py +0 -0
  111. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/crosschain/stargate/transport.py +0 -0
  112. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/crosschain/transport.py +0 -0
  113. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/dependency_graph.py +0 -0
  114. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/entry_cli.py +0 -0
  115. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/entry_mcp.py +0 -0
  116. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/explorer.py +0 -0
  117. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/field_docs.py +0 -0
  118. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/fuses/__init__.py +0 -0
  119. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/fuses/aave_v3.py +0 -0
  120. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/fuses/async_action.py +0 -0
  121. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/fuses/base.py +0 -0
  122. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/fuses/compound_v3.py +0 -0
  123. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/fuses/crosschain/__init__.py +0 -0
  124. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/fuses/crosschain/base.py +0 -0
  125. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/fuses/crosschain/ccip.py +0 -0
  126. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/fuses/crosschain/stargate.py +0 -0
  127. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/fuses/erc4626.py +0 -0
  128. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/fuses/euler_v2.py +0 -0
  129. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/fuses/events.py +0 -0
  130. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/fuses/external_state.py +0 -0
  131. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/fuses/external_state_events.py +0 -0
  132. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/fuses/fluid_instadapp.py +0 -0
  133. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/fuses/gearbox_v3.py +0 -0
  134. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/fuses/hypercore.py +0 -0
  135. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/fuses/hypercore_events.py +0 -0
  136. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/fuses/merkl.py +0 -0
  137. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/fuses/morpho.py +0 -0
  138. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/fuses/ramses_v2.py +0 -0
  139. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/fuses/uniswap_v3.py +0 -0
  140. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/fuses/universal.py +0 -0
  141. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/guide/__init__.py +0 -0
  142. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/guide/architecture.md +0 -0
  143. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/guide/glossary.md +0 -0
  144. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/guide/invariants.md +0 -0
  145. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/guide/quickstart.md +0 -0
  146. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/market_ids.py +0 -0
  147. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/market_info.py +0 -0
  148. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/mcp/__init__.py +0 -0
  149. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/mcp/guide.py +0 -0
  150. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/mcp/models.py +0 -0
  151. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/morpho_api.py +0 -0
  152. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/py.typed +0 -0
  153. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/readers/__init__.py +0 -0
  154. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/readers/aave_v3.py +0 -0
  155. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/readers/aave_v4.py +0 -0
  156. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/readers/compound_v3.py +0 -0
  157. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/readers/lending_health.py +0 -0
  158. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/readers/morpho.py +0 -0
  159. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/readers/oracle_mapping.py +0 -0
  160. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/readers/oracle_mapping_nodes.py +0 -0
  161. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/readers/oracle_mapping_reader.py +0 -0
  162. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/readers/position_manager.py +0 -0
  163. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/readers/ramses_v2.py +0 -0
  164. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/readers/uniswap_v3.py +0 -0
  165. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/readers/unpriceable.py +0 -0
  166. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/substrates.py +0 -0
  167. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/types.py +0 -0
  168. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/validation.py +0 -0
  169. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/vault_data.py +0 -0
  170. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/vault_info.py +0 -0
  171. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/vault_managers.py +0 -0
  172. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/vault_positions.py +0 -0
  173. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/vault_reads.py +0 -0
  174. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/UniswapV3UniversalRouter.py +0 -0
  175. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/_crosschain.py +0 -0
  176. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/_crosschain_lifecycle.py +0 -0
  177. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/_crosschain_recovery.py +0 -0
  178. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/_euler_v2.py +0 -0
  179. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/_foundry.py +0 -0
  180. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/_guide.py +0 -0
  181. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/_hypercore_model.py +0 -0
  182. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/_hypercore_shadow.py +0 -0
  183. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/_multicall.py +0 -0
  184. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/_simulate.py +0 -0
  185. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/_validation.py +0 -0
  186. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/addresses.py +0 -0
  187. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/conftest.py +0 -0
  188. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/constants.py +0 -0
  189. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/fixtures/hypercore_hip3_flow.json +0 -0
  190. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/fixtures/hypercore_market55.json +0 -0
  191. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/fixtures/vault_info_golden/clean.json +0 -0
  192. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/fixtures/vault_info_golden/clean.txt +0 -0
  193. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/fixtures/vault_info_golden/rich.json +0 -0
  194. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/fixtures/vault_info_golden/rich.txt +0 -0
  195. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_about.py +0 -0
  196. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_access_manager.py +0 -0
  197. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_cache.py +0 -0
  198. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_chains.py +0 -0
  199. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_cli_changelog.py +0 -0
  200. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_cli_commands.py +0 -0
  201. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_cli_config_store.py +0 -0
  202. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_cli_helpers.py +0 -0
  203. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_cli_hypercore.py +0 -0
  204. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_cli_lending_health.py +0 -0
  205. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_cli_market_morpho_blue.py +0 -0
  206. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_cli_oracle_mapping.py +0 -0
  207. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_cli_pending_requests_scan.py +0 -0
  208. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_cli_role_accounts.py +0 -0
  209. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_cli_vault_info_golden.py +0 -0
  210. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_cli_vault_predates_getters.py +0 -0
  211. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_cli_vault_validate.py +0 -0
  212. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_contract_call.py +0 -0
  213. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_contract_encoder.py +0 -0
  214. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_crosschain_ccip_events.py +0 -0
  215. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_crosschain_contracts.py +0 -0
  216. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_crosschain_encoding.py +0 -0
  217. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_crosschain_errors.py +0 -0
  218. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_crosschain_lane.py +0 -0
  219. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_crosschain_layering.py +0 -0
  220. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_crosschain_readiness.py +0 -0
  221. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_crosschain_readiness_report.py +0 -0
  222. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_crosschain_transport.py +0 -0
  223. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_erc20.py +0 -0
  224. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_examples_composed_hypercore.py +0 -0
  225. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_examples_crosschain.py +0 -0
  226. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_examples_euler_looping_arbitrum.py +0 -0
  227. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_examples_vaults.py +0 -0
  228. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_explorer.py +0 -0
  229. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_external_state_events.py +0 -0
  230. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_fee_manager.py +0 -0
  231. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_fuse_encoding.py +0 -0
  232. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_fusion_instance.py +0 -0
  233. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_guide.py +0 -0
  234. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_hypercore_deployment.py +0 -0
  235. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_hypercore_events.py +0 -0
  236. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_hypercore_flow.py +0 -0
  237. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_hypercore_live.py +0 -0
  238. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_hypercore_simulation.py +0 -0
  239. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_logs.py +0 -0
  240. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_mcp_hypercore.py +0 -0
  241. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_mcp_models.py +0 -0
  242. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_mcp_vault_validate.py +0 -0
  243. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_multicall.py +0 -0
  244. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_oracle.py +0 -0
  245. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_oracle_mapping.py +0 -0
  246. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_plasma_vault.py +0 -0
  247. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_reader_encoding.py +0 -0
  248. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_readiness_selection.py +0 -0
  249. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_revert_decoding.py +0 -0
  250. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_rewards_manager.py +0 -0
  251. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_roles.py +0 -0
  252. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_sdk_layering.py +0 -0
  253. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_simulate_borrow_aave_v3_mainnet.py +0 -0
  254. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_simulate_claim_merkl_wrapper_base.py +0 -0
  255. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_simulate_claim_morpho_rewards.py +0 -0
  256. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_simulate_crosschain_canary.py +0 -0
  257. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_simulate_crosschain_hypercore.py +0 -0
  258. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_simulate_crosschain_hyperevm.py +0 -0
  259. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_simulate_crosschain_lifecycle.py +0 -0
  260. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_simulate_euler_v2_batch_base.py +0 -0
  261. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_simulate_euler_v2_credit_market_base.py +0 -0
  262. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_simulate_euler_v2_swap_deploy_base.py +0 -0
  263. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_simulate_euler_v2_swap_reconfigure_base.py +0 -0
  264. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_simulate_euler_v2_swap_registry_base.py +0 -0
  265. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_simulate_examples_arbitrum.py +0 -0
  266. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_simulate_examples_base.py +0 -0
  267. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_simulate_examples_crosschain.py +0 -0
  268. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_simulate_external_state_executor_base.py +0 -0
  269. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_simulate_fusion_factory_clone_base.py +0 -0
  270. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_simulate_looping_aave_v3_base.py +0 -0
  271. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_simulate_looping_morpho_blue_base.py +0 -0
  272. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_simulate_morpho_blue.py +0 -0
  273. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_simulate_pilot_v3.py +0 -0
  274. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_simulate_pilot_v4_uniswap.py +0 -0
  275. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_simulate_pilot_v5_ramses.py +0 -0
  276. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_simulate_pilot_v5_scheduled.py +0 -0
  277. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_simulate_plasma_vault_setters_base.py +0 -0
  278. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_simulate_readers_integration.py +0 -0
  279. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_simulate_release_funds.py +0 -0
  280. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_simulate_swap_on_base.py +0 -0
  281. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_simulate_vault_from_scratch_base.py +0 -0
  282. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_solidity_compiler.py +0 -0
  283. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_solidity_mirrors.py +0 -0
  284. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_substrates.py +0 -0
  285. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_types_and_events.py +0 -0
  286. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_unpriceable_tokens.py +0 -0
  287. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_validation.py +0 -0
  288. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_vault_dep_graph.py +0 -0
  289. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_vault_info_onchain_base.py +0 -0
  290. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_vault_info_onchain_base_no_withdraw_manager.py +0 -0
  291. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_web3_context.py +0 -0
  292. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_withdraw_manager.py +0 -0
  293. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/typed_client/consumer.py +0 -0
  294. {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/uv.lock +0 -0
@@ -238,15 +238,20 @@ except below): `chain_id` fields are `ChainId`, `role_id` `RoleId`, `market_id`
238
238
  ## Errors
239
239
 
240
240
  Every exception class the SDK defines now derives from `IporFusionError` and is
241
- importable from `ipor_fusion`; the builtin bases they had are kept. The rule the
242
- changes below apply is: a caller's mistake (a bad argument, or an operation the
243
- object was not built for) raises a plain `ValueError`; a chain, provider or remote
244
- API answering something the SDK cannot use raises an `IporFusionError` subclass.
245
- 4.0 applies it to the paths listed here, not yet everywhere: the HyperCore NAV
246
- reader (`read_hypercore_nav`) still raises `ValueError` for a failed precompile read,
247
- a missing oracle price or an unsupported dex configuration,
248
- `fetch_verifier_result` can still raise `KeyError` or `ValueError` on a malformed
249
- indexer answer, and `erc20_balance_slot` raises `ValueError` when no slot answers.
241
+ importable from `ipor_fusion`; the builtin bases they had are kept. One rule holds
242
+ across the whole SDK: a caller's mistake (a bad argument, or an operation the object
243
+ was not built for) raises a plain `ValueError` (`TypeError` for an argument of the
244
+ wrong type, `KeyError` for an unknown name in a mapping-style lookup such as
245
+ `ipor_fusion.guide.guide_text`); a chain, provider or remote API answering something the
246
+ SDK cannot use raises an `IporFusionError` subclass. Except for
247
+ `CrosschainDiscoveryError` (discovery) and `SimulationError` (`quote_ccip_native_fee`),
248
+ every subclass that replaces a `ValueError` raise also derives from `ValueError`, so
249
+ `except ValueError` keeps catching it; catch the subclass to tell a chain fact from a
250
+ bad argument. Receipts,
251
+ logs and transports passed in that do not contain what the call needs (a clone
252
+ receipt without its events, logs without the lane's `BalanceProposed`, a CCIP
253
+ message to a chain the transport does not configure) stay `ValueError`: the input
254
+ is the wrong one.
250
255
 
251
256
  - `MorphoApiError` (still a `RuntimeError`), `ExternalStateEventDecodeError` (still a
252
257
  `ValueError`) and `VerifierResultUnavailable` (still a `LookupError`) now derive
@@ -274,3 +279,40 @@ indexer answer, and `erc20_balance_slot` raises `ValueError` when no slot answer
274
279
  - The Morpho API client raises `MorphoApiError` for every response it cannot read and
275
280
  for any `OSError` or `http.client.HTTPException` while fetching (it caught only
276
281
  `URLError` and `TimeoutError` before).
282
+ - The MCP `market_meta_morpho` tool lets `MorphoApiError` propagate instead of
283
+ re-raising it as `ValueError`. MCP clients see the same tool error message; only a
284
+ direct Python call of the tool function sees the new type.
285
+ - `read_hypercore_nav` (and `read_hypercore_vault_state`, which calls it) raises the new
286
+ `HyperCoreValuationError` (still a `ValueError`) for a failed `spotBalance`,
287
+ `tokenInfo` or `accountMarginSummary` precompile read, a granted spot token the
288
+ oracle does not price, a price overflow and an unsupported HIP-3 dex bitmap;
289
+ `HyperCoreNav.balance_wad` raises it for a negative NAV.
290
+ - `read_hypercore_transfer_snapshot` raises `UnsupportedChainError` (still a
291
+ `ValueError`) on a chain with no known `HyperCorePendingReader` when none is passed.
292
+ - `fetch_verifier_result` no longer leaks `KeyError`, `TypeError` or `ValueError` from
293
+ an indexer answer it cannot read (not JSON, a missing or mistyped field, invalid hex
294
+ or address, an empty or non-array CCV list, or a result for a different message id,
295
+ both now checked): that
296
+ indexer is skipped like an unreachable one, and when none serves a readable result
297
+ it raises `VerifierResultUnavailable` naming each indexer's reason. A `message_id`
298
+ that is not 32 bytes is still a `ValueError`.
299
+ - `erc20_balance_slot` raises the new `BalanceSlotNotFoundError` (still a
300
+ `ValueError`) when no slot up to `max_slot` answers as the balances mapping; a
301
+ negative `max_slot` is a `ValueError` with its own message.
302
+ - The external-state market raises the new `ExternalStateError` (still a
303
+ `ValueError`): `read_external_state` for a storage word longer than 32 bytes, an
304
+ executor word with bits above the address or a pause word other than 0 or 1;
305
+ `ExternalStateExecutor.for_vault` when no executor is deployed and, instead of
306
+ eth_abi's `NonEmptyPaddingBytes`, for a slot that is not an address;
307
+ `parse_balance_proposed` / `find_balance_proposed` for one of the executor's own
308
+ `BalanceProposed` logs whose data is missing or malformed (including data that is
309
+ not hex at all, which raised `TypeError`) or whose hash is not the hash of its own
310
+ fields; and `mark_nav` when the propose receipt it fetched has no such log or
311
+ records another value or proposer. A log that is not the event or not from the
312
+ executor, and `find_balance_proposed` over logs that hold no match, are still
313
+ `ValueError`.
314
+ - `ccip_token_lane` raises the new `UnsupportedContractVersionError` (still a
315
+ `ValueError`) for an OnRamp version whose layout is not verified.
316
+ - `quote_ccip_native_fee` raises `SimulationError` (label `"quote"`, with
317
+ `revert_reason`) instead of `ValueError` when the call reverts before the CCIP fee
318
+ check. A call that succeeds without sending a message is still a `ValueError`.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: ipor-fusion
3
- Version: 4.0.0.dev83
3
+ Version: 4.0.0.dev86
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.dev83"
3
+ version = "4.0.0.dev86"
4
4
  description = "The IPOR Fusion SDK for Python"
5
5
  authors = [
6
6
  { name = "Adam Mydlarz", email = "amydlarz@gmail.com" },
@@ -173,9 +173,12 @@ from ipor_fusion.dependency_graph import (
173
173
  )
174
174
  from ipor_fusion.errors import (
175
175
  CUSTOM_ERRORS,
176
+ BalanceSlotNotFoundError,
176
177
  ContractNotFoundError,
177
178
  CrosschainDiscoveryError,
178
179
  EmptyCallResultError,
180
+ ExternalStateError,
181
+ HyperCoreValuationError,
179
182
  IporFusionError,
180
183
  LogScanError,
181
184
  LogSourceUnavailable,
@@ -185,6 +188,7 @@ from ipor_fusion.errors import (
185
188
  SimulationRunError,
186
189
  TransactionError,
187
190
  UnsupportedChainError,
191
+ UnsupportedContractVersionError,
188
192
  UnsupportedVaultVersionError,
189
193
  decode_custom_error,
190
194
  get_revert_reason,
@@ -717,9 +721,12 @@ __all__ = [
717
721
  "format_market_label",
718
722
  "market_name",
719
723
  "DOCS",
724
+ "BalanceSlotNotFoundError",
720
725
  "ContractNotFoundError",
721
726
  "CrosschainDiscoveryError",
722
727
  "EmptyCallResultError",
728
+ "ExternalStateError",
729
+ "HyperCoreValuationError",
723
730
  "IporFusionError",
724
731
  "LogScanError",
725
732
  "LogSourceUnavailable",
@@ -729,6 +736,7 @@ __all__ = [
729
736
  "SimulationError",
730
737
  "SimulationRunError",
731
738
  "UnsupportedChainError",
739
+ "UnsupportedContractVersionError",
732
740
  "UnsupportedVaultVersionError",
733
741
  "CUSTOM_ERRORS",
734
742
  "decode_custom_error",
@@ -31,6 +31,7 @@ from web3.types import BlockIdentifier, TxReceipt
31
31
 
32
32
  from ipor_fusion.core.context import Web3Context
33
33
  from ipor_fusion.core.contract import Call, ContractWrapper
34
+ from ipor_fusion.errors import ExternalStateError
34
35
  from ipor_fusion.market_ids import IporFusionMarkets
35
36
  from ipor_fusion.types import (
36
37
  ZERO_ADDRESS,
@@ -107,6 +108,17 @@ def _hash_of(
107
108
  )
108
109
 
109
110
 
111
+ def _no_balance_proposed(
112
+ executor: ChecksumAddress, balance_account: ChecksumAddress | None
113
+ ) -> str:
114
+ for_account = (
115
+ f" for account {Web3.to_checksum_address(balance_account)}"
116
+ if balance_account is not None
117
+ else ""
118
+ )
119
+ return f"no BalanceProposed log from executor {executor}{for_account}"
120
+
121
+
110
122
  def _require_matching_hash(
111
123
  proposal: BalanceProposal, *, executor: ChecksumAddress, chain_id: ChainId
112
124
  ) -> None:
@@ -122,7 +134,7 @@ def _require_matching_hash(
122
134
  if proposal.proposal_hash != _hash_of(
123
135
  proposal, executor=executor, chain_id=chain_id
124
136
  ):
125
- raise ValueError(
137
+ raise ExternalStateError(
126
138
  "proposal hash does not match its own fields: it pairs this value "
127
139
  "with another proposal's hash, or it was computed for a different "
128
140
  "executor or chain id"
@@ -191,7 +203,9 @@ def _storage_word(raw: bytes) -> int:
191
203
  never a value an EVM slot can hold."""
192
204
  data = bytes(raw)
193
205
  if len(data) > 32:
194
- raise ValueError(f"storage word of {len(data)} bytes, more than a 32-byte slot")
206
+ raise ExternalStateError(
207
+ f"storage word of {len(data)} bytes, more than a 32-byte slot"
208
+ )
195
209
  return int.from_bytes(data.rjust(32, b"\x00"), "big")
196
210
 
197
211
 
@@ -230,9 +244,10 @@ def read_external_state(
230
244
  hash by identity; ``pending`` is rejected), so the four values never mix
231
245
  blocks; the block number is returned so executor reads can be pinned to
232
246
  it. Decoding is canonical: a word longer than 32 bytes, an executor word
233
- with bits above the address, or a pause word other than 0 or 1, raises ``ValueError`` rather than passing as
234
- a plausible value; a zero executor word is ``None`` (no executor deployed),
235
- which is a state, while an RPC or decoding failure raises.
247
+ with bits above the address, or a pause word other than 0 or 1, raises
248
+ ``ExternalStateError`` rather than passing as a plausible value; a zero
249
+ executor word is ``None`` (no executor deployed), which is a state, while
250
+ an RPC or decoding failure raises.
236
251
  """
237
252
  vault = Web3.to_checksum_address(vault_address)
238
253
  read_at, block = _snapshot_block(ctx)
@@ -241,11 +256,11 @@ def read_external_state(
241
256
  _storage_word(ctx.get_storage_at(vault, base + i, read_at)) for i in range(4)
242
257
  ]
243
258
  if words[0] >> 160:
244
- raise ValueError(
259
+ raise ExternalStateError(
245
260
  f"malformed executor word in the external-state storage of {vault}"
246
261
  )
247
262
  if words[3] not in (0, 1):
248
- raise ValueError(
263
+ raise ExternalStateError(
249
264
  f"malformed pause flag in the external-state storage of {vault}"
250
265
  )
251
266
  return ExternalStateVaultState(
@@ -309,24 +324,26 @@ class ExternalStateExecutor(ContractWrapper):
309
324
  ) -> ExternalStateExecutor:
310
325
  """Resolve the per-vault executor from vault storage and wrap it.
311
326
 
312
- Raises `ValueError` when no executor is deployed yet (the slot reads
313
- zero) rather than returning a wrapper around the zero address, and lets
314
- `decode` reject a slot whose high 12 bytes are non-zero (a wrong or
315
- corrupt slot, not an address-in-slot).
327
+ Raises `ExternalStateError` when no executor is deployed yet (the slot
328
+ reads zero) rather than returning a wrapper around the zero address, and
329
+ when the slot is not an address (its high 12 bytes are non-zero: a wrong
330
+ or corrupt slot) or is longer than 32 bytes.
316
331
  """
317
- # Left-pad so an RPC that strips leading zero bytes still decodes; the
318
- # address is the slot's low 20 bytes, `decode` verifies the rest is zero.
319
- raw = bytes(
332
+ word = _storage_word(
320
333
  ctx.get_storage_at(
321
334
  Web3.to_checksum_address(vault_address), cls._EXECUTOR_STORAGE_SLOT
322
335
  )
323
- ).rjust(32, b"\x00")
324
- executor = Web3.to_checksum_address(decode(["address"], raw)[0])
325
- if executor == ZERO_ADDRESS:
326
- raise ValueError(
336
+ )
337
+ if word >> 160:
338
+ raise ExternalStateError(
339
+ f"malformed executor word in the external-state storage of "
340
+ f"{vault_address}"
341
+ )
342
+ if word == 0:
343
+ raise ExternalStateError(
327
344
  f"no ExternalStateExecutor deployed for vault {vault_address}"
328
345
  )
329
- return cls(ctx, executor)
346
+ return cls(ctx, Web3.to_checksum_address(f"0x{word:040x}"))
330
347
 
331
348
  def balances(self, balance_account: ChecksumAddress) -> Call[Amount]:
332
349
  """Underlying-unit balance the executor tracks for `balance_account`.
@@ -634,11 +651,10 @@ class ExternalStateExecutor(ContractWrapper):
634
651
  Accepts receipt-shaped logs (HexBytes) and JSON-RPC / `eth_simulateV1`
635
652
  -shaped logs (`0x`-hex strings) alike, so it also decodes rows from an
636
653
  event index. Needs no chain access. Raises `ValueError` for a log that
637
- is not this event, was not emitted by this executor, carries a hash that
638
- is not the hash of its own fields, or whose data is malformed -- and for
639
- a call with no chain id available at all; `TypeError` for `data` that is
640
- not hex at all, which is the one field whose shape it cannot rule out
641
- first.
654
+ is not this event or was not emitted by this executor, and for a call
655
+ with no chain id available at all; `ExternalStateError` (also a
656
+ `ValueError`) for one of ours that carries a hash that is not the hash
657
+ of its own fields, or whose data is missing or malformed.
642
658
 
643
659
  Security: two checks, and both are needed. This executor must have
644
660
  emitted the log, and `proposal_hash` must be the hash of the log's own
@@ -682,7 +698,7 @@ class ExternalStateExecutor(ContractWrapper):
682
698
  """Decode a log whose topic0 is already known to be this event."""
683
699
  data = log.get("data")
684
700
  if data is None:
685
- raise ValueError("BalanceProposed log carries no data")
701
+ raise ExternalStateError("BalanceProposed log carries no data")
686
702
  try:
687
703
  # Unpacked in the order `BalanceProposed` declares them; all six
688
704
  # are unindexed, so topics carries topic0 alone.
@@ -690,8 +706,8 @@ class ExternalStateExecutor(ContractWrapper):
690
706
  cls._BALANCE_PROPOSED_TYPES,
691
707
  HexBytes(data),
692
708
  )
693
- except (DecodingError, ValueError) as exc:
694
- raise ValueError(f"malformed BalanceProposed data: {exc}") from exc
709
+ except (DecodingError, ValueError, TypeError) as exc:
710
+ raise ExternalStateError(f"malformed BalanceProposed data: {exc}") from exc
695
711
  return BalanceProposal(
696
712
  balance_account=Web3.to_checksum_address(account),
697
713
  proposer=Web3.to_checksum_address(proposer),
@@ -750,7 +766,19 @@ class ExternalStateExecutor(ContractWrapper):
750
766
  silently skips, and over a range you filtered yourself a foreign log
751
767
  means the filter is wrong, not that the row is uninteresting.
752
768
  """
753
- expected_emitter = self._require_address()
769
+ found = self._last_balance_proposed(logs, balance_account, chain_id)
770
+ if found is not None:
771
+ return found
772
+ raise ValueError(_no_balance_proposed(self._address, balance_account))
773
+
774
+ def _last_balance_proposed(
775
+ self,
776
+ logs: Iterable[RawLog],
777
+ balance_account: ChecksumAddress | None,
778
+ chain_id: ChainId | None,
779
+ ) -> BalanceProposal | None:
780
+ """`find_balance_proposed` without the raise when nothing matches."""
781
+ self._require_address()
754
782
  wanted = (
755
783
  Web3.to_checksum_address(balance_account)
756
784
  if balance_account is not None
@@ -773,11 +801,7 @@ class ExternalStateExecutor(ContractWrapper):
773
801
  executor=self._address,
774
802
  chain_id=self._require_chain_id(chain_id),
775
803
  )
776
- return found
777
- for_account = f" for account {wanted}" if wanted is not None else ""
778
- raise ValueError(
779
- f"no BalanceProposed log from executor {expected_emitter}{for_account}"
780
- )
804
+ return found
781
805
 
782
806
  def mark_nav(
783
807
  self,
@@ -840,19 +864,24 @@ class ExternalStateExecutor(ContractWrapper):
840
864
  )
841
865
  # Verified against the CONFIRMER's chain, which is the one the confirm
842
866
  # will be checked on; the wrapper's own ctx may be a third context.
843
- proposal = self.find_balance_proposed(
844
- propose_receipt["logs"], balance_account, chain_id=confirmer_ctx.chain_id
867
+ proposal = self._last_balance_proposed(
868
+ propose_receipt["logs"], balance_account, confirmer_ctx.chain_id
845
869
  )
870
+ if proposal is None:
871
+ raise ExternalStateError(
872
+ "the propose receipt records "
873
+ + _no_balance_proposed(self._address, balance_account)
874
+ )
846
875
  # The receipt is data from the node, so confirm only what we asked for:
847
876
  # signing off on a proposal that differs from the one just sent would
848
877
  # attest a value this call never chose.
849
878
  if proposal.value != value:
850
- raise ValueError(
879
+ raise ExternalStateError(
851
880
  f"proposed {value} but the receipt records {proposal.value}; "
852
881
  "refusing to confirm"
853
882
  )
854
883
  if proposal.proposer != proposer_address:
855
- raise ValueError(
884
+ raise ExternalStateError(
856
885
  f"proposal was made by {proposal.proposer}, not {proposer_address}; "
857
886
  "refusing to confirm"
858
887
  )
@@ -16,6 +16,7 @@ from web3.utils.address import get_create_address
16
16
  from ipor_fusion.core.contract import Call, _encode_calldata
17
17
  from ipor_fusion.core.fuse_action import FuseAction
18
18
  from ipor_fusion.errors import (
19
+ BalanceSlotNotFoundError,
19
20
  SimulationError,
20
21
  SimulationRunError,
21
22
  decode_custom_error,
@@ -751,13 +752,13 @@ def erc20_balance_slot(
751
752
  """Find the storage slot of ``token``'s ``balances`` mapping by overriding
752
753
  every candidate in one ``eth_simulateV1`` request, using a distinct holder
753
754
  per slot, until ``balanceOf`` reflects the override (slot 9 for Circle's
754
- FiatToken, 0 for OpenZeppelin ERC20). Raises ``ValueError`` when no slot up
755
- to ``max_slot`` answers, which is the case for tokens whose balances are not
756
- a plain address-keyed mapping."""
755
+ FiatToken, 0 for OpenZeppelin ERC20). Raises ``BalanceSlotNotFoundError``
756
+ when no slot up to ``max_slot`` answers, which is the case for tokens whose
757
+ balances are not a plain address-keyed mapping, and ``ValueError`` for a
758
+ negative ``max_slot``."""
757
759
  probe = 0x1234_5678_9ABC
758
- no_slot = f"no balances mapping slot found for {token} up to {max_slot}"
759
760
  if max_slot < 0:
760
- raise ValueError(no_slot)
761
+ raise ValueError(f"max_slot must be non-negative, got {max_slot}")
761
762
 
762
763
  sim = VaultSimulator(web3, block=block)
763
764
  for slot in range(max_slot + 1):
@@ -778,7 +779,9 @@ def erc20_balance_slot(
778
779
  for slot in range(max_slot + 1):
779
780
  if result.get(f"balance_slot_{slot}") == probe:
780
781
  return slot
781
- raise ValueError(no_slot)
782
+ raise BalanceSlotNotFoundError(
783
+ f"no balances mapping slot found for {token} up to {max_slot}"
784
+ )
782
785
 
783
786
 
784
787
  def _normalize_call_error(
@@ -16,6 +16,7 @@ from eth_typing import ChecksumAddress
16
16
  from ipor_fusion.core.context import Web3Context
17
17
  from ipor_fusion.core.contract import Call, ContractWrapper
18
18
  from ipor_fusion.crosschain.contracts import _address
19
+ from ipor_fusion.errors import UnsupportedContractVersionError
19
20
  from ipor_fusion.types import ZERO_ADDRESS, MessageId
20
21
 
21
22
 
@@ -118,9 +119,9 @@ def ccip_token_lane(
118
119
  chain_selector: int,
119
120
  ) -> CcipTokenLane:
120
121
  """Read, on ``router``'s chain, whether a message lane and a ``token`` lane
121
- exist towards ``chain_selector``. Seven reads at most. Raises ``ValueError``
122
- before reading static config when the OnRamp version has an unverified
123
- layout."""
122
+ exist towards ``chain_selector``. Seven reads at most. Raises
123
+ ``UnsupportedContractVersionError`` before reading static config when the
124
+ OnRamp version has an unverified layout."""
124
125
  router_wrapper = CcipRouter(ctx, router)
125
126
  if not router_wrapper.is_chain_supported(chain_selector).call():
126
127
  return CcipTokenLane(chain_selector, message_lane=False)
@@ -128,7 +129,7 @@ def ccip_token_lane(
128
129
  on_ramp_wrapper = CcipOnRamp(ctx, on_ramp)
129
130
  on_ramp_version = on_ramp_wrapper.type_and_version().call()
130
131
  if on_ramp_version not in _SUPPORTED_ON_RAMP_VERSIONS:
131
- raise ValueError(
132
+ raise UnsupportedContractVersionError(
132
133
  f"unsupported CCIP OnRamp version {on_ramp_version!r} at {on_ramp}; "
133
134
  "ccip_token_lane supports OnRamp 2.0.0"
134
135
  )
@@ -35,8 +35,9 @@ _HEADERS = {"Accept": "application/json", "User-Agent": "ipor-fusion-sdk"}
35
35
 
36
36
  class VerifierResultUnavailable(IporFusionError, LookupError):
37
37
  """No indexer could serve a verifier result for the message (not verified
38
- yet, or the indexers were unreachable); the message says which. Also a
39
- LookupError: the result looked up is not there (yet)."""
38
+ yet, the indexers were unreachable, or they answered something unreadable);
39
+ the message says which, per indexer. Also a LookupError: the result looked
40
+ up is not there (yet)."""
40
41
 
41
42
 
42
43
  @dataclass(frozen=True, slots=True)
@@ -63,7 +64,10 @@ def fetch_verifier_result(
63
64
  timeout: float = 15.0,
64
65
  ) -> VerifierResult:
65
66
  """``GET /v1/verifierresults/<messageId>`` from the first indexer that has
66
- a result; raises :class:`VerifierResultUnavailable` otherwise."""
67
+ a readable result; raises :class:`VerifierResultUnavailable` otherwise. An
68
+ indexer whose answer cannot be read (not JSON, a missing or mistyped field,
69
+ invalid hex or address, a result for another message) is skipped like one
70
+ that is unreachable."""
67
71
  if len(message_id) != 32:
68
72
  raise ValueError("message_id must be 32 bytes")
69
73
  hex_id = "0x" + message_id.hex()
@@ -79,26 +83,57 @@ def fetch_verifier_result(
79
83
  if response.status_code != 200:
80
84
  reasons.append(f"{base}: HTTP {response.status_code}")
81
85
  continue
82
- results = (response.json() or {}).get("results") or []
83
- if not results:
86
+ try:
87
+ result = _parse_answer(response.json(), message_id, base)
88
+ except (KeyError, IndexError, TypeError, ValueError) as exc:
89
+ reasons.append(f"{base}: malformed answer ({type(exc).__name__}: {exc})")
90
+ continue
91
+ if result is None:
84
92
  reasons.append(f"{base}: no result yet")
85
93
  continue
86
- result = results[0]["verifierResult"]
87
- return VerifierResult(
88
- message_id=MessageId(bytes.fromhex(result["message_id"][2:])),
89
- ccv_addresses=tuple(
90
- Web3.to_checksum_address(a) for a in result["message_ccv_addresses"]
91
- ),
92
- ccv_data=bytes.fromhex(result["ccv_data"][2:]),
93
- executor=Web3.to_checksum_address(result["message_executor_address"]),
94
- message=result["message"],
95
- indexer=base,
96
- )
94
+ return result
97
95
  raise VerifierResultUnavailable(
98
96
  f"no verifier result for {hex_id}: " + "; ".join(reasons)
99
97
  )
100
98
 
101
99
 
100
+ def _hex_bytes(value: Any) -> bytes:
101
+ if not isinstance(value, str) or not value.startswith("0x"):
102
+ raise ValueError(f"not 0x-prefixed hex: {value!r}")
103
+ return bytes.fromhex(value[2:])
104
+
105
+
106
+ def _parse_answer(
107
+ body: Any, message_id: MessageId, indexer: str
108
+ ) -> VerifierResult | None:
109
+ """The first result of an indexer's JSON answer; ``None`` when it has none.
110
+ Anything else it cannot read raises a builtin error, reported by the caller
111
+ as a malformed answer."""
112
+ if not isinstance(body, Mapping):
113
+ raise TypeError("answer is not a JSON object")
114
+ results = body.get("results")
115
+ if not results:
116
+ return None
117
+ result = results[0]["verifierResult"]
118
+ served_id = _hex_bytes(result["message_id"])
119
+ if served_id != message_id:
120
+ raise ValueError(f"result is for message 0x{served_id.hex()}")
121
+ message = result["message"]
122
+ if not isinstance(message, Mapping):
123
+ raise TypeError("message is not an object")
124
+ ccvs = result["message_ccv_addresses"]
125
+ if not isinstance(ccvs, list) or not ccvs:
126
+ raise ValueError("message_ccv_addresses is not a non-empty array")
127
+ return VerifierResult(
128
+ message_id=MessageId(served_id),
129
+ ccv_addresses=tuple(Web3.to_checksum_address(a) for a in ccvs),
130
+ ccv_data=_hex_bytes(result["ccv_data"]),
131
+ executor=Web3.to_checksum_address(result["message_executor_address"]),
132
+ message=message,
133
+ indexer=indexer,
134
+ )
135
+
136
+
102
137
  def manual_execution(
103
138
  off_ramp: CcipOffRamp,
104
139
  encoded_message: bytes,
@@ -18,6 +18,7 @@ from web3.types import BlockIdentifier
18
18
 
19
19
  from ipor_fusion.core.contract import Call
20
20
  from ipor_fusion.core.simulation import VaultSimulator
21
+ from ipor_fusion.errors import SimulationError
21
22
 
22
23
  #: Selector of ``CcipInsufficientNativeFeeBalance(uint256 balance, uint256 required)``.
23
24
  INSUFFICIENT_NATIVE_FEE_SELECTOR: bytes = keccak(
@@ -40,8 +41,8 @@ def quote_ccip_native_fee(
40
41
  ``payer`` is the contract that pays: the factory for ``requestDispatcher``,
41
42
  the executor for a supply or command (``from_`` is then the alpha sending
42
43
  the vault's ``execute``), the dispatcher for a return. The call must be
43
- valid at ``block`` up to the fee check; a revert before it, or a call that
44
- sends no message, raises ``ValueError``.
44
+ valid at ``block`` up to the fee check: a revert before it raises
45
+ ``SimulationError`` and a call that sends no message ``ValueError``.
45
46
  """
46
47
  simulator = VaultSimulator(web3, alpha=from_, block=block)
47
48
  simulator.with_state_override(payer, balance=hex(0))
@@ -55,6 +56,8 @@ def quote_ccip_native_fee(
55
56
  raise ValueError(
56
57
  "the call succeeded with the payer at zero balance: it sends no CCIP message"
57
58
  )
58
- raise ValueError(
59
- f"the call reverted before the CCIP fee check: {result.revert_reason}"
59
+ raise SimulationError(
60
+ f"the call reverted before the CCIP fee check: {result.revert_reason}",
61
+ label="quote",
62
+ revert_reason=result.revert_reason,
60
63
  )
@@ -148,16 +148,17 @@ def get_revert_reason(web3: Web3, tx_hash: TxHash, receipt: TxReceipt) -> str |
148
148
  class IporFusionError(Exception):
149
149
  """Base of every exception the SDK defines.
150
150
 
151
- The rule the SDK follows (a few older readers do not yet; see
152
- MIGRATION_4.md): a subclass when the chain, a provider or a remote API
153
- answers something it cannot use. A caller's mistake (a bad argument, or an
154
- operation the object was not built for, such as ``.call()`` on a write-only
155
- ``Call``) raises a builtin ``ValueError`` instead, as the standard library
156
- does for an operation on a closed file. A subclass that also describes bad
157
- input (an address that is not a vault, a chain the tooling does not cover)
158
- additionally derives from ``ValueError``; a few keep the builtin base they
159
- had before 4.0 (``RuntimeError``, ``LookupError``) so existing handlers
160
- still catch them.
151
+ The rule the SDK follows everywhere: a subclass when the chain, a provider
152
+ or a remote API answers something it cannot use. A caller's mistake (a bad
153
+ argument, or an operation the object was not built for, such as
154
+ ``.call()`` on a write-only ``Call``) raises a builtin ``ValueError``
155
+ instead, as the standard library does for an operation on a closed file;
156
+ an argument of the wrong type raises ``TypeError`` and an unknown name in a
157
+ mapping-style lookup ``KeyError``. A subclass that also describes bad input
158
+ (an address that is not a vault, a chain the tooling does not cover)
159
+ additionally derives from ``ValueError``; others keep the builtin base
160
+ their raise sites had before 4.0 (``ValueError``, ``RuntimeError``,
161
+ ``LookupError``) so existing handlers still catch them.
161
162
  """
162
163
 
163
164
 
@@ -191,6 +192,44 @@ class UnsupportedVaultVersionError(IporFusionError, ValueError):
191
192
  """
192
193
 
193
194
 
195
+ class UnsupportedContractVersionError(IporFusionError, ValueError):
196
+ """A contract reports a version whose layout the SDK has not verified, so
197
+ reading it further could misread its storage or return data.
198
+
199
+ Also a ValueError, the type these failures had before 4.0.
200
+ """
201
+
202
+
203
+ class HyperCoreValuationError(IporFusionError, ValueError):
204
+ """The HyperCore NAV cannot be valued: a precompile read failed, the
205
+ oracle prices no granted spot token or its price overflows, the vault
206
+ grants a perp dex bitmap the valuation does not support, or the NAV is
207
+ negative (where the balance fuse reverts).
208
+
209
+ Also a ValueError, the type these failures had before 4.0.
210
+ """
211
+
212
+
213
+ class ExternalStateError(IporFusionError, ValueError):
214
+ """The external-state market's on-chain data is not usable: the vault's
215
+ storage holds no executor or a malformed word, an executor's
216
+ ``BalanceProposed`` log is malformed or carries a hash that is not the hash
217
+ of its own fields, or a propose receipt does not record the proposal that
218
+ was sent.
219
+
220
+ Also a ValueError, the type these failures had before 4.0.
221
+ """
222
+
223
+
224
+ class BalanceSlotNotFoundError(IporFusionError, ValueError):
225
+ """No candidate storage slot of a token answers as its ``balances``
226
+ mapping: its balances are not a plain address-keyed mapping, or the
227
+ mapping lies beyond the slots searched.
228
+
229
+ Also a ValueError, the type this failure had before 4.0.
230
+ """
231
+
232
+
194
233
  class LogScanError(IporFusionError):
195
234
  """An `eth_getLogs` scan could not cover its whole block range.
196
235
 
@@ -252,7 +291,8 @@ class SimulationRunError(IporFusionError, RuntimeError):
252
291
 
253
292
 
254
293
  class SimulationError(IporFusionError):
255
- """A simulated call reverted; raised by ``SimulationResult.raise_for_failure``."""
294
+ """A simulated call reverted; raised by ``SimulationResult.raise_for_failure``
295
+ and by ``quote_ccip_native_fee`` for a revert before the CCIP fee check."""
256
296
 
257
297
  def __init__(
258
298
  self,
@@ -633,10 +633,7 @@ def market_meta_morpho(
633
633
  vault_address: MetaMorpho V1 or Vault V2 address.
634
634
  chain_id: EVM chain ID.
635
635
  """
636
- try:
637
- info = fetch_vault(Web3.to_checksum_address(vault_address), chain_id)
638
- except MorphoApiError as exc:
639
- raise ValueError(str(exc)) from exc
636
+ info = fetch_vault(Web3.to_checksum_address(vault_address), chain_id)
640
637
  payload = build_meta_morpho_json(info, chain_id)
641
638
  return MetaMorphoVaultResponse.model_validate(payload)
642
639