ipor-fusion 4.1.0.dev52__tar.gz → 4.1.0.dev56__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 (362) hide show
  1. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/PKG-INFO +1 -1
  2. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/pyproject.toml +1 -1
  3. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/__init__.py +2 -0
  4. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/core/context.py +4 -0
  5. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/errors.py +3 -1
  6. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/mcp/models.py +10 -0
  7. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/mcp/server.py +3 -1
  8. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/readers/__init__.py +2 -0
  9. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/readers/oracle_mapping.py +4 -1
  10. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/readers/oracle_mapping_nodes.py +41 -0
  11. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/readers/oracle_mapping_reader.py +8 -0
  12. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/_oracle_mapping_fake.py +4 -0
  13. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/fixtures/oracle_mapping_golden/mapping.json +16 -1
  14. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/fixtures/oracle_mapping_golden/mapping.mcp.json +16 -1
  15. ipor_fusion-4.1.0.dev56/tests/test_ccip_read_disabled.py +96 -0
  16. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_cli_oracle_mapping_golden.py +11 -0
  17. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_oracle_mapping.py +48 -0
  18. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_public_api.py +2 -0
  19. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/.claude-plugin/marketplace.json +0 -0
  20. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/.claude-plugin/plugin.json +0 -0
  21. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/.env.example +0 -0
  22. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/.gemini/settings.json +0 -0
  23. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/.git-blame-ignore-revs +0 -0
  24. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/.github/workflows/cd.yml +0 -0
  25. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/.github/workflows/ci.yml +0 -0
  26. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/.github/workflows/commit-lint.yml +0 -0
  27. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/.github/workflows/notify-slack.yml +0 -0
  28. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/.github/workflows/python-build.yml +0 -0
  29. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/.github/workflows/python-publish.yml +0 -0
  30. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/.github/workflows/release.yml +0 -0
  31. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/.github/workflows/report-slack.yml +0 -0
  32. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/.gitignore +0 -0
  33. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/.mcp.json +0 -0
  34. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/.pre-commit-config.yaml +0 -0
  35. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/.python-version +0 -0
  36. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/AGENTS.md +0 -0
  37. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/CHANGELOG.md +0 -0
  38. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/CLAUDE.md +0 -0
  39. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/CODEOWNERS +0 -0
  40. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/LICENSE +0 -0
  41. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/MIGRATION_4.md +0 -0
  42. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/README.md +0 -0
  43. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/build.sh +0 -0
  44. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/examples/CONTRIBUTING.md +0 -0
  45. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/examples/README.md +0 -0
  46. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/examples/advanced_euler_v2_credit_market_base.py +0 -0
  47. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/examples/advanced_euler_v2_looping_arbitrum.py +0 -0
  48. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/examples/crosschain_ccip_usdc_arbitrum_hyperevm.py +0 -0
  49. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/examples/external_state_margin_leg_base.py +0 -0
  50. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/examples/hypercore_vault_official_deployment_hyperevm.py +0 -0
  51. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/examples/simple_aave_v3_supply_base.py +0 -0
  52. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/pytest.ini +0 -0
  53. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/scripts/commit-check.sh +0 -0
  54. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/scripts/fusion_error_signatures.py +0 -0
  55. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/skills/ipor-deploy-vault/SKILL.md +0 -0
  56. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/about.py +0 -0
  57. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/cache.py +0 -0
  58. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/chains.py +0 -0
  59. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/cli/__init__.py +0 -0
  60. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/cli/address_cmd.py +0 -0
  61. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/cli/changelog_cmd.py +0 -0
  62. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/cli/config_cmd.py +0 -0
  63. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/cli/config_store.py +0 -0
  64. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/cli/main.py +0 -0
  65. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/cli/market_cmd.py +0 -0
  66. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/cli/options.py +0 -0
  67. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/cli/oracle_mapping_view.py +0 -0
  68. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/cli/preflight_cmd.py +0 -0
  69. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/cli/vault_cmd.py +0 -0
  70. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/cli/vault_health.py +0 -0
  71. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/cli/vault_info_view.py +0 -0
  72. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/cli/vault_markets_view.py +0 -0
  73. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/cli/vault_rendering.py +0 -0
  74. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/config/__init__.py +0 -0
  75. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/config/roles.py +0 -0
  76. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/core/__init__.py +0 -0
  77. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/core/access.py +0 -0
  78. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/core/address.py +0 -0
  79. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/core/context_manager.py +0 -0
  80. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/core/contract.py +0 -0
  81. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/core/erc20.py +0 -0
  82. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/core/external_state_executor.py +0 -0
  83. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/core/fee_manager.py +0 -0
  84. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/core/fuse.py +0 -0
  85. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/core/fuse_action.py +0 -0
  86. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/core/fuse_decoding.py +0 -0
  87. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/core/fusion_factory.py +0 -0
  88. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/core/logs.py +0 -0
  89. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/core/multicall.py +0 -0
  90. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/core/oracle.py +0 -0
  91. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/core/plasma_vault.py +0 -0
  92. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/core/rewards_manager.py +0 -0
  93. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/core/simulation.py +0 -0
  94. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/core/simulation_result.py +0 -0
  95. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/core/withdraw_manager.py +0 -0
  96. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/crosschain/__init__.py +0 -0
  97. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/crosschain/ccip/__init__.py +0 -0
  98. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/crosschain/ccip/chainlink.py +0 -0
  99. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/crosschain/ccip/codec.py +0 -0
  100. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/crosschain/ccip/contracts.py +0 -0
  101. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/crosschain/ccip/events.py +0 -0
  102. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/crosschain/ccip/indexer.py +0 -0
  103. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/crosschain/ccip/lane.py +0 -0
  104. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/crosschain/ccip/quote.py +0 -0
  105. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/crosschain/ccip/transport.py +0 -0
  106. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/crosschain/contracts.py +0 -0
  107. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/crosschain/discovery.py +0 -0
  108. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/crosschain/errors.py +0 -0
  109. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/crosschain/fuses/__init__.py +0 -0
  110. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/crosschain/fuses/base.py +0 -0
  111. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/crosschain/fuses/ccip.py +0 -0
  112. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/crosschain/fuses/stargate.py +0 -0
  113. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/crosschain/lane.py +0 -0
  114. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/crosschain/logs.py +0 -0
  115. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/crosschain/messages.py +0 -0
  116. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/crosschain/simulation.py +0 -0
  117. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/crosschain/stargate/__init__.py +0 -0
  118. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/crosschain/stargate/contracts.py +0 -0
  119. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/crosschain/stargate/lane.py +0 -0
  120. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/crosschain/stargate/layerzero.py +0 -0
  121. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/crosschain/stargate/transport.py +0 -0
  122. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/crosschain/transport.py +0 -0
  123. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/dependency_graph.py +0 -0
  124. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/entry_cli.py +0 -0
  125. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/entry_mcp.py +0 -0
  126. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/explorer.py +0 -0
  127. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/field_docs.py +0 -0
  128. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/fuses/__init__.py +0 -0
  129. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/fuses/aave_v3.py +0 -0
  130. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/fuses/async_action.py +0 -0
  131. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/fuses/base.py +0 -0
  132. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/fuses/compound_v3.py +0 -0
  133. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/fuses/erc4626.py +0 -0
  134. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/fuses/euler_v2.py +0 -0
  135. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/fuses/events.py +0 -0
  136. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/fuses/external_state.py +0 -0
  137. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/fuses/external_state_event_decoding.py +0 -0
  138. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/fuses/fluid_instadapp.py +0 -0
  139. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/fuses/gearbox_v3.py +0 -0
  140. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/fuses/hypercore.py +0 -0
  141. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/fuses/hypercore_event_decoding.py +0 -0
  142. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/fuses/hypercore_pre_hooks.py +0 -0
  143. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/fuses/merkl.py +0 -0
  144. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/fuses/morpho.py +0 -0
  145. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/fuses/ramses_v2.py +0 -0
  146. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/fuses/uniswap_v3.py +0 -0
  147. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/fuses/universal.py +0 -0
  148. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/fusion_errors.py +0 -0
  149. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/guide/__init__.py +0 -0
  150. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/guide/architecture.md +0 -0
  151. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/guide/glossary.md +0 -0
  152. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/guide/invariants.md +0 -0
  153. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/guide/quickstart.md +0 -0
  154. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/hypercore/__init__.py +0 -0
  155. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/market_ids.py +0 -0
  156. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/market_info.py +0 -0
  157. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/mcp/__init__.py +0 -0
  158. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/mcp/guide.py +0 -0
  159. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/morpho_api.py +0 -0
  160. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/preflight.py +0 -0
  161. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/py.typed +0 -0
  162. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/readers/aave_v3.py +0 -0
  163. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/readers/aave_v4.py +0 -0
  164. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/readers/compound_v3.py +0 -0
  165. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/readers/euler_v2.py +0 -0
  166. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/readers/hypercore.py +0 -0
  167. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/readers/hypercore_outcome.py +0 -0
  168. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/readers/lending_health.py +0 -0
  169. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/readers/morpho.py +0 -0
  170. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/readers/position_manager.py +0 -0
  171. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/readers/ramses_v2.py +0 -0
  172. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/readers/uniswap_v3.py +0 -0
  173. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/readers/unpriceable.py +0 -0
  174. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/registry.py +0 -0
  175. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/substrates.py +0 -0
  176. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/types.py +0 -0
  177. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/validation.py +0 -0
  178. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/vault_data.py +0 -0
  179. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/vault_erc20.py +0 -0
  180. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/vault_info.py +0 -0
  181. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/vault_managers.py +0 -0
  182. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/vault_positions.py +0 -0
  183. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/src/ipor_fusion/vault_reads.py +0 -0
  184. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/UniswapV3UniversalRouter.py +0 -0
  185. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/_addresses.py +0 -0
  186. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/_crosschain.py +0 -0
  187. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/_crosschain_lifecycle.py +0 -0
  188. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/_crosschain_recovery.py +0 -0
  189. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/_euler_v2.py +0 -0
  190. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/_event_args.py +0 -0
  191. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/_fake_rpc.py +0 -0
  192. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/_foundry.py +0 -0
  193. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/_golden.py +0 -0
  194. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/_guide.py +0 -0
  195. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/_hypercore_model.py +0 -0
  196. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/_hypercore_shadow.py +0 -0
  197. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/_live.py +0 -0
  198. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/_multicall.py +0 -0
  199. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/_simulate.py +0 -0
  200. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/_validation.py +0 -0
  201. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/addresses.py +0 -0
  202. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/conftest.py +0 -0
  203. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/constants.py +0 -0
  204. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/fixtures/hypercore_hip3_flow.json +0 -0
  205. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/fixtures/hypercore_market55.json +0 -0
  206. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/fixtures/hypercore_official.json +0 -0
  207. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/fixtures/market_golden/meta_morpho_v1.json +0 -0
  208. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/fixtures/market_golden/meta_morpho_v1.mcp.json +0 -0
  209. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/fixtures/market_golden/meta_morpho_v2.json +0 -0
  210. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/fixtures/market_golden/meta_morpho_v2.mcp.json +0 -0
  211. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/fixtures/market_golden/morpho_api/market.json +0 -0
  212. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/fixtures/market_golden/morpho_api/vault_v1.json +0 -0
  213. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/fixtures/market_golden/morpho_api/vault_v2.json +0 -0
  214. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/fixtures/market_golden/morpho_api/vault_v2_not_found.json +0 -0
  215. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/fixtures/market_golden/morpho_blue.json +0 -0
  216. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/fixtures/market_golden/morpho_blue.mcp.json +0 -0
  217. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/fixtures/market_golden/morpho_blue_api_error.json +0 -0
  218. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/fixtures/market_golden/morpho_blue_api_error.mcp.json +0 -0
  219. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/fixtures/market_golden/morpho_blue_no_api.json +0 -0
  220. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/fixtures/market_golden/morpho_blue_no_api.mcp.json +0 -0
  221. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/fixtures/simulate_v1_request.json +0 -0
  222. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/fixtures/vault_info_golden/clean.json +0 -0
  223. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/fixtures/vault_info_golden/clean.mcp.json +0 -0
  224. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/fixtures/vault_info_golden/clean.txt +0 -0
  225. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/fixtures/vault_info_golden/deployment_error.json +0 -0
  226. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/fixtures/vault_info_golden/deployment_error.mcp.json +0 -0
  227. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/fixtures/vault_info_golden/deployment_error.txt +0 -0
  228. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/fixtures/vault_info_golden/fetched.cli.json +0 -0
  229. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/fixtures/vault_info_golden/fetched.json +0 -0
  230. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/fixtures/vault_info_golden/fetched.mcp.json +0 -0
  231. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/fixtures/vault_info_golden/full.json +0 -0
  232. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/fixtures/vault_info_golden/full.mcp.json +0 -0
  233. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/fixtures/vault_info_golden/full.txt +0 -0
  234. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/fixtures/vault_info_golden/rich.json +0 -0
  235. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/fixtures/vault_info_golden/rich.mcp.json +0 -0
  236. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/fixtures/vault_info_golden/rich.txt +0 -0
  237. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_about.py +0 -0
  238. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_access_manager.py +0 -0
  239. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_addresses_helper.py +0 -0
  240. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_cache.py +0 -0
  241. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_chains.py +0 -0
  242. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_cli_address.py +0 -0
  243. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_cli_changelog.py +0 -0
  244. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_cli_commands.py +0 -0
  245. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_cli_config_store.py +0 -0
  246. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_cli_helpers.py +0 -0
  247. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_cli_hypercore.py +0 -0
  248. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_cli_lending_health.py +0 -0
  249. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_cli_market_golden.py +0 -0
  250. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_cli_market_morpho_blue.py +0 -0
  251. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_cli_mcp_parity.py +0 -0
  252. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_cli_oracle_mapping.py +0 -0
  253. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_cli_pending_requests_scan.py +0 -0
  254. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_cli_preflight.py +0 -0
  255. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_cli_role_accounts.py +0 -0
  256. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_cli_vault_info_golden.py +0 -0
  257. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_cli_vault_predates_getters.py +0 -0
  258. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_cli_vault_validate.py +0 -0
  259. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_contract_call.py +0 -0
  260. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_contract_encoder.py +0 -0
  261. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_crosschain_ccip_events.py +0 -0
  262. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_crosschain_ccip_indexer.py +0 -0
  263. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_crosschain_ccip_quote.py +0 -0
  264. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_crosschain_chainlink.py +0 -0
  265. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_crosschain_contracts.py +0 -0
  266. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_crosschain_encoding.py +0 -0
  267. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_crosschain_errors.py +0 -0
  268. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_crosschain_lane.py +0 -0
  269. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_crosschain_layering.py +0 -0
  270. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_crosschain_transport.py +0 -0
  271. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_docs_typing.py +0 -0
  272. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_erc20.py +0 -0
  273. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_error_hierarchy.py +0 -0
  274. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_examples_crosschain.py +0 -0
  275. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_examples_euler_looping_arbitrum.py +0 -0
  276. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_examples_hypercore_hyperevm.py +0 -0
  277. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_examples_vaults.py +0 -0
  278. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_explorer.py +0 -0
  279. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_external_state_events.py +0 -0
  280. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_external_state_executor.py +0 -0
  281. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_fee_manager.py +0 -0
  282. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_fuse_decoding.py +0 -0
  283. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_fuse_encoding.py +0 -0
  284. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_fusion_errors.py +0 -0
  285. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_fusion_instance.py +0 -0
  286. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_governance_wrappers.py +0 -0
  287. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_guide.py +0 -0
  288. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_hypercore_deployment.py +0 -0
  289. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_hypercore_events.py +0 -0
  290. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_hypercore_flow.py +0 -0
  291. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_hypercore_live.py +0 -0
  292. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_hypercore_outcome.py +0 -0
  293. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_hypercore_pre_hooks.py +0 -0
  294. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_hypercore_readers.py +0 -0
  295. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_hypercore_simulation.py +0 -0
  296. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_logs.py +0 -0
  297. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_mcp_address.py +0 -0
  298. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_mcp_hypercore.py +0 -0
  299. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_mcp_models.py +0 -0
  300. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_mcp_preflight.py +0 -0
  301. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_mcp_server.py +0 -0
  302. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_mcp_vault_validate.py +0 -0
  303. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_multicall.py +0 -0
  304. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_oracle.py +0 -0
  305. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_plasma_vault.py +0 -0
  306. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_preflight.py +0 -0
  307. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_public_type_reachability.py +0 -0
  308. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_reader_encoding.py +0 -0
  309. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_registry.py +0 -0
  310. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_revert_decoding.py +0 -0
  311. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_rewards_manager.py +0 -0
  312. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_roles.py +0 -0
  313. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_sdk_layering.py +0 -0
  314. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_simulate_borrow_aave_v3_mainnet.py +0 -0
  315. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_simulate_claim_merkl_wrapper_base.py +0 -0
  316. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_simulate_claim_morpho_rewards.py +0 -0
  317. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_simulate_crosschain_hyperevm.py +0 -0
  318. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_simulate_euler_v2_batch_base.py +0 -0
  319. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_simulate_euler_v2_credit_market_base.py +0 -0
  320. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_simulate_euler_v2_swap_deploy_base.py +0 -0
  321. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_simulate_euler_v2_swap_reconfigure_base.py +0 -0
  322. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_simulate_euler_v2_swap_registry_base.py +0 -0
  323. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_simulate_examples_arbitrum.py +0 -0
  324. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_simulate_examples_base.py +0 -0
  325. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_simulate_examples_crosschain.py +0 -0
  326. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_simulate_examples_hyperevm.py +0 -0
  327. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_simulate_external_state_executor_base.py +0 -0
  328. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_simulate_fusion_factory_clone_base.py +0 -0
  329. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_simulate_looping_aave_v3_base.py +0 -0
  330. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_simulate_looping_morpho_blue_base.py +0 -0
  331. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_simulate_morpho_blue.py +0 -0
  332. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_simulate_pilot_v3.py +0 -0
  333. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_simulate_pilot_v4_uniswap.py +0 -0
  334. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_simulate_pilot_v5_ramses.py +0 -0
  335. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_simulate_pilot_v5_scheduled.py +0 -0
  336. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_simulate_plasma_vault_setters_base.py +0 -0
  337. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_simulate_preflight_base.py +0 -0
  338. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_simulate_readers_integration.py +0 -0
  339. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_simulate_release_funds.py +0 -0
  340. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_simulate_swap_on_base.py +0 -0
  341. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_simulate_timelock_base.py +0 -0
  342. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_simulate_vault_from_scratch_base.py +0 -0
  343. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_simulation_errors.py +0 -0
  344. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_simulation_observation.py +0 -0
  345. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_solidity_compiler.py +0 -0
  346. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_solidity_mirrors.py +0 -0
  347. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_substrates.py +0 -0
  348. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_types_and_events.py +0 -0
  349. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_unpriceable_tokens.py +0 -0
  350. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_validation.py +0 -0
  351. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_vault_data_block.py +0 -0
  352. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_vault_dep_graph.py +0 -0
  353. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_vault_info_fetched_golden.py +0 -0
  354. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_vault_info_onchain_base.py +0 -0
  355. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_vault_info_onchain_base_no_withdraw_manager.py +0 -0
  356. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_vault_reconciliation_onchain_base.py +0 -0
  357. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_vault_simulator_blocks.py +0 -0
  358. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_web3_context.py +0 -0
  359. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/test_withdraw_manager.py +0 -0
  360. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/typed_client/consumer.py +0 -0
  361. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/tests/typed_client/rejected.py +0 -0
  362. {ipor_fusion-4.1.0.dev52 → ipor_fusion-4.1.0.dev56}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: ipor-fusion
3
- Version: 4.1.0.dev52
3
+ Version: 4.1.0.dev56
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.1.0.dev52"
3
+ version = "4.1.0.dev56"
4
4
  description = "The IPOR Fusion SDK for Python"
5
5
  authors = [
6
6
  { name = "Adam Mydlarz", email = "amydlarz@gmail.com" },
@@ -325,6 +325,7 @@ from ipor_fusion.readers.oracle_mapping_nodes import (
325
325
  Erc4626DetailJson,
326
326
  MiddlewareFallbackDetailJson,
327
327
  MorphoFeedDetailJson,
328
+ MorphoOracleFeedJson,
328
329
  OracleAssetJson,
329
330
  OracleMappingJson,
330
331
  OracleNodeJson,
@@ -760,6 +761,7 @@ __all__ = [
760
761
  "Erc4626DetailJson",
761
762
  "MiddlewareFallbackDetailJson",
762
763
  "MorphoFeedDetailJson",
764
+ "MorphoOracleFeedJson",
763
765
  "OracleAssetJson",
764
766
  "OracleMappingJson",
765
767
  "OracleNodeJson",
@@ -100,6 +100,10 @@ class Web3Context:
100
100
  log_source: LogSource | None = None,
101
101
  cache: ContractCache | None = None,
102
102
  ):
103
+ # EIP-3668: web3 would otherwise follow a contract's OffchainLookup
104
+ # revert from inside eth.call, sending an HTTP request to a URL chosen
105
+ # by whoever deployed the contract. Nothing in the SDK reads through it.
106
+ web3.provider.global_ccip_read_enabled = False
103
107
  self._web3 = web3
104
108
  self._chain_id = chain_id
105
109
  # Bounds of every `get_logs` scan: wall clock, and eth_getLogs requests
@@ -132,7 +132,9 @@ def get_revert_reason(web3: Web3, tx_hash: TxHash, receipt: TxReceipt) -> str |
132
132
  call_params["gas"] = tx["gas"]
133
133
 
134
134
  block_number = receipt["blockNumber"]
135
- web3.eth.call(call_params, block_identifier=block_number)
135
+ web3.eth.call(
136
+ call_params, block_identifier=block_number, ccip_read_enabled=False
137
+ )
136
138
  return None # replay succeeded — can't determine reason
137
139
  except Exception as exc:
138
140
  exc_data = getattr(exc, "data", None)
@@ -564,11 +564,21 @@ class Erc4626Detail(_Base):
564
564
  rate: str | None
565
565
 
566
566
 
567
+ class MorphoOracleFeed(AggregatorFeedDetail):
568
+ """A feed behind a Morpho oracle; role names the getter it came from."""
569
+
570
+ role: str
571
+
572
+
567
573
  class MorphoFeedDetail(_Base):
568
574
  morpho_oracle: str
569
575
  collateral_token: str | None
570
576
  loan_token: str
571
577
  morpho_price: str | None
578
+ oracle_feeds: list[MorphoOracleFeed] | None = Field(
579
+ description="Feeds the oracle uses; null when it does not expose them, "
580
+ "so their freshness cannot be read."
581
+ )
572
582
 
573
583
 
574
584
  class MiddlewareFallbackDetail(_Base):
@@ -468,7 +468,9 @@ def vault_oracle_mapping(
468
468
  description, a foreign getter answering; still resolved, but verify the
469
469
  address yourself if identity matters), ERC4626PriceFeed (share→asset
470
470
  rate, recurses on the underlying), CollateralTokenOnMorphoMarketPriceFeed
471
- (recurses on the loan token), middleware_fallback (no per-vault source;
471
+ (recurses on the loan token; source_detail.oracle_feeds lists the feeds
472
+ behind the Morpho oracle, null when it does not expose them),
473
+ middleware_fallback (no per-vault source;
472
474
  priced by the oracle's underlying global middleware, followed as a
473
475
  dependency when discoverable), custom_unknown (partial). Classification is
474
476
  heuristic interface probing: it grades on-chain evidence and cannot prove
@@ -45,6 +45,7 @@ from ipor_fusion.readers.oracle_mapping_nodes import (
45
45
  Erc4626DetailJson,
46
46
  MiddlewareFallbackDetailJson,
47
47
  MorphoFeedDetailJson,
48
+ MorphoOracleFeedJson,
48
49
  OracleAssetJson,
49
50
  OracleMappingJson,
50
51
  OracleNodeJson,
@@ -94,6 +95,7 @@ __all__ = [
94
95
  "Erc4626DetailJson",
95
96
  "MiddlewareFallbackDetailJson",
96
97
  "MorphoFeedDetailJson",
98
+ "MorphoOracleFeedJson",
97
99
  "OracleAssetJson",
98
100
  "OracleMappingJson",
99
101
  "OracleNodeJson",
@@ -9,7 +9,8 @@ collateral feeds). Unknown feeds are reported as partial, never dropped.
9
9
  A zero source is not a dead end: the oracle may be a manager variant that
10
10
  delegates such assets to an underlying global middleware — that delegation is
11
11
  followed and reported as ``middleware_fallback``. Every aggregator-compatible
12
- read (Chainlink-tier leaves, dual-cross-reference component feeds) carries
12
+ read (Chainlink-tier leaves, dual-cross-reference component feeds, feeds
13
+ behind a Morpho oracle) carries
13
14
  ``description()`` and the full ``latestRoundData()`` round in
14
15
  ``source_detail`` — raw values plus ISO-8601 UTC twins of the timestamps,
15
16
  no staleness judgment; ``description`` is null when the feed does not
@@ -83,6 +84,7 @@ from ipor_fusion.readers.oracle_mapping_nodes import (
83
84
  _apply_dependency_status,
84
85
  _collect_unresolved,
85
86
  _mapping_status,
87
+ _morpho_oracle_feeds,
86
88
  _partial,
87
89
  _price_block,
88
90
  _resolve_chainlink,
@@ -181,6 +183,7 @@ def _resolve_morpho(
181
183
  "collateral_token": collateral,
182
184
  "loan_token": loan,
183
185
  "morpho_price": str(price) if price is not None else None,
186
+ "oracle_feeds": _morpho_oracle_feeds(reader, morpho_oracle),
184
187
  }
185
188
  dep = _resolve(reader, loan, depth + 1, visited, max_depth)
186
189
  node.dependencies = [dep]
@@ -30,6 +30,12 @@ TYPE_CHAINLINK = "ChainlinkAggregator"
30
30
  TYPE_CHAINLINK_STYLE = "chainlink_style"
31
31
  TYPE_ERC4626 = "ERC4626PriceFeed"
32
32
  TYPE_MORPHO = "CollateralTokenOnMorphoMarketPriceFeed"
33
+ MORPHO_ORACLE_FEED_ROLES = (
34
+ "BASE_FEED_1",
35
+ "BASE_FEED_2",
36
+ "QUOTE_FEED_1",
37
+ "QUOTE_FEED_2",
38
+ )
33
39
  TYPE_DUAL_XREF = "DualCrossReferencePriceFeed"
34
40
  TYPE_MIDDLEWARE_FALLBACK = "middleware_fallback"
35
41
  TYPE_UNKNOWN = "custom_unknown"
@@ -83,11 +89,21 @@ class Erc4626DetailJson(TypedDict):
83
89
  rate: str | None
84
90
 
85
91
 
92
+ class MorphoOracleFeedJson(AggregatorFeedDetailJson):
93
+ """A feed behind a Morpho oracle; ``role`` names the getter it came from."""
94
+
95
+ role: str
96
+
97
+
86
98
  class MorphoFeedDetailJson(TypedDict):
99
+ """``oracle_feeds`` lists the feeds the oracle uses; null when the oracle
100
+ does not expose them, so their freshness cannot be read."""
101
+
87
102
  morpho_oracle: ChecksumAddress
88
103
  collateral_token: ChecksumAddress | None
89
104
  loan_token: ChecksumAddress
90
105
  morpho_price: str | None
106
+ oracle_feeds: list[MorphoOracleFeedJson] | None
91
107
 
92
108
 
93
109
  class MiddlewareFallbackDetailJson(TypedDict):
@@ -413,6 +429,31 @@ def _resolve_chainlink(
413
429
  return node
414
430
 
415
431
 
432
+ def _morpho_oracle_feeds(
433
+ reader: OracleMappingReader, morpho_oracle: ChecksumAddress
434
+ ) -> list[MorphoOracleFeedJson] | None:
435
+ """The feeds behind a Morpho oracle, skipping its unused (zero) slots."""
436
+ feeds: list[MorphoOracleFeedJson] = []
437
+ for role in MORPHO_ORACLE_FEED_ROLES:
438
+ address = reader.morpho_oracle_feed(morpho_oracle, role)
439
+ if address is None:
440
+ return None
441
+ if address.lower() == ZERO_ADDRESS:
442
+ continue
443
+ feeds.append(
444
+ {
445
+ "address": address,
446
+ **_aggregator_detail(
447
+ reader.feed_description(address),
448
+ reader.feed_latest_round_data(address),
449
+ reader.feed_decimals(address),
450
+ ),
451
+ "role": role,
452
+ }
453
+ )
454
+ return feeds
455
+
456
+
416
457
  def _resolve_dual_xref(
417
458
  reader: OracleMappingReader,
418
459
  node: OracleNode,
@@ -59,6 +59,9 @@ class _MorphoOracle(ContractWrapper):
59
59
  def price(self) -> Call[int]:
60
60
  return self._view("price()", output_types=["uint256"], decoder=int)
61
61
 
62
+ def feed(self, role: str) -> Call[ChecksumAddress]:
63
+ return self._view(f"{role}()", output_types=["address"], decoder=_addr)
64
+
62
65
 
63
66
  class _DualXrefFeed(ContractWrapper):
64
67
  def asset_x(self) -> Call[ChecksumAddress]:
@@ -222,6 +225,11 @@ class OracleMappingReader:
222
225
  def morpho_oracle_price(self, morpho_oracle: ChecksumAddress) -> int | None:
223
226
  return self._safe(_MorphoOracle(morpho_oracle).price())
224
227
 
228
+ def morpho_oracle_feed(
229
+ self, morpho_oracle: ChecksumAddress, role: str
230
+ ) -> ChecksumAddress | None:
231
+ return self._safe(_MorphoOracle(morpho_oracle).feed(role))
232
+
225
233
  def feed_asset_x(self, source: ChecksumAddress) -> ChecksumAddress | None:
226
234
  return self._safe(_DualXrefFeed(source).asset_x())
227
235
 
@@ -52,6 +52,7 @@ class FakeReader:
52
52
  self.feeds: dict[str, Feed] = {}
53
53
  self.vaults: dict[str, Erc4626] = {}
54
54
  self.morpho_prices: dict[str, int] = {}
55
+ self.morpho_feeds: dict[str, dict[str, str]] = {}
55
56
  # oracle variant (middleware-fallback resolution); "_mw" because the
56
57
  # exact method name would be shadowed by the attribute
57
58
  self.underlying_mw: str | None = None
@@ -125,6 +126,9 @@ class FakeReader:
125
126
  def morpho_oracle_price(self, o: str) -> int | None:
126
127
  return self.morpho_prices.get(o)
127
128
 
129
+ def morpho_oracle_feed(self, o: str, role: str) -> str | None:
130
+ return self.morpho_feeds.get(o, {}).get(role)
131
+
128
132
  def feed_asset_x(self, s: str) -> str | None:
129
133
  return self._f(s).get("asset_x")
130
134
 
@@ -300,7 +300,22 @@
300
300
  "morpho_oracle": "0x0000000000000000000000000000000000000031",
301
301
  "collateral_token": "0x0000000000000000000000000000000000000006",
302
302
  "loan_token": "0x0000000000000000000000000000000000000001",
303
- "morpho_price": "970000000000000000000000"
303
+ "morpho_price": "970000000000000000000000",
304
+ "oracle_feeds": [
305
+ {
306
+ "address": "0x0000000000000000000000000000000000000032",
307
+ "description": "COLL / LOAN",
308
+ "round_id": "9",
309
+ "answer": "970000000000000000",
310
+ "decimals": 18,
311
+ "started_at": 1700000000,
312
+ "started_at_utc": "2023-11-14T22:13:20Z",
313
+ "updated_at": 1700000050,
314
+ "updated_at_utc": "2023-11-14T22:14:10Z",
315
+ "answered_in_round": "9",
316
+ "role": "BASE_FEED_1"
317
+ }
318
+ ]
304
319
  },
305
320
  "dependencies": [
306
321
  {
@@ -300,7 +300,22 @@
300
300
  "morpho_oracle": "0x0000000000000000000000000000000000000031",
301
301
  "collateral_token": "0x0000000000000000000000000000000000000006",
302
302
  "loan_token": "0x0000000000000000000000000000000000000001",
303
- "morpho_price": "970000000000000000000000"
303
+ "morpho_price": "970000000000000000000000",
304
+ "oracle_feeds": [
305
+ {
306
+ "address": "0x0000000000000000000000000000000000000032",
307
+ "description": "COLL / LOAN",
308
+ "round_id": "9",
309
+ "answer": "970000000000000000",
310
+ "decimals": 18,
311
+ "started_at": 1700000000,
312
+ "started_at_utc": "2023-11-14T22:13:20Z",
313
+ "updated_at": 1700000050,
314
+ "updated_at_utc": "2023-11-14T22:14:10Z",
315
+ "answered_in_round": "9",
316
+ "role": "BASE_FEED_1"
317
+ }
318
+ ]
304
319
  },
305
320
  "dependencies": [
306
321
  {
@@ -0,0 +1,96 @@
1
+ """A contract must not be able to make the SDK send an HTTP request.
2
+
3
+ EIP-3668 lets a contract revert with `OffchainLookup(urls, ...)`, which web3
4
+ follows by default from inside `eth.call`. Any address a caller passes is
5
+ therefore a request to a URL chosen by whoever deployed the contract.
6
+ """
7
+
8
+ from typing import Any
9
+ from unittest.mock import patch
10
+
11
+ import pytest
12
+ from eth_abi import encode
13
+ from eth_typing import URI
14
+ from web3 import Web3
15
+ from web3.providers.base import BaseProvider
16
+ from web3.types import RPCEndpoint, RPCResponse
17
+
18
+ from ipor_fusion import NotPlasmaVaultError, Web3Context
19
+ from ipor_fusion.core.access import resolve_access_manager
20
+ from ipor_fusion.errors import get_revert_reason
21
+
22
+ ADDRESS = Web3.to_checksum_address("0x" + "11" * 20)
23
+ OFFCHAIN_LOOKUP_SELECTOR = bytes.fromhex("556f1830")
24
+ LOOKUP_URL = "https://192.0.2.1/lookup"
25
+
26
+
27
+ def _offchain_lookup_revert() -> str:
28
+ args = encode(
29
+ ["address", "string[]", "bytes", "bytes4", "bytes"],
30
+ [ADDRESS, [LOOKUP_URL], b"", b"\xd8\xd7\x86\x9d", b""],
31
+ )
32
+ return "0x" + (OFFCHAIN_LOOKUP_SELECTOR + args).hex()
33
+
34
+
35
+ class _LookupProvider(BaseProvider):
36
+ """A chain where `ADDRESS` has code and reverts every call with a lookup."""
37
+
38
+ def make_request(self, method: RPCEndpoint, params: Any) -> RPCResponse:
39
+ if method == "eth_call":
40
+ return {
41
+ "jsonrpc": "2.0",
42
+ "id": 1,
43
+ "error": {
44
+ "code": 3,
45
+ "message": "execution reverted",
46
+ "data": _offchain_lookup_revert(),
47
+ },
48
+ }
49
+ results: dict[str, Any] = {
50
+ "eth_chainId": "0x1",
51
+ "eth_getCode": "0x6000",
52
+ "eth_getTransactionByHash": {
53
+ "blockHash": "0x" + "22" * 32,
54
+ "blockNumber": "0x1",
55
+ "from": ADDRESS,
56
+ "gas": "0x5208",
57
+ "gasPrice": "0x1",
58
+ "hash": "0x" + "33" * 32,
59
+ "input": "0x",
60
+ "nonce": "0x0",
61
+ "to": ADDRESS,
62
+ "transactionIndex": "0x0",
63
+ "value": "0x0",
64
+ "type": "0x0",
65
+ "v": "0x1b",
66
+ "r": "0x1",
67
+ "s": "0x1",
68
+ },
69
+ }
70
+ return {"jsonrpc": "2.0", "id": 1, "result": results[method]}
71
+
72
+ def endpoint_uri(self) -> URI:
73
+ return URI("test://lookup")
74
+
75
+
76
+ def _web3() -> Web3:
77
+ return Web3(_LookupProvider())
78
+
79
+
80
+ def test_context_built_on_any_web3_does_not_follow_a_lookup() -> None:
81
+ ctx = Web3Context(web3=_web3(), chain_id=1)
82
+
83
+ with patch("requests.Session.request") as request:
84
+ with pytest.raises(NotPlasmaVaultError):
85
+ resolve_access_manager(ctx, ADDRESS)
86
+
87
+ request.assert_not_called()
88
+
89
+
90
+ def test_revert_replay_does_not_follow_a_lookup() -> None:
91
+ receipt: Any = {"blockNumber": 1}
92
+
93
+ with patch("requests.Session.request") as request:
94
+ get_revert_reason(_web3(), bytes.fromhex("33" * 32), receipt)
95
+
96
+ request.assert_not_called()
@@ -154,6 +154,17 @@ def _reader() -> FakeReader:
154
154
  "loan": USDC,
155
155
  }
156
156
  r.morpho_prices[addr(0x31)] = 970_000 * 10**18
157
+ r.morpho_feeds[addr(0x31)] = {
158
+ "BASE_FEED_1": addr(0x32),
159
+ "BASE_FEED_2": addr(0),
160
+ "QUOTE_FEED_1": addr(0),
161
+ "QUOTE_FEED_2": addr(0),
162
+ }
163
+ r.feeds[addr(0x32)] = {
164
+ "round": (9, 97 * 10**16, 1_700_000_000, 1_700_000_050, 9),
165
+ "feed_decimals": 18,
166
+ "description": "COLL / LOAN",
167
+ }
157
168
 
158
169
  r.symbols[WAT] = "WAT"
159
170
  r.decimals[WAT] = 18
@@ -270,6 +270,53 @@ class TestResolve:
270
270
  assert [d.asset for d in node.dependencies] == [loan]
271
271
  assert node.path[:2] == ["COLL", "Morpho collateral/loan oracle"]
272
272
 
273
+ def test_morpho_oracle_feeds_report_their_rounds_and_skip_unused_slots(self):
274
+ r = FakeReader()
275
+ coll, loan = addr(1), addr(2)
276
+ feed, m_oracle, base_feed = addr(0x11), addr(0x31), addr(0x41)
277
+ r.sources[coll] = feed
278
+ r.prices[coll] = Price(asset=coll, amount=10**8, decimals=8)
279
+ r.feeds[feed] = {"morpho_oracle": m_oracle, "collateral": coll, "loan": loan}
280
+ r.feeds[base_feed] = {
281
+ "round": (7, 10**18, 1_700_000_000, 1_700_000_100, 7),
282
+ "feed_decimals": 18,
283
+ "description": "COLL / LOAN",
284
+ }
285
+ zero = addr(0)
286
+ r.morpho_feeds[m_oracle] = {
287
+ "BASE_FEED_1": base_feed,
288
+ "BASE_FEED_2": zero,
289
+ "QUOTE_FEED_1": zero,
290
+ "QUOTE_FEED_2": zero,
291
+ }
292
+ chainlink_asset(r, loan, addr(0x12), symbol="LOAN")
293
+
294
+ detail = om.resolve_asset(r, coll, max_depth=6).source_detail
295
+
296
+ assert detail is not None and "oracle_feeds" in detail
297
+ feeds = detail["oracle_feeds"]
298
+ assert feeds is not None
299
+ assert [(f["role"], f["address"]) for f in feeds] == [
300
+ ("BASE_FEED_1", base_feed)
301
+ ]
302
+ assert feeds[0]["description"] == "COLL / LOAN"
303
+ assert feeds[0]["updated_at"] == 1_700_000_100
304
+
305
+ def test_morpho_oracle_without_the_feed_getters_reports_none(self):
306
+ r = FakeReader()
307
+ coll, loan = addr(1), addr(2)
308
+ feed, m_oracle = addr(0x11), addr(0x31)
309
+ r.sources[coll] = feed
310
+ r.prices[coll] = Price(asset=coll, amount=10**8, decimals=8)
311
+ r.feeds[feed] = {"morpho_oracle": m_oracle, "collateral": coll, "loan": loan}
312
+ chainlink_asset(r, loan, addr(0x12), symbol="LOAN")
313
+
314
+ node = om.resolve_asset(r, coll, max_depth=6)
315
+
316
+ assert node.status == "resolved"
317
+ assert node.source_detail is not None and "oracle_feeds" in node.source_detail
318
+ assert node.source_detail["oracle_feeds"] is None
319
+
273
320
  def test_dual_xref_wins_over_chainlink(self):
274
321
  r = FakeReader()
275
322
  wsteth = addr(1)
@@ -1165,6 +1212,7 @@ class TestOracleMappingReader:
1165
1212
  assert reader.feed_asset_x_asset_y_feed(feed) == target
1166
1213
  assert reader.feed_asset_y_usd_feed(feed) == target
1167
1214
  assert reader.feed_aggregator(feed) == target
1215
+ assert reader.morpho_oracle_feed(addr(0x31), "BASE_FEED_1") == target
1168
1216
 
1169
1217
  ctx.call.return_value = encode(["uint256"], [1_030_000])
1170
1218
  assert reader.morpho_oracle_price(addr(0x31)) == 1_030_000
@@ -228,6 +228,7 @@ _ALL_SNAPSHOT: dict[str, frozenset[str]] = {
228
228
  "MorphoClaimFuse",
229
229
  "MorphoCollateralFuse",
230
230
  "MorphoFeedDetailJson",
231
+ "MorphoOracleFeedJson",
231
232
  "MorphoFlashLoanFuse",
232
233
  "MorphoFlowCapConfigJson",
233
234
  "MorphoMarket",
@@ -952,6 +953,7 @@ _ALL_SNAPSHOT: dict[str, frozenset[str]] = {
952
953
  "MiddlewarePricedToken",
953
954
  "UnpriceableCheck",
954
955
  "MorphoFeedDetailJson",
956
+ "MorphoOracleFeedJson",
955
957
  "MorphoMarket",
956
958
  "MorphoMarketParams",
957
959
  "MorphoMarketRates",