ipor-fusion 4.0.0.dev191__tar.gz → 4.0.0.dev200__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 (339) hide show
  1. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/AGENTS.md +14 -1
  2. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/MIGRATION_4.md +34 -1
  3. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/PKG-INFO +1 -1
  4. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/pyproject.toml +1 -1
  5. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/cli/vault_health.py +14 -0
  6. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/core/plasma_vault.py +28 -0
  7. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/mcp/models.py +26 -1
  8. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/readers/lending_health.py +2 -9
  9. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/readers/morpho.py +26 -16
  10. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/types.py +1 -0
  11. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/validation.py +94 -9
  12. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/vault_data.py +50 -4
  13. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/vault_info.py +10 -0
  14. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/fixtures/vault_info_golden/clean.json +10 -0
  15. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/fixtures/vault_info_golden/clean.mcp.json +10 -0
  16. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/fixtures/vault_info_golden/deployment_error.json +10 -0
  17. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/fixtures/vault_info_golden/deployment_error.mcp.json +10 -0
  18. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/fixtures/vault_info_golden/fetched.cli.json +10 -0
  19. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/fixtures/vault_info_golden/fetched.json +10 -0
  20. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/fixtures/vault_info_golden/fetched.mcp.json +10 -0
  21. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/fixtures/vault_info_golden/full.json +10 -0
  22. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/fixtures/vault_info_golden/full.mcp.json +10 -0
  23. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/fixtures/vault_info_golden/rich.json +10 -0
  24. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/fixtures/vault_info_golden/rich.mcp.json +10 -0
  25. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_cli_commands.py +36 -0
  26. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_cli_lending_health.py +47 -21
  27. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_cli_vault_predates_getters.py +54 -0
  28. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_mcp_models.py +2 -0
  29. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_plasma_vault.py +31 -0
  30. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_reader_encoding.py +11 -8
  31. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_validation.py +128 -0
  32. ipor_fusion-4.0.0.dev200/tests/test_vault_reconciliation_onchain_base.py +65 -0
  33. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/.claude-plugin/marketplace.json +0 -0
  34. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/.claude-plugin/plugin.json +0 -0
  35. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/.env.example +0 -0
  36. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/.gemini/settings.json +0 -0
  37. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/.git-blame-ignore-revs +0 -0
  38. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/.github/workflows/cd.yml +0 -0
  39. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/.github/workflows/ci.yml +0 -0
  40. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/.github/workflows/commit-lint.yml +0 -0
  41. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/.github/workflows/notify-slack.yml +0 -0
  42. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/.github/workflows/python-build.yml +0 -0
  43. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/.github/workflows/python-publish.yml +0 -0
  44. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/.github/workflows/release.yml +0 -0
  45. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/.github/workflows/report-slack.yml +0 -0
  46. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/.gitignore +0 -0
  47. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/.mcp.json +0 -0
  48. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/.pre-commit-config.yaml +0 -0
  49. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/.python-version +0 -0
  50. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/CHANGELOG.md +0 -0
  51. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/CLAUDE.md +0 -0
  52. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/CODEOWNERS +0 -0
  53. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/LICENSE +0 -0
  54. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/README.md +0 -0
  55. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/build.sh +0 -0
  56. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/examples/CONTRIBUTING.md +0 -0
  57. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/examples/README.md +0 -0
  58. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/examples/advanced_euler_v2_credit_market_base.py +0 -0
  59. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/examples/advanced_euler_v2_looping_arbitrum.py +0 -0
  60. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/examples/composed_hypercore_flow_arbitrum_hyperevm.py +0 -0
  61. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/examples/crosschain_ccip_usdc_arbitrum_hyperevm.py +0 -0
  62. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/examples/external_state_margin_leg_base.py +0 -0
  63. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/examples/simple_aave_v3_supply_base.py +0 -0
  64. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/pytest.ini +0 -0
  65. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/scripts/commit-check.sh +0 -0
  66. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/scripts/crosschain_readiness.py +0 -0
  67. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/skills/ipor-deploy-vault/SKILL.md +0 -0
  68. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/__init__.py +0 -0
  69. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/about.py +0 -0
  70. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/cache.py +0 -0
  71. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/chains.py +0 -0
  72. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/cli/__init__.py +0 -0
  73. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/cli/changelog_cmd.py +0 -0
  74. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/cli/config_cmd.py +0 -0
  75. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/cli/config_store.py +0 -0
  76. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/cli/main.py +0 -0
  77. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/cli/market_cmd.py +0 -0
  78. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/cli/options.py +0 -0
  79. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/cli/oracle_mapping_view.py +0 -0
  80. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/cli/vault_cmd.py +0 -0
  81. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/cli/vault_info_view.py +0 -0
  82. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/cli/vault_markets_view.py +0 -0
  83. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/cli/vault_rendering.py +0 -0
  84. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/config/__init__.py +0 -0
  85. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/config/roles.py +0 -0
  86. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/core/__init__.py +0 -0
  87. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/core/access.py +0 -0
  88. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/core/address.py +0 -0
  89. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/core/context.py +0 -0
  90. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/core/contract.py +0 -0
  91. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/core/erc20.py +0 -0
  92. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/core/external_state_executor.py +0 -0
  93. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/core/fee_manager.py +0 -0
  94. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/core/fuse.py +0 -0
  95. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/core/fuse_action.py +0 -0
  96. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/core/fusion_factory.py +0 -0
  97. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/core/logs.py +0 -0
  98. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/core/multicall.py +0 -0
  99. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/core/oracle.py +0 -0
  100. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/core/rewards_manager.py +0 -0
  101. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/core/simulation.py +0 -0
  102. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/core/simulation_result.py +0 -0
  103. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/core/withdraw_manager.py +0 -0
  104. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/crosschain/__init__.py +0 -0
  105. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/crosschain/ccip/__init__.py +0 -0
  106. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/crosschain/ccip/chainlink.py +0 -0
  107. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/crosschain/ccip/codec.py +0 -0
  108. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/crosschain/ccip/contracts.py +0 -0
  109. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/crosschain/ccip/events.py +0 -0
  110. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/crosschain/ccip/indexer.py +0 -0
  111. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/crosschain/ccip/lane.py +0 -0
  112. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/crosschain/ccip/quote.py +0 -0
  113. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/crosschain/ccip/transport.py +0 -0
  114. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/crosschain/contracts.py +0 -0
  115. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/crosschain/discovery.py +0 -0
  116. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/crosschain/errors.py +0 -0
  117. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/crosschain/fuses/__init__.py +0 -0
  118. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/crosschain/fuses/base.py +0 -0
  119. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/crosschain/fuses/ccip.py +0 -0
  120. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/crosschain/fuses/stargate.py +0 -0
  121. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/crosschain/lane.py +0 -0
  122. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/crosschain/logs.py +0 -0
  123. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/crosschain/messages.py +0 -0
  124. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/crosschain/simulation.py +0 -0
  125. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/crosschain/stargate/__init__.py +0 -0
  126. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/crosschain/stargate/contracts.py +0 -0
  127. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/crosschain/stargate/lane.py +0 -0
  128. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/crosschain/stargate/layerzero.py +0 -0
  129. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/crosschain/stargate/transport.py +0 -0
  130. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/crosschain/transport.py +0 -0
  131. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/dependency_graph.py +0 -0
  132. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/entry_cli.py +0 -0
  133. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/entry_mcp.py +0 -0
  134. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/errors.py +0 -0
  135. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/explorer.py +0 -0
  136. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/field_docs.py +0 -0
  137. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/fuses/__init__.py +0 -0
  138. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/fuses/aave_v3.py +0 -0
  139. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/fuses/async_action.py +0 -0
  140. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/fuses/base.py +0 -0
  141. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/fuses/compound_v3.py +0 -0
  142. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/fuses/erc4626.py +0 -0
  143. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/fuses/euler_v2.py +0 -0
  144. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/fuses/events.py +0 -0
  145. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/fuses/external_state.py +0 -0
  146. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/fuses/external_state_event_decoding.py +0 -0
  147. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/fuses/fluid_instadapp.py +0 -0
  148. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/fuses/gearbox_v3.py +0 -0
  149. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/fuses/hypercore.py +0 -0
  150. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/fuses/hypercore_event_decoding.py +0 -0
  151. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/fuses/merkl.py +0 -0
  152. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/fuses/morpho.py +0 -0
  153. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/fuses/ramses_v2.py +0 -0
  154. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/fuses/uniswap_v3.py +0 -0
  155. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/fuses/universal.py +0 -0
  156. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/guide/__init__.py +0 -0
  157. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/guide/architecture.md +0 -0
  158. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/guide/glossary.md +0 -0
  159. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/guide/invariants.md +0 -0
  160. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/guide/quickstart.md +0 -0
  161. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/market_ids.py +0 -0
  162. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/market_info.py +0 -0
  163. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/mcp/__init__.py +0 -0
  164. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/mcp/guide.py +0 -0
  165. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/mcp/server.py +0 -0
  166. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/morpho_api.py +0 -0
  167. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/py.typed +0 -0
  168. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/readers/__init__.py +0 -0
  169. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/readers/aave_v3.py +0 -0
  170. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/readers/aave_v4.py +0 -0
  171. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/readers/compound_v3.py +0 -0
  172. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/readers/hypercore.py +0 -0
  173. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/readers/hypercore_outcome.py +0 -0
  174. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/readers/oracle_mapping.py +0 -0
  175. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/readers/oracle_mapping_nodes.py +0 -0
  176. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/readers/oracle_mapping_reader.py +0 -0
  177. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/readers/position_manager.py +0 -0
  178. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/readers/ramses_v2.py +0 -0
  179. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/readers/uniswap_v3.py +0 -0
  180. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/readers/unpriceable.py +0 -0
  181. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/substrates.py +0 -0
  182. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/vault_erc20.py +0 -0
  183. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/vault_managers.py +0 -0
  184. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/vault_positions.py +0 -0
  185. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/src/ipor_fusion/vault_reads.py +0 -0
  186. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/UniswapV3UniversalRouter.py +0 -0
  187. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/_crosschain.py +0 -0
  188. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/_crosschain_lifecycle.py +0 -0
  189. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/_crosschain_recovery.py +0 -0
  190. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/_euler_v2.py +0 -0
  191. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/_event_args.py +0 -0
  192. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/_fake_rpc.py +0 -0
  193. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/_foundry.py +0 -0
  194. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/_golden.py +0 -0
  195. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/_guide.py +0 -0
  196. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/_hypercore_model.py +0 -0
  197. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/_hypercore_shadow.py +0 -0
  198. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/_multicall.py +0 -0
  199. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/_oracle_mapping_fake.py +0 -0
  200. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/_simulate.py +0 -0
  201. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/_validation.py +0 -0
  202. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/addresses.py +0 -0
  203. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/conftest.py +0 -0
  204. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/constants.py +0 -0
  205. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/fixtures/hypercore_hip3_flow.json +0 -0
  206. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/fixtures/hypercore_market55.json +0 -0
  207. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/fixtures/market_golden/meta_morpho_v1.json +0 -0
  208. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/fixtures/market_golden/meta_morpho_v1.mcp.json +0 -0
  209. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/fixtures/market_golden/meta_morpho_v2.json +0 -0
  210. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/fixtures/market_golden/meta_morpho_v2.mcp.json +0 -0
  211. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/fixtures/market_golden/morpho_api/market.json +0 -0
  212. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/fixtures/market_golden/morpho_api/vault_v1.json +0 -0
  213. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/fixtures/market_golden/morpho_api/vault_v2.json +0 -0
  214. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/fixtures/market_golden/morpho_api/vault_v2_not_found.json +0 -0
  215. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/fixtures/market_golden/morpho_blue.json +0 -0
  216. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/fixtures/market_golden/morpho_blue.mcp.json +0 -0
  217. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/fixtures/market_golden/morpho_blue_api_error.json +0 -0
  218. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/fixtures/market_golden/morpho_blue_api_error.mcp.json +0 -0
  219. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/fixtures/market_golden/morpho_blue_no_api.json +0 -0
  220. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/fixtures/market_golden/morpho_blue_no_api.mcp.json +0 -0
  221. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/fixtures/oracle_mapping_golden/mapping.json +0 -0
  222. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/fixtures/oracle_mapping_golden/mapping.mcp.json +0 -0
  223. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/fixtures/simulate_v1_request.json +0 -0
  224. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/fixtures/vault_info_golden/clean.txt +0 -0
  225. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/fixtures/vault_info_golden/deployment_error.txt +0 -0
  226. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/fixtures/vault_info_golden/full.txt +0 -0
  227. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/fixtures/vault_info_golden/rich.txt +0 -0
  228. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_about.py +0 -0
  229. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_access_manager.py +0 -0
  230. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_cache.py +0 -0
  231. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_chains.py +0 -0
  232. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_cli_changelog.py +0 -0
  233. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_cli_config_store.py +0 -0
  234. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_cli_helpers.py +0 -0
  235. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_cli_hypercore.py +0 -0
  236. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_cli_market_golden.py +0 -0
  237. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_cli_market_morpho_blue.py +0 -0
  238. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_cli_oracle_mapping.py +0 -0
  239. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_cli_oracle_mapping_golden.py +0 -0
  240. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_cli_pending_requests_scan.py +0 -0
  241. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_cli_role_accounts.py +0 -0
  242. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_cli_vault_info_golden.py +0 -0
  243. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_cli_vault_validate.py +0 -0
  244. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_contract_call.py +0 -0
  245. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_contract_encoder.py +0 -0
  246. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_crosschain_ccip_events.py +0 -0
  247. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_crosschain_ccip_indexer.py +0 -0
  248. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_crosschain_ccip_quote.py +0 -0
  249. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_crosschain_chainlink.py +0 -0
  250. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_crosschain_contracts.py +0 -0
  251. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_crosschain_encoding.py +0 -0
  252. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_crosschain_errors.py +0 -0
  253. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_crosschain_lane.py +0 -0
  254. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_crosschain_layering.py +0 -0
  255. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_crosschain_readiness.py +0 -0
  256. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_crosschain_readiness_report.py +0 -0
  257. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_crosschain_transport.py +0 -0
  258. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_docs_typing.py +0 -0
  259. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_erc20.py +0 -0
  260. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_error_hierarchy.py +0 -0
  261. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_examples_composed_hypercore.py +0 -0
  262. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_examples_crosschain.py +0 -0
  263. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_examples_euler_looping_arbitrum.py +0 -0
  264. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_examples_vaults.py +0 -0
  265. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_explorer.py +0 -0
  266. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_external_state_events.py +0 -0
  267. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_external_state_executor.py +0 -0
  268. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_fee_manager.py +0 -0
  269. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_fuse_encoding.py +0 -0
  270. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_fusion_instance.py +0 -0
  271. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_guide.py +0 -0
  272. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_hypercore_deployment.py +0 -0
  273. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_hypercore_events.py +0 -0
  274. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_hypercore_flow.py +0 -0
  275. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_hypercore_live.py +0 -0
  276. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_hypercore_outcome.py +0 -0
  277. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_hypercore_readers.py +0 -0
  278. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_hypercore_simulation.py +0 -0
  279. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_logs.py +0 -0
  280. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_mcp_hypercore.py +0 -0
  281. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_mcp_server.py +0 -0
  282. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_mcp_vault_validate.py +0 -0
  283. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_multicall.py +0 -0
  284. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_oracle.py +0 -0
  285. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_oracle_mapping.py +0 -0
  286. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_public_api.py +0 -0
  287. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_public_type_reachability.py +0 -0
  288. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_readiness_selection.py +0 -0
  289. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_revert_decoding.py +0 -0
  290. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_rewards_manager.py +0 -0
  291. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_roles.py +0 -0
  292. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_sdk_layering.py +0 -0
  293. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_simulate_borrow_aave_v3_mainnet.py +0 -0
  294. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_simulate_claim_merkl_wrapper_base.py +0 -0
  295. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_simulate_claim_morpho_rewards.py +0 -0
  296. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_simulate_crosschain_canary.py +0 -0
  297. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_simulate_crosschain_hypercore.py +0 -0
  298. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_simulate_crosschain_hyperevm.py +0 -0
  299. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_simulate_crosschain_lifecycle.py +0 -0
  300. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_simulate_euler_v2_batch_base.py +0 -0
  301. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_simulate_euler_v2_credit_market_base.py +0 -0
  302. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_simulate_euler_v2_swap_deploy_base.py +0 -0
  303. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_simulate_euler_v2_swap_reconfigure_base.py +0 -0
  304. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_simulate_euler_v2_swap_registry_base.py +0 -0
  305. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_simulate_examples_arbitrum.py +0 -0
  306. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_simulate_examples_base.py +0 -0
  307. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_simulate_examples_crosschain.py +0 -0
  308. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_simulate_external_state_executor_base.py +0 -0
  309. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_simulate_fusion_factory_clone_base.py +0 -0
  310. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_simulate_looping_aave_v3_base.py +0 -0
  311. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_simulate_looping_morpho_blue_base.py +0 -0
  312. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_simulate_morpho_blue.py +0 -0
  313. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_simulate_pilot_v3.py +0 -0
  314. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_simulate_pilot_v4_uniswap.py +0 -0
  315. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_simulate_pilot_v5_ramses.py +0 -0
  316. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_simulate_pilot_v5_scheduled.py +0 -0
  317. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_simulate_plasma_vault_setters_base.py +0 -0
  318. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_simulate_readers_integration.py +0 -0
  319. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_simulate_release_funds.py +0 -0
  320. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_simulate_swap_on_base.py +0 -0
  321. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_simulate_vault_from_scratch_base.py +0 -0
  322. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_simulation_errors.py +0 -0
  323. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_simulation_observation.py +0 -0
  324. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_solidity_compiler.py +0 -0
  325. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_solidity_mirrors.py +0 -0
  326. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_substrates.py +0 -0
  327. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_types_and_events.py +0 -0
  328. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_unpriceable_tokens.py +0 -0
  329. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_vault_data_block.py +0 -0
  330. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_vault_dep_graph.py +0 -0
  331. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_vault_info_fetched_golden.py +0 -0
  332. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_vault_info_onchain_base.py +0 -0
  333. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_vault_info_onchain_base_no_withdraw_manager.py +0 -0
  334. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_vault_simulator_blocks.py +0 -0
  335. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_web3_context.py +0 -0
  336. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/test_withdraw_manager.py +0 -0
  337. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/typed_client/consumer.py +0 -0
  338. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/tests/typed_client/rejected.py +0 -0
  339. {ipor_fusion-4.0.0.dev191 → ipor_fusion-4.0.0.dev200}/uv.lock +0 -0
@@ -513,7 +513,20 @@ decoder adds its own fields.
513
513
  - `ZeroBalanceFuse` markets are capabilities (swap, flash-loan,
514
514
  instant-withdrawal, admin) with structurally zero balance, not venues. They
515
515
  go in `zero_balance_fuses` and are not counted as liquidity markets.
516
- - Reconciliation: `totalAssets = balance_fuses_sum + underlying_on_vault`.
516
+ - Reconciliation: `totalAssets = balance_fuses_sum + underlying_on_vault +
517
+ rewards_manager_balance` (`PlasmaVault._getGrossTotalAssets` adds
518
+ `IRewardsClaimManager.balanceOf()`, the vested rewards, when a manager is set;
519
+ 0 without one; a manager or balance that cannot be read is a
520
+ `rewards_manager_balance` warning, and pre-rename vaults answer
521
+ `getRewardsManagerAddress()`). Vaults built from contracts before ipor-fusion
522
+ #475 (Feb 2026), the August 2024 audit generation included, return gross minus
523
+ the unrealized management fee; later ones return gross. The deployed version
524
+ is not readable, so the reconciliation decides by behavior: it nets the fee
525
+ (`management_fee_netted`) when `totalAssets` equals `max(0, gross - fee)` and not
526
+ `gross`, with gross built from the stored global market total
527
+ (`PlasmaVault.read_stored_markets_total`, an ERC-7201 slot), never from the
528
+ per-market caches.
529
+ Never key it on the legacy-getter or pre-audit detection.
517
530
  Balance fuses already price non-underlying ERC20s (e.g. ERC20BalanceFuse
518
531
  values BOLD + BOLDUSDC-gauge in WETH terms). Never add all ERC20 values on
519
532
  top of balance fuse totals; that double-counts. `erc20_direct_total` in JSON
@@ -639,7 +639,7 @@ converted amounts `Amount`. A custom `VaultState` annotates the same.
639
639
  `Shares` (vault shares), and `transfer` / `approve` / `transfer_from` /
640
640
  `set_total_supply_cap` take `Shares`; `ERC20` keeps `Amount`.
641
641
  `Reconciliation` amounts (`balance_fuses_total`, `underlying_on_vault`,
642
- `erc20_direct_total`, `sum`, `on_chain_total_assets`, `delta`,
642
+ `rewards_manager_balance`, `erc20_direct_total`, `sum`, `on_chain_total_assets`, `delta`,
643
643
  `pending_withdrawals`, `implied_market_total`, `market_storage_divergence`) are
644
644
  `Amount`. `FusionInstance.asset_decimals` / `underlying_token_decimals` are
645
645
  `Decimals`, and `CloneArgs.redemption_delay_seconds` is a `Period`.
@@ -950,6 +950,39 @@ and `log_index` was passed through as given (a hex string from a raw log); the e
950
950
  These apply to every caller, type-checked or not. Changes described in full above
951
951
  are listed with a link.
952
952
 
953
+ - **The reconciliation counts the rewards claim manager.** `PlasmaVault.totalAssets`
954
+ adds `RewardsClaimManager.balanceOf()` (vested rewards) to the vault's idle
955
+ underlying and its markets; 3.x left it out, so a vault with vested rewards showed
956
+ a delta of that size and an inflated `implied_market_total` (a false market-storage
957
+ drift). `fetch_vault_data` reads it into `VaultData.rewards_manager_balance` (0
958
+ without a manager, `None` when the read fails, which validation reports as a
959
+ `rewards_manager_balance` warning), and `Reconciliation`, the `vault info` and
960
+ `vault validate` documents carry a `rewards_manager_balance` amount. A custom
961
+ `VaultState` adds the `rewards_manager_balance` property, and code constructing a
962
+ `Reconciliation` passes `rewards_manager_balance` and `rewards_manager_balance_usd`.
963
+
964
+ - **The reconciliation follows the vault's `totalAssets` formula.** Contracts before
965
+ ipor-fusion #475 (Feb 2026) return gross assets minus the unrealized management
966
+ fee, later ones gross. 3.x always compared gross, so every older vault with an
967
+ accrued fee showed that fee as a delta. The reconciliation now subtracts the
968
+ fee (`Reconciliation.management_fee_netted`, also in both JSON documents) when
969
+ `totalAssets` equals `max(0, gross - fee)` rather than `gross`, gross built from
970
+ the global market total the vault stores (`VaultData.stored_markets_total`, read
971
+ by `PlasmaVault.read_stored_markets_total`); the vault's age plays no part. A
972
+ custom `VaultState` adds the `unrealized_management_fee` (`VaultData` derives it
973
+ from `fee_data`) and `stored_markets_total` properties; code constructing a
974
+ `Reconciliation` passes `management_fee_netted` and `management_fee_netted_usd`.
975
+
976
+ - **Morpho shares convert to assets as Morpho Blue does.** The position breakdown
977
+ (`MorphoReader.read_position_breakdown`, the `vault info` Morpho rows) and the Morpho
978
+ lending health follow `SharesMathLib`: integer math with 1e6 virtual shares and one
979
+ virtual asset, borrow rounded up, supply rounded down, on the market totals Morpho
980
+ stores (interest accrued since the market's last update is not included). 3.x
981
+ converted borrow shares by dividing by `total_shares + 1` in floating point and
982
+ supply shares without the virtual offsets, so large positions lost their low digits
983
+ and small or empty markets could be off by orders of magnitude: expect materially
984
+ different numbers where they were wrong.
985
+
953
986
  - **MCP `outputSchema` describes every nested block.** The tool output is
954
987
  byte-identical; the schemas of `vault_info`, `vault_oracle_mapping`,
955
988
  `market_morpho_blue` and `market_meta_morpho` replace each free-form object with
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: ipor-fusion
3
- Version: 4.0.0.dev191
3
+ Version: 4.0.0.dev200
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.dev191"
3
+ version = "4.0.0.dev200"
4
4
  description = "The IPOR Fusion SDK for Python"
5
5
  authors = [
6
6
  { name = "Adam Mydlarz", email = "amydlarz@gmail.com" },
@@ -73,6 +73,20 @@ def _print_reconciliation(data: VaultData, validation: VaultValidation) -> None:
73
73
  click.echo(
74
74
  f" Underlying on vault: {fmt_underlying} {sym}{usd_underlying} [{sym} held directly]"
75
75
  )
76
+ if recon.rewards_manager_balance > 0:
77
+ fmt_rewards = _format_amount(recon.rewards_manager_balance, decimals)
78
+ usd_rewards = f" (${recon.rewards_manager_balance_usd:,.2f})" if price else ""
79
+ click.echo(
80
+ f" Rewards manager: {fmt_rewards} {sym}{usd_rewards}"
81
+ " [vested rewards, in totalAssets]"
82
+ )
83
+ if recon.management_fee_netted > 0:
84
+ fmt_fee = _format_amount(recon.management_fee_netted, decimals)
85
+ usd_fee = f" (${recon.management_fee_netted_usd:,.2f})" if price else ""
86
+ click.echo(
87
+ f" Management fee: -{fmt_fee} {sym}{usd_fee}"
88
+ " [unrealized, netted by totalAssets]"
89
+ )
76
90
  click.echo(f" Sum: {fmt_sum} {sym}{usd_sum}")
77
91
  click.echo(f" On-chain totalAssets: {fmt_onchain} {sym}{usd_onchain}")
78
92
  sign = "+" if recon.delta >= 0 else "-"
@@ -90,6 +90,15 @@ def _market_id_list_decoder(value: tuple[int, ...]) -> list[MarketId]:
90
90
 
91
91
  _BALANCE_OF_SELECTOR = function_signature_to_4byte_selector("balanceOf()")
92
92
 
93
+ # PlasmaVaultStorageLib.PLASMA_VAULT_TOTAL_ASSETS_IN_ALL_MARKETS, and its value
94
+ # before the June 2024 storage rename (ipor-fusion #43).
95
+ _TOTAL_ASSETS_IN_ALL_MARKETS_SLOT = int(
96
+ "0x24e02552e88772b8e8fd15f3e6699ba530635ffc6b52322da922b0b497a77300", 16
97
+ )
98
+ _LEGACY_TOTAL_ASSETS_SLOT = int(
99
+ "0xf660efede6b8071aab61a75191de57a4a8a416b088f961d8c2a18168aa7c7700", 16
100
+ )
101
+
93
102
 
94
103
  def _read_result_data(result: tuple[bytes]) -> bytes:
95
104
  """``ReadResult.data``, undecoded."""
@@ -449,6 +458,15 @@ class PlasmaVault(ContractWrapper):
449
458
  decoder=Web3.to_checksum_address,
450
459
  )
451
460
 
461
+ def get_rewards_manager_address(self) -> Call[ChecksumAddress]:
462
+ """The getter of vaults deployed before the manager was renamed
463
+ RewardsClaimManager (June 2024)."""
464
+ return self._view(
465
+ "getRewardsManagerAddress()",
466
+ output_types=["address"],
467
+ decoder=Web3.to_checksum_address,
468
+ )
469
+
452
470
  def get_performance_fee_data(self) -> Call[PerformanceFeeData]:
453
471
  return self._view(
454
472
  "getPerformanceFeeData()",
@@ -576,6 +594,16 @@ class PlasmaVault(ContractWrapper):
576
594
  for market_id, fuse in zip(market_ids, fuses, strict=True)
577
595
  ]
578
596
 
597
+ def read_stored_markets_total(self, ctx: Web3Context) -> Amount:
598
+ """The global market total the vault stores (the
599
+ ``totalAssetsInAllMarkets`` term of ``totalAssets``), read from its
600
+ ERC-7201 slot; vaults built before June 2024 keep it at an earlier slot,
601
+ read when the current one is empty."""
602
+ for slot in (_TOTAL_ASSETS_IN_ALL_MARKETS_SLOT, _LEGACY_TOTAL_ASSETS_SLOT):
603
+ if value := int.from_bytes(ctx.get_storage_at(self._address, slot)):
604
+ return Amount(value)
605
+ return Amount(0)
606
+
579
607
  def _address_at(self, ctx: Web3Context, slot: int) -> ChecksumAddress:
580
608
  return _address_in_word(ctx.get_storage_at(self._address, slot))
581
609
 
@@ -397,6 +397,16 @@ class ERC20Entry(_Base):
397
397
  class Reconciliation(_Base):
398
398
  balance_fuses_total: Amount
399
399
  underlying_on_vault: Amount
400
+ rewards_manager_balance: Amount | None = Field(
401
+ default=None,
402
+ description="RewardsClaimManager.balanceOf(), which totalAssets includes; "
403
+ "0 without a manager or when it could not be read.",
404
+ )
405
+ management_fee_netted: Amount | None = Field(
406
+ default=None,
407
+ description="Unrealized management fee subtracted from sum: totalAssets "
408
+ "nets it on vaults built before ipor-fusion #475; 0 otherwise.",
409
+ )
400
410
  erc20_direct_total: Amount
401
411
  sum: Amount
402
412
  on_chain_total_assets: Amount
@@ -764,10 +774,19 @@ class FindingModel(_Base):
764
774
 
765
775
 
766
776
  class ReconciliationModel(_Base):
767
- """totalAssets against balance fuses plus idle underlying."""
777
+ """totalAssets against balance fuses, idle underlying and the rewards
778
+ claim manager's balance."""
768
779
 
769
780
  balance_fuses_total: Amount
770
781
  underlying_on_vault: Amount
782
+ rewards_manager_balance: Amount = Field(
783
+ description="RewardsClaimManager.balanceOf(), which totalAssets includes; "
784
+ "0 without a manager or when it could not be read."
785
+ )
786
+ management_fee_netted: Amount = Field(
787
+ description="Unrealized management fee subtracted from sum: totalAssets "
788
+ "nets it on vaults built before ipor-fusion #475; 0 otherwise."
789
+ )
771
790
  erc20_direct_total: Amount = Field(
772
791
  description="Informational: already valued by the ERC20_VAULT_BALANCE "
773
792
  "balance fuse, never added to sum."
@@ -797,6 +816,12 @@ class ReconciliationModel(_Base):
797
816
  underlying_on_vault=amount(
798
817
  rec.underlying_on_vault, rec.underlying_on_vault_usd
799
818
  ),
819
+ rewards_manager_balance=amount(
820
+ rec.rewards_manager_balance, rec.rewards_manager_balance_usd
821
+ ),
822
+ management_fee_netted=amount(
823
+ rec.management_fee_netted, rec.management_fee_netted_usd
824
+ ),
800
825
  erc20_direct_total=amount(
801
826
  rec.erc20_direct_total, rec.erc20_direct_total_usd
802
827
  ),
@@ -7,7 +7,6 @@ for lending positions held by Plasma Vaults.
7
7
  from __future__ import annotations
8
8
 
9
9
  import logging
10
- import math
11
10
  from concurrent.futures import ThreadPoolExecutor
12
11
  from dataclasses import dataclass
13
12
 
@@ -24,6 +23,7 @@ from ipor_fusion.readers.aave_v4 import AaveV4OracleReader, AaveV4SpokeReader
24
23
  from ipor_fusion.readers.morpho import (
25
24
  MorphoReader,
26
25
  morpho_blue_address,
26
+ to_assets_up,
27
27
  )
28
28
  from ipor_fusion.substrates import decode_substrate, market_name
29
29
  from ipor_fusion.types import (
@@ -145,13 +145,6 @@ def _call_morpho_oracle_price(ctx: Web3Context, oracle_address: ChecksumAddress)
145
145
  return price
146
146
 
147
147
 
148
- def _shares_to_assets_up(shares: int, total_assets: int, total_shares: int) -> int:
149
- """Convert shares to assets, rounding up (Morpho convention)."""
150
- if total_shares == 0:
151
- return shares
152
- return math.ceil(shares * (total_assets + 1) / (total_shares + 1))
153
-
154
-
155
148
  def _compute_morpho_market_health(
156
149
  ctx: Web3Context,
157
150
  reader: MorphoReader,
@@ -197,7 +190,7 @@ def _compute_morpho_market_health(
197
190
  _logger.debug("Failed to read Morpho oracle price for %s", morpho_market_id)
198
191
  return None
199
192
 
200
- borrowed = _shares_to_assets_up(
193
+ borrowed = to_assets_up(
201
194
  position.borrow_shares, market.total_borrow_assets, market.total_borrow_shares
202
195
  )
203
196
 
@@ -62,6 +62,26 @@ def morpho_blue_address(chain_id: ChainId) -> ChecksumAddress:
62
62
  )
63
63
 
64
64
 
65
+ # Morpho Blue's SharesMathLib: virtual shares and assets offset every
66
+ # conversion, so a market's first depositor cannot inflate the share price.
67
+ VIRTUAL_SHARES = 10**6
68
+ VIRTUAL_ASSETS = 1
69
+
70
+
71
+ def to_assets_down(shares: int, total_assets: int, total_shares: int) -> Amount:
72
+ """``SharesMathLib.toAssetsDown``: supply shares to assets, rounded down."""
73
+ return Amount(
74
+ shares * (total_assets + VIRTUAL_ASSETS) // (total_shares + VIRTUAL_SHARES)
75
+ )
76
+
77
+
78
+ def to_assets_up(shares: int, total_assets: int, total_shares: int) -> Amount:
79
+ """``SharesMathLib.toAssetsUp``: borrow shares to assets, rounded up."""
80
+ numerator = shares * (total_assets + VIRTUAL_ASSETS)
81
+ denominator = total_shares + VIRTUAL_SHARES
82
+ return Amount((numerator + denominator - 1) // denominator)
83
+
84
+
65
85
  @dataclass(slots=True)
66
86
  class MorphoMarket:
67
87
  """On-chain state of a Morpho Blue lending market."""
@@ -206,22 +226,12 @@ class MorphoReader(ContractWrapper):
206
226
  params_read = batch.add(self.market_params(market_id))
207
227
  batch.run()
208
228
  pos, market, params = pos_read.value, market_read.value, params_read.value
209
- if market.total_borrow_shares > 0:
210
- borrow_assets = math.ceil(
211
- pos.borrow_shares
212
- * (market.total_borrow_assets + 1)
213
- / (market.total_borrow_shares + 1)
214
- )
215
- else:
216
- borrow_assets = 0
217
- if market.total_supply_shares > 0:
218
- supply_assets = (
219
- pos.supply_shares
220
- * market.total_supply_assets
221
- // market.total_supply_shares
222
- )
223
- else:
224
- supply_assets = 0
229
+ borrow_assets = to_assets_up(
230
+ pos.borrow_shares, market.total_borrow_assets, market.total_borrow_shares
231
+ )
232
+ supply_assets = to_assets_down(
233
+ pos.supply_shares, market.total_supply_assets, market.total_supply_shares
234
+ )
225
235
  return MorphoPositionBreakdown(
226
236
  market_id=market_id,
227
237
  loan_token=params.loan_token,
@@ -79,6 +79,7 @@ Severity: TypeAlias = Literal["ok", "warning", "critical"]
79
79
  Verdict: TypeAlias = Literal["ok", "warning", "critical"]
80
80
  Check: TypeAlias = Literal[
81
81
  "unimplemented_getter",
82
+ "rewards_manager_balance",
82
83
  "orphan_fuse",
83
84
  "missing_erc20_dependency",
84
85
  "unpriceable_token",
@@ -119,6 +119,22 @@ class VaultState(Protocol):
119
119
  def total_assets(self) -> Amount: ...
120
120
  @property
121
121
  def underlying_balance_on_vault(self) -> Amount: ...
122
+ @property
123
+ def stored_markets_total(self) -> Amount | None:
124
+ """The global market total the vault stores; None when not read."""
125
+ ...
126
+
127
+ @property
128
+ def unrealized_management_fee(self) -> Amount | None:
129
+ """``getUnrealizedManagementFee()``, None when not read."""
130
+ ...
131
+
132
+ @property
133
+ def rewards_manager_balance(self) -> Amount | None:
134
+ """The RewardsClaimManager's balanceOf(), part of totalAssets; 0
135
+ without a manager, None when it could not be read."""
136
+ ...
137
+
122
138
  @property
123
139
  def price_oracle(self) -> ChecksumAddress: ...
124
140
  @property
@@ -155,20 +171,30 @@ class Finding:
155
171
 
156
172
  @dataclass(frozen=True)
157
173
  class Reconciliation:
158
- """``totalAssets`` against what the vault's markets and idle underlying add
159
- up to. Raw amounts are in the underlying asset's decimals; ``*_usd`` values
174
+ """``totalAssets`` against what the vault's markets, idle underlying and
175
+ rewards claim manager add up to. Raw amounts are in the underlying asset's decimals; ``*_usd`` values
160
176
  are 0 when the asset has no USD price (``on_chain_total_assets_usd``:
161
177
  ``None``).
162
178
 
163
179
  Balance fuses already value non-underlying ERC20s (via
164
180
  ERC20_VAULT_BALANCE), so ``sum`` is balance fuses plus idle underlying
165
- only; ``erc20_direct_total`` is informational and never added to it.
181
+ plus the rewards claim manager's balance, minus the unrealized management
182
+ fee where ``totalAssets`` nets it (``management_fee_netted``);
183
+ ``erc20_direct_total`` is informational and never added to it.
166
184
  """
167
185
 
168
186
  balance_fuses_total: Amount
169
187
  balance_fuses_total_usd: float
170
188
  underlying_on_vault: Amount
171
189
  underlying_on_vault_usd: float
190
+ # RewardsClaimManager.balanceOf(): vested rewards totalAssets counts
191
+ # (0 without a manager or when it could not be read).
192
+ rewards_manager_balance: Amount
193
+ rewards_manager_balance_usd: float
194
+ # The unrealized management fee totalAssets subtracts, on vaults whose
195
+ # contracts net it (before ipor-fusion #475); 0 on a vault returning gross.
196
+ management_fee_netted: Amount
197
+ management_fee_netted_usd: float
172
198
  erc20_direct_total: Amount
173
199
  erc20_direct_total_usd: float
174
200
  sum: Amount
@@ -181,7 +207,8 @@ class Reconciliation:
181
207
  # sharesToRelease of the WithdrawManager converted to assets.
182
208
  pending_withdrawals: Amount
183
209
  pending_withdrawals_usd: float
184
- # totalAssets minus idle underlying: the global market total the vault
210
+ # totalAssets minus idle underlying and the rewards claim manager's
211
+ # balance: the global market total the vault
185
212
  # stores, which per-market caches drift from after partial refreshes.
186
213
  implied_market_total: Amount
187
214
 
@@ -259,6 +286,7 @@ def validate_vault_state(state: VaultState) -> VaultValidation:
259
286
  reconciliation = _reconcile(state)
260
287
  findings = [
261
288
  *_unimplemented_getter_findings(state),
289
+ *_rewards_manager_findings(state),
262
290
  *_orphan_fuse_findings(state),
263
291
  *_missing_erc20_dependency_findings(state),
264
292
  *_unpriceable_token_findings(state),
@@ -300,9 +328,15 @@ def _reconcile(state: VaultState) -> Reconciliation:
300
328
  if underlying > 0 and price:
301
329
  underlying_usd = (underlying / 10**decimals) * price
302
330
 
303
- total = Amount(balance_fuses_total + underlying)
304
- total_usd = balance_fuses_usd + underlying_usd
331
+ rewards = state.rewards_manager_balance or Amount(0)
332
+ rewards_usd = _usd(rewards, decimals, price)
333
+
305
334
  on_chain = state.total_assets
335
+ gross = Amount(balance_fuses_total + underlying + rewards)
336
+ fee = _netted_management_fee(state, underlying + rewards)
337
+ fee_usd = _usd(fee, decimals, price)
338
+ total = Amount(gross - fee)
339
+ total_usd = balance_fuses_usd + underlying_usd + rewards_usd - fee_usd
306
340
  delta = Amount(total - on_chain)
307
341
  delta_usd = abs(total_usd - (on_chain / 10**decimals * price)) if price else 0
308
342
  delta_percent = abs(delta / on_chain * 100) if on_chain else 0.0
@@ -325,6 +359,10 @@ def _reconcile(state: VaultState) -> Reconciliation:
325
359
  balance_fuses_total_usd=balance_fuses_usd,
326
360
  underlying_on_vault=underlying,
327
361
  underlying_on_vault_usd=underlying_usd,
362
+ rewards_manager_balance=rewards,
363
+ rewards_manager_balance_usd=rewards_usd,
364
+ management_fee_netted=fee,
365
+ management_fee_netted_usd=fee_usd,
328
366
  erc20_direct_total=erc20.value_in_asset,
329
367
  erc20_direct_total_usd=erc20.value_usd,
330
368
  sum=total,
@@ -336,10 +374,36 @@ def _reconcile(state: VaultState) -> Reconciliation:
336
374
  delta_percent=delta_percent,
337
375
  pending_withdrawals=pending,
338
376
  pending_withdrawals_usd=pending_usd,
339
- implied_market_total=Amount(max(0, on_chain - underlying)),
377
+ implied_market_total=Amount(max(0, on_chain + fee - underlying - rewards)),
340
378
  )
341
379
 
342
380
 
381
+ def _usd(amount: int, decimals: int, price: float | None) -> float:
382
+ return (amount / 10**decimals) * price if amount and price else 0.0
383
+
384
+
385
+ def _netted_management_fee(state: VaultState, outside_markets: int) -> Amount:
386
+ """The unrealized management fee ``totalAssets`` subtracts, 0 when it
387
+ returns gross.
388
+
389
+ PlasmaVault contracts before ipor-fusion #475 (Feb 2026) return
390
+ ``max(0, gross - fee)``, later ones ``gross``, where gross is idle
391
+ underlying plus the stored global market total plus the rewards manager's
392
+ balance. The deployed version is not readable, so the formula is decided by
393
+ which one ``totalAssets`` matches, from the stored global total rather than
394
+ the per-market caches the reconciliation compares (a drift between those
395
+ must not pass for the fee). Without the stored total, the fee or a match,
396
+ nothing is netted.
397
+ """
398
+ fee, stored = state.unrealized_management_fee, state.stored_markets_total
399
+ if not fee or stored is None:
400
+ return Amount(0)
401
+ gross = outside_markets + stored
402
+ if state.total_assets == gross or state.total_assets != max(0, gross - fee):
403
+ return Amount(0)
404
+ return Amount(min(fee, gross))
405
+
406
+
343
407
  def _unimplemented_getter_findings(state: VaultState) -> list[Finding]:
344
408
  return [
345
409
  Finding(
@@ -351,6 +415,20 @@ def _unimplemented_getter_findings(state: VaultState) -> list[Finding]:
351
415
  ]
352
416
 
353
417
 
418
+ def _rewards_manager_findings(state: VaultState) -> list[Finding]:
419
+ if state.rewards_manager_balance is not None:
420
+ return []
421
+ return [
422
+ Finding(
423
+ "rewards_manager_balance",
424
+ "warning",
425
+ "WARNING — the rewards claim manager or its balanceOf() could not be "
426
+ "read: totalAssets includes that balance, so the reconciliation "
427
+ "leaves it out and its delta may be that balance",
428
+ )
429
+ ]
430
+
431
+
354
432
  def _orphan_fuse_findings(state: VaultState) -> list[Finding]:
355
433
  """Action fuses whose market has no balance fuse: their positions silently
356
434
  bypass ``totalAssets``. Flow-through markets (flash loans, swaps) are
@@ -522,8 +600,13 @@ def _reconciliation_findings(
522
600
  symbol = state.asset_symbol
523
601
  expected = reconciliation.sum
524
602
  percent = abs(expected - total_assets) / total_assets * 100
603
+ terms = "Balance fuses + underlying"
604
+ if reconciliation.rewards_manager_balance > 0:
605
+ terms += " + rewards manager"
606
+ if reconciliation.management_fee_netted > 0:
607
+ terms += " - unrealized management fee"
525
608
  line = (
526
- f"Balance fuses + underlying = {_format_amount(expected, decimals)} "
609
+ f"{terms} = {_format_amount(expected, decimals)} "
527
610
  f"{symbol} vs totalAssets {_format_amount(total_assets, decimals)} "
528
611
  f"{symbol} ({percent:.2f}%)"
529
612
  )
@@ -541,7 +624,9 @@ def _reconciliation_findings(
541
624
  )
542
625
  )
543
626
  divergence = reconciliation.market_storage_divergence
544
- if abs(divergence) > 10**decimals:
627
+ # With the rewards manager's balance unknown the divergence may be that
628
+ # balance; the rewards_manager_balance warning says so instead.
629
+ if abs(divergence) > 10**decimals and state.rewards_manager_balance is not None:
545
630
  findings.append(
546
631
  Finding(
547
632
  "market_storage_drift",
@@ -26,9 +26,10 @@ from ipor_fusion.core.access import AccessManager, RoleAccount, role_account_sor
26
26
  from ipor_fusion.core.context import Web3Context
27
27
  from ipor_fusion.core.contract import Call
28
28
  from ipor_fusion.core.erc20 import ERC20
29
- from ipor_fusion.core.multicall import Multicall3
29
+ from ipor_fusion.core.multicall import BatchValue, Multicall3
30
30
  from ipor_fusion.core.oracle import PriceOracleMiddleware, price_oracle_middleware
31
31
  from ipor_fusion.core.plasma_vault import BalanceFuse, PlasmaVault
32
+ from ipor_fusion.core.rewards_manager import RewardsManager
32
33
  from ipor_fusion.errors import (
33
34
  EmptyCallResultError,
34
35
  UnsupportedVaultVersionError,
@@ -143,6 +144,13 @@ class VaultData:
143
144
  # ERC20_VAULT_BALANCE substrate, so it is read directly in
144
145
  # fetch_vault_data rather than derived from substrates.
145
146
  underlying_balance_on_vault: Amount = Amount(0)
147
+ # RewardsClaimManager.balanceOf() (its vested underlying), which
148
+ # PlasmaVault.totalAssets adds to the vault's own balance and its markets.
149
+ # 0 without a manager; None when the vault has one but the read failed.
150
+ rewards_manager_balance: Amount | None = Amount(0)
151
+ # The global market total the vault stores (the totalAssetsInAllMarkets
152
+ # term of totalAssets); None when the storage read failed.
153
+ stored_markets_total: Amount | None = None
146
154
  vault_name: str = ""
147
155
  deployment_block: int | None = None
148
156
  deployment_timestamp: Timestamp | None = None
@@ -204,6 +212,11 @@ class VaultData:
204
212
  token_symbols: dict[HexAddress, str] = field(default_factory=dict)
205
213
  token_decimals: dict[HexAddress, Decimals | None] = field(default_factory=dict)
206
214
 
215
+ @property
216
+ def unrealized_management_fee(self) -> Amount | None:
217
+ """``getUnrealizedManagementFee()`` from the fee snapshot."""
218
+ return self.fee_data.unrealized_management_fee if self.fee_data else None
219
+
207
220
 
208
221
  _MARKET_ID_SELECTOR = function_signature_to_4byte_selector("MARKET_ID()")
209
222
 
@@ -342,6 +355,7 @@ class _VaultReads:
342
355
  asset_price_usd: float | None
343
356
  fuse_markets: dict[FuseAddress, MarketId] | None
344
357
  unimplemented_getters: list[str]
358
+ rewards_manager_balance: Amount | None
345
359
 
346
360
 
347
361
  def _fetch_vault_reads(
@@ -351,8 +365,9 @@ def _fetch_vault_reads(
351
365
  oracle wrapper resolved for them (reused, so its ABI is probed once).
352
366
 
353
367
  Two Multicall3 round trips: vault getters, then everything that needs the
354
- asset, the oracle or the fuse list (asset metadata, idle balance, price,
355
- each fuse's MARKET_ID()).
368
+ asset, the oracle, the fuse list or the rewards claim manager (asset
369
+ metadata, idle balance, price, each fuse's MARKET_ID(), the manager's
370
+ vested balance).
356
371
  """
357
372
  pv = plasma_vault
358
373
  batch = Multicall3(ctx).batch()
@@ -365,6 +380,7 @@ def _fetch_vault_reads(
365
380
  instant_fuses = batch.add(pv.get_instant_withdrawal_fuses())
366
381
  vault_name = batch.try_add(pv.name())
367
382
  rewards_manager = batch.try_add(pv.get_rewards_claim_manager_address())
383
+ legacy_rewards_manager = batch.try_add(pv.get_rewards_manager_address())
368
384
  supply_cap_read = batch.try_add(pv.get_total_supply_cap())
369
385
  middleware_oracle = batch.try_add(pv.get_price_oracle_middleware_address())
370
386
  legacy_oracle = batch.try_add(pv.get_price_oracle_address())
@@ -398,6 +414,18 @@ def _fetch_vault_reads(
398
414
  fuse_market_ids = [
399
415
  (fuse, batch.try_add(_fuse_market_id_call(fuse))) for fuse in fuses
400
416
  ]
417
+ # Vaults before the RewardsClaimManager rename answer the legacy getter;
418
+ # None from both leaves the manager, and so its balance, unknown.
419
+ manager = (
420
+ rewards_manager.value
421
+ if rewards_manager.value is not None
422
+ else legacy_rewards_manager.value
423
+ )
424
+ manager_balance = (
425
+ batch.try_add(RewardsManager(manager).balance_of())
426
+ if manager is not None and int(manager, 16) != 0
427
+ else None
428
+ )
401
429
  batch.run()
402
430
  # Fuses without a readable MARKET_ID() are left out; the rest detect orphan
403
431
  # markets (action fuse registered, no balance fuse for its market).
@@ -416,7 +444,8 @@ def _fetch_vault_reads(
416
444
  fuses=fuses,
417
445
  instant_fuses=instant_fuses.value,
418
446
  vault_name=vault_name.value or "",
419
- rewards_manager=rewards_manager.value,
447
+ rewards_manager=manager,
448
+ rewards_manager_balance=_rewards_manager_balance(manager, manager_balance),
420
449
  asset_decimals=asset_decimals.value,
421
450
  underlying_balance_on_vault=underlying_balance.value,
422
451
  asset_symbol=symbol.value or "?",
@@ -426,6 +455,16 @@ def _fetch_vault_reads(
426
455
  ), oracle
427
456
 
428
457
 
458
+ def _rewards_manager_balance(
459
+ manager: ChecksumAddress | None, read: BatchValue[Amount | None] | None
460
+ ) -> Amount | None:
461
+ """The manager's balance; 0 for the zero address (no manager), None when
462
+ the manager or its balance could not be read."""
463
+ if read is not None:
464
+ return read.value
465
+ return None if manager is None else Amount(0)
466
+
467
+
429
468
  def _fetch_role_accounts(
430
469
  ctx: Web3Context, plasma_vault: PlasmaVault
431
470
  ) -> list[RoleAccount] | None:
@@ -587,6 +626,9 @@ def fetch_vault_data(
587
626
  f_fees = pool.submit(_fetch_fee_data, ctx, plasma_vault)
588
627
  f_withdraw = pool.submit(_fetch_withdraw_manager, ctx, plasma_vault)
589
628
  f_roles = pool.submit(_fetch_role_accounts, ctx, plasma_vault)
629
+ f_stored_total = pool.submit(
630
+ _safe_call, partial(plasma_vault.read_stored_markets_total, ctx)
631
+ )
590
632
  f_markets = pool.submit(
591
633
  _fetch_market_reads, ctx, plasma_vault, with_aave_pools=lending
592
634
  )
@@ -622,6 +664,7 @@ def fetch_vault_data(
622
664
  fee_data=f_fees.result(),
623
665
  role_accounts=f_roles.result(),
624
666
  hypercore=f_hypercore.result(),
667
+ stored_markets_total=f_stored_total.result(),
625
668
  )
626
669
 
627
670
 
@@ -645,6 +688,7 @@ def _snapshot(
645
688
  fee_data: FeeData | None,
646
689
  role_accounts: list[RoleAccount] | None,
647
690
  hypercore: HyperCoreVaultState | None,
691
+ stored_markets_total: Amount | None,
648
692
  ) -> VaultData:
649
693
  """`VaultData` from the pipelines' results."""
650
694
  return VaultData(
@@ -664,6 +708,8 @@ def _snapshot(
664
708
  instant_fuses=reads.instant_fuses,
665
709
  vault_name=reads.vault_name,
666
710
  rewards_manager=reads.rewards_manager,
711
+ rewards_manager_balance=reads.rewards_manager_balance,
712
+ stored_markets_total=stored_markets_total,
667
713
  asset_decimals=reads.asset_decimals,
668
714
  underlying_balance_on_vault=reads.underlying_balance_on_vault,
669
715
  asset_symbol=reads.asset_symbol,
@@ -305,6 +305,8 @@ class Erc20BalanceJson(TypedDict):
305
305
  class ReconciliationJson(TypedDict):
306
306
  balance_fuses_total: UsdAmountJson
307
307
  underlying_on_vault: UsdAmountJson
308
+ rewards_manager_balance: UsdAmountJson
309
+ management_fee_netted: UsdAmountJson
308
310
  erc20_direct_total: UsdAmountJson
309
311
  sum: UsdAmountJson
310
312
  on_chain_total_assets: UsdAmountJson
@@ -810,6 +812,14 @@ def _reconciliation_json(recon: Reconciliation, decimals: int) -> Reconciliation
810
812
  "underlying_on_vault": _amount_json(
811
813
  recon.underlying_on_vault, decimals, recon.underlying_on_vault_usd
812
814
  ),
815
+ "rewards_manager_balance": _amount_json(
816
+ recon.rewards_manager_balance,
817
+ decimals,
818
+ recon.rewards_manager_balance_usd,
819
+ ),
820
+ "management_fee_netted": _amount_json(
821
+ recon.management_fee_netted, decimals, recon.management_fee_netted_usd
822
+ ),
813
823
  "erc20_direct_total": _amount_json(
814
824
  recon.erc20_direct_total, decimals, recon.erc20_direct_total_usd
815
825
  ),