ipor-fusion 4.0.0.dev108__tar.gz → 4.0.0.dev110__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 (297) hide show
  1. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/AGENTS.md +1 -1
  2. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/MIGRATION_4.md +8 -3
  3. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/PKG-INFO +1 -1
  4. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/pyproject.toml +1 -1
  5. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/cli/vault_info_view.py +5 -7
  6. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/cli/vault_markets_view.py +19 -26
  7. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/vault_data.py +16 -9
  8. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/vault_info.py +8 -6
  9. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/test_cache.py +46 -0
  10. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/test_cli_commands.py +6 -18
  11. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/test_cli_helpers.py +28 -52
  12. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/test_cli_vault_info_golden.py +58 -17
  13. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/test_cli_vault_predates_getters.py +3 -1
  14. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/.claude-plugin/marketplace.json +0 -0
  15. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/.claude-plugin/plugin.json +0 -0
  16. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/.env.example +0 -0
  17. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/.gemini/settings.json +0 -0
  18. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/.git-blame-ignore-revs +0 -0
  19. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/.github/workflows/cd.yml +0 -0
  20. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/.github/workflows/ci.yml +0 -0
  21. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/.github/workflows/commit-lint.yml +0 -0
  22. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/.github/workflows/notify-slack.yml +0 -0
  23. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/.github/workflows/python-build.yml +0 -0
  24. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/.github/workflows/python-publish.yml +0 -0
  25. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/.github/workflows/release.yml +0 -0
  26. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/.github/workflows/report-slack.yml +0 -0
  27. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/.gitignore +0 -0
  28. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/.mcp.json +0 -0
  29. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/.pre-commit-config.yaml +0 -0
  30. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/.python-version +0 -0
  31. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/CHANGELOG.md +0 -0
  32. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/CLAUDE.md +0 -0
  33. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/CODEOWNERS +0 -0
  34. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/LICENSE +0 -0
  35. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/README.md +0 -0
  36. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/build.sh +0 -0
  37. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/examples/CONTRIBUTING.md +0 -0
  38. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/examples/README.md +0 -0
  39. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/examples/advanced_euler_v2_credit_market_base.py +0 -0
  40. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/examples/advanced_euler_v2_looping_arbitrum.py +0 -0
  41. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/examples/composed_hypercore_flow_arbitrum_hyperevm.py +0 -0
  42. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/examples/crosschain_ccip_usdc_arbitrum_hyperevm.py +0 -0
  43. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/examples/external_state_margin_leg_base.py +0 -0
  44. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/examples/simple_aave_v3_supply_base.py +0 -0
  45. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/pytest.ini +0 -0
  46. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/scripts/commit-check.sh +0 -0
  47. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/scripts/crosschain_readiness.py +0 -0
  48. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/skills/ipor-deploy-vault/SKILL.md +0 -0
  49. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/__init__.py +0 -0
  50. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/about.py +0 -0
  51. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/cache.py +0 -0
  52. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/chains.py +0 -0
  53. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/cli/__init__.py +0 -0
  54. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/cli/changelog_cmd.py +0 -0
  55. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/cli/config_cmd.py +0 -0
  56. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/cli/config_store.py +0 -0
  57. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/cli/main.py +0 -0
  58. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/cli/market_cmd.py +0 -0
  59. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/cli/options.py +0 -0
  60. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/cli/oracle_mapping_view.py +0 -0
  61. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/cli/vault_cmd.py +0 -0
  62. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/cli/vault_health.py +0 -0
  63. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/cli/vault_rendering.py +0 -0
  64. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/config/__init__.py +0 -0
  65. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/config/roles.py +0 -0
  66. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/core/__init__.py +0 -0
  67. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/core/access.py +0 -0
  68. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/core/address.py +0 -0
  69. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/core/context.py +0 -0
  70. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/core/contract.py +0 -0
  71. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/core/erc20.py +0 -0
  72. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/core/external_state_executor.py +0 -0
  73. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/core/fee_manager.py +0 -0
  74. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/core/fuse.py +0 -0
  75. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/core/fuse_action.py +0 -0
  76. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/core/fusion_factory.py +0 -0
  77. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/core/logs.py +0 -0
  78. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/core/multicall.py +0 -0
  79. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/core/oracle.py +0 -0
  80. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/core/plasma_vault.py +0 -0
  81. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/core/rewards_manager.py +0 -0
  82. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/core/simulation.py +0 -0
  83. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/core/withdraw_manager.py +0 -0
  84. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/crosschain/__init__.py +0 -0
  85. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/crosschain/ccip/__init__.py +0 -0
  86. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/crosschain/ccip/chainlink.py +0 -0
  87. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/crosschain/ccip/codec.py +0 -0
  88. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/crosschain/ccip/contracts.py +0 -0
  89. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/crosschain/ccip/events.py +0 -0
  90. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/crosschain/ccip/indexer.py +0 -0
  91. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/crosschain/ccip/lane.py +0 -0
  92. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/crosschain/ccip/quote.py +0 -0
  93. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/crosschain/ccip/transport.py +0 -0
  94. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/crosschain/contracts.py +0 -0
  95. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/crosschain/discovery.py +0 -0
  96. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/crosschain/errors.py +0 -0
  97. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/crosschain/fuses/__init__.py +0 -0
  98. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/crosschain/fuses/base.py +0 -0
  99. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/crosschain/fuses/ccip.py +0 -0
  100. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/crosschain/fuses/stargate.py +0 -0
  101. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/crosschain/lane.py +0 -0
  102. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/crosschain/logs.py +0 -0
  103. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/crosschain/messages.py +0 -0
  104. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/crosschain/simulation.py +0 -0
  105. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/crosschain/stargate/__init__.py +0 -0
  106. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/crosschain/stargate/contracts.py +0 -0
  107. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/crosschain/stargate/lane.py +0 -0
  108. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/crosschain/stargate/layerzero.py +0 -0
  109. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/crosschain/stargate/transport.py +0 -0
  110. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/crosschain/transport.py +0 -0
  111. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/dependency_graph.py +0 -0
  112. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/entry_cli.py +0 -0
  113. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/entry_mcp.py +0 -0
  114. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/errors.py +0 -0
  115. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/explorer.py +0 -0
  116. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/field_docs.py +0 -0
  117. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/fuses/__init__.py +0 -0
  118. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/fuses/aave_v3.py +0 -0
  119. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/fuses/async_action.py +0 -0
  120. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/fuses/base.py +0 -0
  121. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/fuses/compound_v3.py +0 -0
  122. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/fuses/erc4626.py +0 -0
  123. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/fuses/euler_v2.py +0 -0
  124. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/fuses/events.py +0 -0
  125. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/fuses/external_state.py +0 -0
  126. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/fuses/external_state_events.py +0 -0
  127. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/fuses/fluid_instadapp.py +0 -0
  128. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/fuses/gearbox_v3.py +0 -0
  129. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/fuses/hypercore.py +0 -0
  130. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/fuses/hypercore_events.py +0 -0
  131. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/fuses/merkl.py +0 -0
  132. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/fuses/morpho.py +0 -0
  133. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/fuses/ramses_v2.py +0 -0
  134. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/fuses/uniswap_v3.py +0 -0
  135. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/fuses/universal.py +0 -0
  136. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/guide/__init__.py +0 -0
  137. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/guide/architecture.md +0 -0
  138. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/guide/glossary.md +0 -0
  139. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/guide/invariants.md +0 -0
  140. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/guide/quickstart.md +0 -0
  141. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/market_ids.py +0 -0
  142. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/market_info.py +0 -0
  143. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/mcp/__init__.py +0 -0
  144. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/mcp/guide.py +0 -0
  145. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/mcp/models.py +0 -0
  146. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/mcp/server.py +0 -0
  147. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/morpho_api.py +0 -0
  148. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/py.typed +0 -0
  149. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/readers/__init__.py +0 -0
  150. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/readers/aave_v3.py +0 -0
  151. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/readers/aave_v4.py +0 -0
  152. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/readers/compound_v3.py +0 -0
  153. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/readers/hypercore.py +0 -0
  154. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/readers/hypercore_outcome.py +0 -0
  155. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/readers/lending_health.py +0 -0
  156. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/readers/morpho.py +0 -0
  157. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/readers/oracle_mapping.py +0 -0
  158. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/readers/oracle_mapping_nodes.py +0 -0
  159. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/readers/oracle_mapping_reader.py +0 -0
  160. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/readers/position_manager.py +0 -0
  161. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/readers/ramses_v2.py +0 -0
  162. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/readers/uniswap_v3.py +0 -0
  163. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/readers/unpriceable.py +0 -0
  164. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/substrates.py +0 -0
  165. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/types.py +0 -0
  166. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/validation.py +0 -0
  167. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/vault_erc20.py +0 -0
  168. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/vault_managers.py +0 -0
  169. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/vault_positions.py +0 -0
  170. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/src/ipor_fusion/vault_reads.py +0 -0
  171. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/UniswapV3UniversalRouter.py +0 -0
  172. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/_crosschain.py +0 -0
  173. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/_crosschain_lifecycle.py +0 -0
  174. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/_crosschain_recovery.py +0 -0
  175. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/_euler_v2.py +0 -0
  176. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/_foundry.py +0 -0
  177. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/_guide.py +0 -0
  178. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/_hypercore_model.py +0 -0
  179. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/_hypercore_shadow.py +0 -0
  180. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/_multicall.py +0 -0
  181. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/_simulate.py +0 -0
  182. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/_validation.py +0 -0
  183. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/addresses.py +0 -0
  184. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/conftest.py +0 -0
  185. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/constants.py +0 -0
  186. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/fixtures/hypercore_hip3_flow.json +0 -0
  187. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/fixtures/hypercore_market55.json +0 -0
  188. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/fixtures/vault_info_golden/clean.json +0 -0
  189. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/fixtures/vault_info_golden/clean.txt +0 -0
  190. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/fixtures/vault_info_golden/rich.json +0 -0
  191. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/fixtures/vault_info_golden/rich.txt +0 -0
  192. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/test_about.py +0 -0
  193. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/test_access_manager.py +0 -0
  194. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/test_chains.py +0 -0
  195. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/test_cli_changelog.py +0 -0
  196. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/test_cli_config_store.py +0 -0
  197. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/test_cli_hypercore.py +0 -0
  198. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/test_cli_lending_health.py +0 -0
  199. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/test_cli_market_morpho_blue.py +0 -0
  200. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/test_cli_oracle_mapping.py +0 -0
  201. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/test_cli_pending_requests_scan.py +0 -0
  202. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/test_cli_role_accounts.py +0 -0
  203. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/test_cli_vault_validate.py +0 -0
  204. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/test_contract_call.py +0 -0
  205. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/test_contract_encoder.py +0 -0
  206. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/test_crosschain_ccip_events.py +0 -0
  207. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/test_crosschain_ccip_indexer.py +0 -0
  208. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/test_crosschain_ccip_quote.py +0 -0
  209. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/test_crosschain_chainlink.py +0 -0
  210. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/test_crosschain_contracts.py +0 -0
  211. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/test_crosschain_encoding.py +0 -0
  212. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/test_crosschain_errors.py +0 -0
  213. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/test_crosschain_lane.py +0 -0
  214. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/test_crosschain_layering.py +0 -0
  215. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/test_crosschain_readiness.py +0 -0
  216. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/test_crosschain_readiness_report.py +0 -0
  217. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/test_crosschain_transport.py +0 -0
  218. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/test_erc20.py +0 -0
  219. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/test_error_hierarchy.py +0 -0
  220. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/test_examples_composed_hypercore.py +0 -0
  221. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/test_examples_crosschain.py +0 -0
  222. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/test_examples_euler_looping_arbitrum.py +0 -0
  223. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/test_examples_vaults.py +0 -0
  224. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/test_explorer.py +0 -0
  225. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/test_external_state_events.py +0 -0
  226. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/test_external_state_executor.py +0 -0
  227. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/test_fee_manager.py +0 -0
  228. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/test_fuse_encoding.py +0 -0
  229. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/test_fusion_instance.py +0 -0
  230. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/test_guide.py +0 -0
  231. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/test_hypercore_deployment.py +0 -0
  232. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/test_hypercore_events.py +0 -0
  233. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/test_hypercore_flow.py +0 -0
  234. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/test_hypercore_live.py +0 -0
  235. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/test_hypercore_outcome.py +0 -0
  236. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/test_hypercore_readers.py +0 -0
  237. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/test_hypercore_simulation.py +0 -0
  238. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/test_logs.py +0 -0
  239. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/test_mcp_hypercore.py +0 -0
  240. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/test_mcp_models.py +0 -0
  241. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/test_mcp_server.py +0 -0
  242. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/test_mcp_vault_validate.py +0 -0
  243. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/test_multicall.py +0 -0
  244. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/test_oracle.py +0 -0
  245. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/test_oracle_mapping.py +0 -0
  246. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/test_plasma_vault.py +0 -0
  247. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/test_public_api.py +0 -0
  248. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/test_reader_encoding.py +0 -0
  249. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/test_readiness_selection.py +0 -0
  250. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/test_revert_decoding.py +0 -0
  251. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/test_rewards_manager.py +0 -0
  252. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/test_roles.py +0 -0
  253. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/test_sdk_layering.py +0 -0
  254. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/test_simulate_borrow_aave_v3_mainnet.py +0 -0
  255. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/test_simulate_claim_merkl_wrapper_base.py +0 -0
  256. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/test_simulate_claim_morpho_rewards.py +0 -0
  257. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/test_simulate_crosschain_canary.py +0 -0
  258. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/test_simulate_crosschain_hypercore.py +0 -0
  259. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/test_simulate_crosschain_hyperevm.py +0 -0
  260. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/test_simulate_crosschain_lifecycle.py +0 -0
  261. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/test_simulate_euler_v2_batch_base.py +0 -0
  262. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/test_simulate_euler_v2_credit_market_base.py +0 -0
  263. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/test_simulate_euler_v2_swap_deploy_base.py +0 -0
  264. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/test_simulate_euler_v2_swap_reconfigure_base.py +0 -0
  265. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/test_simulate_euler_v2_swap_registry_base.py +0 -0
  266. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/test_simulate_examples_arbitrum.py +0 -0
  267. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/test_simulate_examples_base.py +0 -0
  268. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/test_simulate_examples_crosschain.py +0 -0
  269. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/test_simulate_external_state_executor_base.py +0 -0
  270. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/test_simulate_fusion_factory_clone_base.py +0 -0
  271. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/test_simulate_looping_aave_v3_base.py +0 -0
  272. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/test_simulate_looping_morpho_blue_base.py +0 -0
  273. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/test_simulate_morpho_blue.py +0 -0
  274. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/test_simulate_pilot_v3.py +0 -0
  275. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/test_simulate_pilot_v4_uniswap.py +0 -0
  276. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/test_simulate_pilot_v5_ramses.py +0 -0
  277. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/test_simulate_pilot_v5_scheduled.py +0 -0
  278. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/test_simulate_plasma_vault_setters_base.py +0 -0
  279. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/test_simulate_readers_integration.py +0 -0
  280. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/test_simulate_release_funds.py +0 -0
  281. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/test_simulate_swap_on_base.py +0 -0
  282. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/test_simulate_vault_from_scratch_base.py +0 -0
  283. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/test_solidity_compiler.py +0 -0
  284. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/test_solidity_mirrors.py +0 -0
  285. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/test_substrates.py +0 -0
  286. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/test_types_and_events.py +0 -0
  287. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/test_unpriceable_tokens.py +0 -0
  288. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/test_validation.py +0 -0
  289. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/test_vault_dep_graph.py +0 -0
  290. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/test_vault_info_onchain_base.py +0 -0
  291. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/test_vault_info_onchain_base_no_withdraw_manager.py +0 -0
  292. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/test_vault_simulator_blocks.py +0 -0
  293. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/test_web3_context.py +0 -0
  294. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/test_withdraw_manager.py +0 -0
  295. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/typed_client/consumer.py +0 -0
  296. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/tests/typed_client/rejected.py +0 -0
  297. {ipor_fusion-4.0.0.dev108 → ipor_fusion-4.0.0.dev110}/uv.lock +0 -0
@@ -158,7 +158,7 @@ the lifecycle matrix follows that.
158
158
  subpackage (`core`, `config`, `fuses`, `readers`, `crosschain`,
159
159
  `crosschain.fuses`, `crosschain.ccip`, `crosschain.stargate`) also goes in that
160
160
  subpackage's `__all__`. `ipor_fusion.guide` keeps its own namespace. The
161
- public modules (`about`, `chains`, `dependency_graph`, `errors`, `explorer`,
161
+ public modules (`about`, `cache`, `chains`, `dependency_graph`, `errors`, `explorer`,
162
162
  `field_docs`, `market_ids`, `market_info`, `morpho_api`, `substrates`, `types`,
163
163
  `validation`, `vault_data`, `vault_info`) stay importable for the names they
164
164
  define; `cli`, `mcp` and every other module are internal. Examples, the guide
@@ -351,8 +351,12 @@ Contract names, token symbols and decimals and deployment blocks are cached in a
351
351
  (`fetch_vault_data(metadata=True)`) and `fetch_deployment_info` use `ctx.cache`.
352
352
  `get_contract_name(chain_id, address, api_key=None, *, cache=None)` takes the cache
353
353
  explicitly (default: the file cache); pass `cache=ctx.cache` to share the
354
- context's policy. `build_vault_info_json` without `resolve_name` still names
355
- contracts from the default file cache; pass `resolve_name` to avoid it.
354
+ context's policy. The CLI's text and JSON output name contracts through the
355
+ context's cache.
356
+ - `build_vault_info_json(data)` without `resolve_name` names no contract (every
357
+ `contract` is `"?"`) instead of reading names from the on-disk cache; pass
358
+ `resolve_name=partial(get_contract_name, chain_id, api_key=..., cache=ctx.cache)`
359
+ for the CLI's document.
356
360
  - `ipor_fusion.cache` is a public module now and exports `ContractCache`,
357
361
  `FileContractCache`, `NullCache`, `CacheKey`, `CacheField`, `cache_key` and
358
362
  `default_contract_cache` (also from `ipor_fusion`). Its 3.x functions and constants
@@ -384,7 +388,8 @@ Etherscan key) and the old file can be deleted.
384
388
  - A cache never fails a read: a corrupt or unreadable file reads as empty (logged
385
389
  warning from `ipor_fusion.cache`) and is rewritten by the next write; an unwritable
386
390
  directory (read-only file system) keeps the entries in memory for the process; a
387
- custom cache that raises is treated as a miss.
391
+ custom cache that raises is treated as a miss, and so is a stored number that does
392
+ not parse.
388
393
  - `fetch_deployment_info` returns `rpc-fetch-failed` only when the creation
389
394
  transaction or its block cannot be read; a cache failure is no longer reported as
390
395
  one (the deployment block is returned).
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: ipor-fusion
3
- Version: 4.0.0.dev108
3
+ Version: 4.0.0.dev110
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.dev108"
3
+ version = "4.0.0.dev110"
4
4
  description = "The IPOR Fusion SDK for Python"
5
5
  authors = [
6
6
  { name = "Adam Mydlarz", email = "amydlarz@gmail.com" },
@@ -85,13 +85,11 @@ def _print_vault_info(
85
85
  data.deployment_error,
86
86
  ) = fetch_deployment_info(ctx, data.vault, api_key=api_key)
87
87
 
88
+ resolve_name = partial(
89
+ get_contract_name, chain_id, api_key=api_key, cache=ctx.cache
90
+ )
88
91
  if json_output:
89
- result = build_vault_info_json(
90
- data,
91
- resolve_name=partial(
92
- get_contract_name, chain_id, api_key=api_key, cache=ctx.cache
93
- ),
94
- )
92
+ result = build_vault_info_json(data, resolve_name=resolve_name)
95
93
  click.echo(json.dumps(result, indent=2))
96
94
  return
97
95
 
@@ -105,7 +103,7 @@ def _print_vault_info(
105
103
 
106
104
  _print_role_accounts(_role_accounts_json(data))
107
105
 
108
- _print_markets(data, api_key)
106
+ _print_markets(data, resolve_name)
109
107
 
110
108
  validation = validate_vault_state(data)
111
109
  click.echo("ERC20 Balances (vault holdings):")
@@ -1,6 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
- from collections.abc import Sequence
3
+ from collections.abc import Callable, Sequence
4
4
  from concurrent.futures import Future, ThreadPoolExecutor
5
5
  from typing import Any
6
6
 
@@ -13,12 +13,11 @@ from ipor_fusion.cli.vault_rendering import (
13
13
  _print_table,
14
14
  _substrate_details,
15
15
  )
16
- from ipor_fusion.explorer import get_contract_name
17
16
  from ipor_fusion.substrates import (
18
17
  decode_substrate,
19
18
  format_market_label,
20
19
  )
21
- from ipor_fusion.types import ChainId, FuseAddress, MarketId, parse_hex_address
20
+ from ipor_fusion.types import FuseAddress, MarketId, parse_hex_address
22
21
  from ipor_fusion.vault_data import (
23
22
  GET_MARKET_SUBSTRATES,
24
23
  VaultData,
@@ -27,6 +26,10 @@ from ipor_fusion.vault_info import (
27
26
  _CAPABILITY_BALANCE_FUSES,
28
27
  )
29
28
 
29
+ # Address to contract name, "" when unknown (`get_contract_name` bound to the
30
+ # chain, API key and the context's cache).
31
+ NameResolver = Callable[[HexAddress], str]
32
+
30
33
 
31
34
  def _print_lending_health(data: VaultData) -> None: # noqa: C901
32
35
  lh = data.lending_health
@@ -154,16 +157,14 @@ def _print_health_lines(market: Any, indent: str) -> None:
154
157
  click.secho(f"{indent}Status: {status}", fg=color, bold=bold)
155
158
 
156
159
 
157
- def _print_markets(data: VaultData, api_key: str | None) -> None:
160
+ def _print_markets(data: VaultData, resolve_name: NameResolver) -> None:
158
161
  """Fuses, balance fuses, dependency graph and substrates."""
159
- chain_id = data.chain_id
160
162
  _print_fuse_section(
161
163
  "Fuses",
162
164
  data.fuses,
163
165
  data.fuse_markets,
164
166
  data.market_substrates,
165
- chain_id,
166
- api_key,
167
+ resolve_name,
167
168
  )
168
169
  click.echo()
169
170
 
@@ -172,18 +173,17 @@ def _print_markets(data: VaultData, api_key: str | None) -> None:
172
173
  data.instant_fuses,
173
174
  data.fuse_markets,
174
175
  data.market_substrates,
175
- chain_id,
176
- api_key,
176
+ resolve_name,
177
177
  )
178
178
  click.echo()
179
179
 
180
- _print_balance_fuses_table(data, api_key)
180
+ _print_balance_fuses_table(data, resolve_name)
181
181
  click.echo()
182
182
 
183
183
  _print_dependency_graph(data)
184
184
 
185
185
  click.echo("Substrates per Market:")
186
- _print_market_substrates(data, api_key)
186
+ _print_market_substrates(data, resolve_name)
187
187
  click.echo()
188
188
 
189
189
 
@@ -235,8 +235,7 @@ def _print_fuse_section(
235
235
  fuses: Sequence[FuseAddress],
236
236
  fuse_markets: dict[FuseAddress, MarketId] | None,
237
237
  market_substrates: dict[MarketId, list[bytes]] | None,
238
- chain_id: ChainId,
239
- api_key: str | None,
238
+ resolve_name: NameResolver,
240
239
  ) -> None:
241
240
  """Render a fuse section (regular or instant-withdrawal) with a unified
242
241
  layout.
@@ -262,10 +261,7 @@ def _print_fuse_section(
262
261
  return
263
262
 
264
263
  with ThreadPoolExecutor() as pool:
265
- name_futs = {
266
- addr: pool.submit(get_contract_name, chain_id, addr, api_key)
267
- for addr in counts
268
- }
264
+ name_futs = {addr: pool.submit(resolve_name, addr) for addr in counts}
269
265
  rows: list[tuple[str, ...]] = []
270
266
  for idx, addr in enumerate(counts, 1):
271
267
  market_label = "?"
@@ -285,7 +281,7 @@ def _print_fuse_section(
285
281
  _print_table(("#", "Address", "Contract", "Market", "Substrates"), rows)
286
282
 
287
283
 
288
- def _print_balance_fuses_table(data: VaultData, api_key: str | None) -> None:
284
+ def _print_balance_fuses_table(data: VaultData, resolve_name: NameResolver) -> None:
289
285
  balance_fuses = data.balance_fuses
290
286
  decimals = data.asset_decimals
291
287
  asset_symbol = data.asset_symbol
@@ -294,9 +290,7 @@ def _print_balance_fuses_table(data: VaultData, api_key: str | None) -> None:
294
290
  futures: list[tuple[int, MarketId, str, Future]] = []
295
291
  for idx, balance_fuse in enumerate(balance_fuses, 1):
296
292
  market_id_str = format_market_label(balance_fuse.market_id)
297
- f_contract = pool.submit(
298
- get_contract_name, data.chain_id, balance_fuse.fuse, api_key
299
- )
293
+ f_contract = pool.submit(resolve_name, balance_fuse.fuse)
300
294
  futures.append((idx, balance_fuse.market_id, market_id_str, f_contract))
301
295
 
302
296
  venue_rows: list[tuple[str, ...]] = []
@@ -328,18 +322,18 @@ def _print_balance_fuses_table(data: VaultData, api_key: str | None) -> None:
328
322
  _print_table(header, [(str(i), *row) for i, row in enumerate(zero_rows, 1)])
329
323
 
330
324
 
331
- def _print_market_substrates(data: VaultData, api_key: str | None) -> set[str]:
325
+ def _print_market_substrates(data: VaultData, resolve_name: NameResolver) -> set[str]:
332
326
  if GET_MARKET_SUBSTRATES in data.unimplemented_getters:
333
327
  click.secho(
334
328
  f" (unavailable: the vault predates {GET_MARKET_SUBSTRATES})",
335
329
  fg="yellow",
336
330
  )
337
331
  return set()
338
- return _print_substrates(data, api_key)
332
+ return _print_substrates(data, resolve_name)
339
333
 
340
334
 
341
335
  def _print_substrates( # noqa: C901
342
- data: VaultData, api_key: str | None
336
+ data: VaultData, resolve_name: NameResolver
343
337
  ) -> set[str]:
344
338
  substrates_by_market = data.market_substrates or {}
345
339
  balance_fuses = data.balance_fuses
@@ -364,8 +358,7 @@ def _print_substrates( # noqa: C901
364
358
  resolved_symbols = data.token_symbols
365
359
  with ThreadPoolExecutor() as pool:
366
360
  contract_futs = {
367
- addr: pool.submit(get_contract_name, data.chain_id, addr, api_key)
368
- for addr in all_addresses
361
+ addr: pool.submit(resolve_name, addr) for addr in all_addresses
369
362
  }
370
363
  resolved_contracts = {addr: fut.result() for addr, fut in contract_futs.items()}
371
364
 
@@ -252,9 +252,16 @@ def resolve_token_symbol(ctx: Web3Context, address: HexAddress) -> str:
252
252
  return symbol
253
253
 
254
254
 
255
- def _cached_decimals(cached: dict[CacheKey, str], key: CacheKey) -> int | None:
256
- value = cached.get(key)
257
- return int(value) if value is not None and value.isdigit() else None
255
+ def _cached_int(cached: dict[CacheKey, str], key: CacheKey) -> int | None:
256
+ """A cached non-negative integer; anything else (a corrupt or foreign
257
+ entry) is a miss."""
258
+ value = cached.get(key, "")
259
+ if not (value.isascii() and value.isdigit()):
260
+ return None
261
+ try:
262
+ return int(value)
263
+ except ValueError: # beyond the int-string conversion limit
264
+ return None
258
265
 
259
266
 
260
267
  def resolve_token_decimals(ctx: Web3Context, address: HexAddress) -> int | None:
@@ -262,7 +269,7 @@ def resolve_token_decimals(ctx: Web3Context, address: HexAddress) -> int | None:
262
269
  (not cached) when ``address`` has no code at the context's block or does
263
270
  not answer."""
264
271
  key = cache_key(ctx.chain_id, address, "decimals")
265
- if (cached := _cached_decimals(cache_get(ctx.cache, [key]), key)) is not None:
272
+ if (cached := _cached_int(cache_get(ctx.cache, [key]), key)) is not None:
266
273
  return cached
267
274
 
268
275
  checksum = Web3.to_checksum_address(address)
@@ -696,7 +703,7 @@ def _token_metadata(
696
703
  decimals: dict[HexAddress, int | None] = {
697
704
  a: value
698
705
  for a, k in decimals_keys.items()
699
- if (value := _cached_decimals(cached, k)) is not None
706
+ if (value := _cached_int(cached, k)) is not None
700
707
  }
701
708
  missing_symbols = sorted((breakdown | substrate_addresses) - symbols.keys())
702
709
  missing_decimals = sorted(breakdown - decimals.keys())
@@ -771,10 +778,10 @@ def fetch_deployment_info(
771
778
  block_key = cache_key(chain_id, vault_address, "deployment_block")
772
779
  time_key = cache_key(chain_id, vault_address, "deployment_timestamp")
773
780
  cached = cache_get(ctx.cache, [block_key, time_key])
774
- if (block := cached.get(block_key, "")).isdigit() and (
775
- stamp := cached.get(time_key, "")
776
- ).isdigit():
777
- return int(block), int(stamp), None
781
+ block = _cached_int(cached, block_key)
782
+ stamp = _cached_int(cached, time_key)
783
+ if block is not None and stamp is not None:
784
+ return block, stamp, None
778
785
 
779
786
  tx_hash, error = get_deployment_tx(chain_id, vault_address, api_key)
780
787
  if not tx_hash:
@@ -6,7 +6,6 @@ from __future__ import annotations
6
6
  from collections.abc import Callable
7
7
  from concurrent.futures import Future, ThreadPoolExecutor
8
8
  from datetime import UTC, datetime
9
- from functools import partial
10
9
  from typing import Any
11
10
 
12
11
  from eth_typing import HexAddress
@@ -14,7 +13,6 @@ from eth_typing import HexAddress
14
13
  from ipor_fusion.chains import CHAIN_NAMES
15
14
  from ipor_fusion.core.fee_manager import HighWaterMarkPerformanceFee, RecipientFee
16
15
  from ipor_fusion.core.plasma_vault import BalanceFuse
17
- from ipor_fusion.explorer import get_contract_name
18
16
  from ipor_fusion.field_docs import DOCS
19
17
  from ipor_fusion.readers.aave_v3 import AaveV3PositionBreakdown
20
18
  from ipor_fusion.readers.hypercore import HyperCorePendingState, HyperCorePerpLeg
@@ -705,6 +703,10 @@ def _markets_json(
705
703
  return markets
706
704
 
707
705
 
706
+ def _no_name(_address: HexAddress) -> str:
707
+ return ""
708
+
709
+
708
710
  def build_vault_info_json(
709
711
  data: VaultData,
710
712
  *,
@@ -716,14 +718,14 @@ def build_vault_info_json(
716
718
  ``data`` comes from `fetch_vault_data` (with ``metadata=True`` for token
717
719
  symbols); set its ``deployment_*`` fields first (`fetch_deployment_info`)
718
720
  for the ``deployment`` block. ``resolve_name`` names the fuses and
719
- substrate contracts (address to contract name, ``""`` when unknown); the
720
- default reads `ipor_fusion.explorer.get_contract_name` without an API key,
721
- so only names already in the on-disk cache.
721
+ substrate contracts (address to contract name, ``""`` when unknown), for
722
+ example `ipor_fusion.explorer.get_contract_name` bound to the chain, an
723
+ API key and ``ctx.cache``; without it no contract is named.
722
724
  """
723
725
  chain_id = data.chain_id
724
726
  chain_label = CHAIN_NAMES.get(chain_id, str(chain_id))
725
727
  if resolve_name is None:
726
- resolve_name = partial(get_contract_name, chain_id)
728
+ resolve_name = _no_name
727
729
 
728
730
  markets = _markets_json(data, resolve_name)
729
731
  validation = validate_vault_state(data)
@@ -1,6 +1,7 @@
1
1
  import json
2
2
  import logging
3
3
  import os
4
+ import threading
4
5
  from pathlib import Path
5
6
  from types import SimpleNamespace
6
7
  from unittest.mock import MagicMock, patch
@@ -128,6 +129,30 @@ class TestFileContractCache:
128
129
  key = cache_key(BASE, LOWER, "name")
129
130
  assert cache.get([key]) == {key: "Other"}
130
131
 
132
+ def test_concurrent_writers_never_tear_the_file(self, path):
133
+ writers = [FileContractCache(path) for _ in range(4)]
134
+ barrier = threading.Barrier(len(writers))
135
+
136
+ def write(index: int) -> None:
137
+ barrier.wait()
138
+ for n in range(25):
139
+ address = f"0x{index:02x}{n:038x}"
140
+ writers[index].put({cache_key(ETHEREUM, address, "name"): str(n)})
141
+
142
+ threads = [threading.Thread(target=write, args=(i,)) for i in range(4)]
143
+ for thread in threads:
144
+ thread.start()
145
+ for thread in threads:
146
+ thread.join()
147
+ on_disk = _on_disk(path)
148
+ assert on_disk
149
+ assert all(key.endswith(":name") for key in on_disk)
150
+ assert sorted(p.name for p in path.parent.iterdir()) == [path.name]
151
+ # Each writer still serves every entry it wrote, persisted or not.
152
+ for index, writer in enumerate(writers):
153
+ key = cache_key(ETHEREUM, f"0x{index:02x}{24:038x}", "name")
154
+ assert writer.get([key]) == {key: "24"}
155
+
131
156
  @pytest.mark.skipif(os.geteuid() == 0, reason="root ignores permissions")
132
157
  def test_a_read_only_directory_degrades_to_memory(self, path, caplog):
133
158
  path.parent.mkdir(parents=True)
@@ -229,6 +254,13 @@ class TestTokenResolvers:
229
254
  ctx.web3.eth.get_code.assert_not_called()
230
255
  assert _on_disk(path) == {f"1:{LOWER}:decimals": "6"}
231
256
 
257
+ @pytest.mark.parametrize("value", ["\u00b2", "9" * 5000, "-1", " 6"])
258
+ @patch("ipor_fusion.vault_data.ERC20")
259
+ def test_a_corrupt_decimals_entry_is_a_miss(self, erc20, path, value):
260
+ _write(path, {f"1:{LOWER}:decimals": value})
261
+ erc20.return_value.decimals.return_value.call.return_value = 6
262
+ assert resolve_token_decimals(_ctx(FileContractCache(path)), CHECKSUM) == 6
263
+
232
264
  def test_token_metadata_reads_and_fills_the_contexts_cache(self, path):
233
265
  cache = FileContractCache(path)
234
266
  cache.put({cache_key(ETHEREUM, LOWER, "symbol"): "MAINNET"})
@@ -307,6 +339,20 @@ class TestFetchDeploymentInfo:
307
339
  ctx = _deployment_ctx(FileContractCache(path))
308
340
  assert fetch_deployment_info(ctx, VAULT) == (100, 1_700_000_000, None)
309
341
 
342
+ @pytest.mark.parametrize("value", ["\u00b2", "9" * 5000])
343
+ @patch("ipor_fusion.vault_data.get_deployment_tx", return_value=(TX, None))
344
+ def test_a_corrupt_deployment_entry_is_a_miss(self, get_tx, path, value):
345
+ _write(
346
+ path,
347
+ {
348
+ f"1:{LOWER}:deployment_block": value,
349
+ f"1:{LOWER}:deployment_timestamp": "5",
350
+ },
351
+ )
352
+ ctx = _deployment_ctx(FileContractCache(path))
353
+ assert fetch_deployment_info(ctx, VAULT) == (100, 1_700_000_000, None)
354
+ get_tx.assert_called_once()
355
+
310
356
  @patch(
311
357
  "ipor_fusion.vault_data.get_deployment_tx",
312
358
  return_value=(None, "no-api-key"),
@@ -418,9 +418,7 @@ class TestVaultInfo:
418
418
  yield
419
419
 
420
420
  @patch("ipor_fusion.vault_managers.WithdrawManager")
421
- @patch(
422
- "ipor_fusion.cli.vault_markets_view.get_contract_name", return_value="SomeFuse"
423
- )
421
+ @patch("ipor_fusion.cli.vault_info_view.get_contract_name", return_value="SomeFuse")
424
422
  @patch("ipor_fusion.vault_data.price_oracle_middleware")
425
423
  @patch("ipor_fusion.vault_data.ERC20")
426
424
  @patch("ipor_fusion.vault_data.PlasmaVault")
@@ -546,9 +544,7 @@ class TestVaultInfo:
546
544
  assert f" {ADDR_FEE_RECIPIENT} 8.0000%" in result.output
547
545
  assert "accrued, uncollected: 5" in result.output
548
546
 
549
- @patch(
550
- "ipor_fusion.cli.vault_markets_view.get_contract_name", return_value="SomeFuse"
551
- )
547
+ @patch("ipor_fusion.cli.vault_info_view.get_contract_name", return_value="SomeFuse")
552
548
  @patch("ipor_fusion.vault_data.price_oracle_middleware")
553
549
  @patch("ipor_fusion.vault_data.ERC20")
554
550
  @patch("ipor_fusion.vault_data.PlasmaVault")
@@ -624,9 +620,7 @@ class TestVaultInfo:
624
620
  assert "99999" in result.output
625
621
  assert mock_ctx.default_block == 99999
626
622
 
627
- @patch(
628
- "ipor_fusion.cli.vault_markets_view.get_contract_name", return_value="SomeFuse"
629
- )
623
+ @patch("ipor_fusion.cli.vault_info_view.get_contract_name", return_value="SomeFuse")
630
624
  @patch("ipor_fusion.vault_data.price_oracle_middleware")
631
625
  @patch("ipor_fusion.vault_data.ERC20")
632
626
  @patch("ipor_fusion.vault_data.PlasmaVault")
@@ -690,9 +684,7 @@ class TestVaultInfo:
690
684
  assert result.exit_code == 0, result.output
691
685
  assert "Supply Cap: unlimited" in result.output
692
686
 
693
- @patch(
694
- "ipor_fusion.cli.vault_markets_view.get_contract_name", return_value="SomeFuse"
695
- )
687
+ @patch("ipor_fusion.cli.vault_info_view.get_contract_name", return_value="SomeFuse")
696
688
  @patch("ipor_fusion.vault_data.price_oracle_middleware")
697
689
  @patch("ipor_fusion.vault_data.ERC20")
698
690
  @patch("ipor_fusion.vault_data.PlasmaVault")
@@ -766,9 +758,7 @@ class TestVaultInfo:
766
758
  "ipor_fusion.vault_data.get_deployment_tx",
767
759
  return_value=("0xdeadbeef", None),
768
760
  )
769
- @patch(
770
- "ipor_fusion.cli.vault_markets_view.get_contract_name", return_value="SomeFuse"
771
- )
761
+ @patch("ipor_fusion.cli.vault_info_view.get_contract_name", return_value="SomeFuse")
772
762
  @patch("ipor_fusion.vault_data.price_oracle_middleware")
773
763
  @patch("ipor_fusion.vault_data.ERC20")
774
764
  @patch("ipor_fusion.vault_data.PlasmaVault")
@@ -836,9 +826,7 @@ class TestVaultInfo:
836
826
  assert "Deployed at: block 18500000" in result.output
837
827
  assert "2023-11-14" in result.output
838
828
 
839
- @patch(
840
- "ipor_fusion.cli.vault_markets_view.get_contract_name", return_value="SomeFuse"
841
- )
829
+ @patch("ipor_fusion.cli.vault_info_view.get_contract_name", return_value="SomeFuse")
842
830
  @patch("ipor_fusion.vault_data.price_oracle_middleware")
843
831
  @patch("ipor_fusion.vault_data.ERC20")
844
832
  @patch("ipor_fusion.vault_data.PlasmaVault")
@@ -703,6 +703,10 @@ class TestResolveTokenSymbol:
703
703
  ADDR_1 = "0x1111111111111111111111111111111111111111"
704
704
 
705
705
 
706
+ def _no_name(_address: str) -> str:
707
+ return ""
708
+
709
+
706
710
  def _snapshot(**fields: Any) -> Any:
707
711
  """The `VaultData` fields the market printers read, overridable."""
708
712
  defaults: dict[str, Any] = {
@@ -1049,16 +1053,13 @@ class TestPrintSubstrates:
1049
1053
 
1050
1054
  def test_no_substrates(self, capsys):
1051
1055
  _print_substrates(
1052
- _snapshot(balance_fuses=[FakeBalanceFuse(market_id=1, fuse=ADDR_1)]), None
1056
+ _snapshot(balance_fuses=[FakeBalanceFuse(market_id=1, fuse=ADDR_1)]),
1057
+ _no_name,
1053
1058
  )
1054
1059
  captured = capsys.readouterr()
1055
1060
  assert "(none)" in captured.out
1056
1061
 
1057
- @patch(
1058
- "ipor_fusion.cli.vault_markets_view.get_contract_name",
1059
- return_value="TestContract",
1060
- )
1061
- def test_with_substrates(self, mock_get_name, capsys):
1062
+ def test_with_substrates(self, capsys):
1062
1063
  addr_bytes = bytes.fromhex("00" * 12 + "ab" * 20)
1063
1064
  _print_substrates(
1064
1065
  _snapshot(
@@ -1066,7 +1067,7 @@ class TestPrintSubstrates:
1066
1067
  balance_fuses=[FakeBalanceFuse(market_id=7, fuse=ADDR_1)],
1067
1068
  token_symbols={"0x" + "ab" * 20: "WETH"},
1068
1069
  ),
1069
- "key",
1070
+ lambda _address: "TestContract",
1070
1071
  )
1071
1072
  captured = capsys.readouterr()
1072
1073
  assert "WETH" in captured.out
@@ -1079,7 +1080,7 @@ class TestPrintSubstrates:
1079
1080
  market_substrates={14: [raw_bytes32]},
1080
1081
  balance_fuses=[FakeBalanceFuse(market_id=14, fuse=ADDR_1)],
1081
1082
  ),
1082
- None,
1083
+ _no_name,
1083
1084
  )
1084
1085
  captured = capsys.readouterr()
1085
1086
  assert "morpho_market_id" in captured.out
@@ -1092,7 +1093,7 @@ class TestPrintSubstrates:
1092
1093
  market_substrates={7: [bad_bytes]},
1093
1094
  balance_fuses=[FakeBalanceFuse(market_id=7, fuse=ADDR_1)],
1094
1095
  ),
1095
- None,
1096
+ _no_name,
1096
1097
  )
1097
1098
  captured = capsys.readouterr()
1098
1099
  assert "[encoding error]" in captured.out
@@ -1106,41 +1107,37 @@ class TestPrintSubstrates:
1106
1107
  market_substrates={54: [malformed_executor]},
1107
1108
  balance_fuses=[FakeBalanceFuse(market_id=54, fuse=ADDR_1)],
1108
1109
  ),
1109
- None,
1110
+ _no_name,
1110
1111
  )
1111
1112
  captured = capsys.readouterr()
1112
1113
  assert "[encoding error: EXECUTOR, chain_id=8453]" in captured.out
1113
1114
 
1114
1115
 
1115
1116
  class TestPrintBalanceFusesTable:
1116
- @patch("ipor_fusion.cli.vault_markets_view.get_contract_name")
1117
- def test_splits_venues_and_zero_balance(self, mock_get_name, capsys):
1118
- mock_get_name.side_effect = lambda chain, addr, key: (
1119
- "ZeroBalanceFuse" if addr == ADDR_2 else "MorphoBalanceFuse"
1120
- )
1117
+ def test_splits_venues_and_zero_balance(self, capsys):
1118
+ def resolve_name(addr):
1119
+ return "ZeroBalanceFuse" if addr == ADDR_2 else "MorphoBalanceFuse"
1120
+
1121
1121
  fuses = [
1122
1122
  FakeBalanceFuse(market_id=14, fuse=ADDR_1),
1123
1123
  FakeBalanceFuse(market_id=12, fuse=ADDR_2),
1124
1124
  ]
1125
1125
 
1126
1126
  _print_balance_fuses_table(
1127
- _snapshot(balance_fuses=fuses, market_balances={14: 0, 12: 0}), None
1127
+ _snapshot(balance_fuses=fuses, market_balances={14: 0, 12: 0}),
1128
+ resolve_name,
1128
1129
  )
1129
1130
  out = capsys.readouterr().out
1130
1131
  assert "Balance Fuses (1):" in out
1131
1132
  assert "Zero-Balance Fuses (1):" in out
1132
1133
 
1133
- @patch(
1134
- "ipor_fusion.cli.vault_markets_view.get_contract_name",
1135
- return_value="MorphoBalanceFuse",
1136
- )
1137
- def test_no_zero_balance_section_when_all_venues(self, mock_get_name, capsys):
1134
+ def test_no_zero_balance_section_when_all_venues(self, capsys):
1138
1135
  _print_balance_fuses_table(
1139
1136
  _snapshot(
1140
1137
  balance_fuses=[FakeBalanceFuse(market_id=14, fuse=ADDR_1)],
1141
1138
  market_balances={14: 0},
1142
1139
  ),
1143
- None,
1140
+ lambda _address: "MorphoBalanceFuse",
1144
1141
  )
1145
1142
  out = capsys.readouterr().out
1146
1143
  assert "Balance Fuses (1):" in out
@@ -2081,19 +2078,14 @@ class TestPrintFuseSection:
2081
2078
  Fuses. Dedupes by address, reports a Registrations column, and surfaces
2082
2079
  duplicate registrations in the section header."""
2083
2080
 
2084
- @patch(
2085
- "ipor_fusion.cli.vault_markets_view.get_contract_name",
2086
- return_value="MorphoSupplyFuse",
2087
- )
2088
- def test_renders_market_label_and_substrate_count(self, _mock_name, capsys):
2081
+ def test_renders_market_label_and_substrate_count(self, capsys):
2089
2082
  addr = ADDR_1
2090
2083
  _print_fuse_section(
2091
2084
  "Fuses",
2092
2085
  [addr],
2093
2086
  fuse_markets={addr: IporFusionMarkets.MORPHO},
2094
2087
  market_substrates={IporFusionMarkets.MORPHO: [b"\x11" * 32, b"\x22" * 32]},
2095
- chain_id=1,
2096
- api_key=None,
2088
+ resolve_name=lambda _address: "MorphoSupplyFuse",
2097
2089
  )
2098
2090
  captured = capsys.readouterr()
2099
2091
  assert "Fuses (1 unique)" in captured.out
@@ -2103,52 +2095,37 @@ class TestPrintFuseSection:
2103
2095
  # Substrate count comes from market_substrates, not from registrations
2104
2096
  assert " 2 " in captured.out or "2\n" in captured.out
2105
2097
 
2106
- @patch(
2107
- "ipor_fusion.cli.vault_markets_view.get_contract_name",
2108
- return_value="UnknownFuse",
2109
- )
2110
- def test_renders_question_mark_without_fuse_markets(self, _mock_name, capsys):
2098
+ def test_renders_question_mark_without_fuse_markets(self, capsys):
2111
2099
  _print_fuse_section(
2112
2100
  "Fuses",
2113
2101
  [ADDR_1],
2114
2102
  fuse_markets=None,
2115
2103
  market_substrates=None,
2116
- chain_id=1,
2117
- api_key=None,
2104
+ resolve_name=lambda _address: "UnknownFuse",
2118
2105
  )
2119
2106
  captured = capsys.readouterr()
2120
2107
  assert "?" in captured.out
2121
2108
 
2122
- @patch(
2123
- "ipor_fusion.cli.vault_markets_view.get_contract_name",
2124
- return_value="MorphoSupplyFuse",
2125
- )
2126
- def test_dedupes_repeated_fuse_address(self, _mock_name, capsys):
2109
+ def test_dedupes_repeated_fuse_address(self, capsys):
2127
2110
  addr = ADDR_1
2128
2111
  _print_fuse_section(
2129
2112
  "Instant Withdrawal Fuses",
2130
2113
  [addr, addr, addr],
2131
2114
  fuse_markets={addr: IporFusionMarkets.MORPHO},
2132
2115
  market_substrates={IporFusionMarkets.MORPHO: [b"\x11" * 32] * 3},
2133
- chain_id=1,
2134
- api_key=None,
2116
+ resolve_name=lambda _address: "MorphoSupplyFuse",
2135
2117
  )
2136
2118
  captured = capsys.readouterr()
2137
2119
  assert "1 unique / 3 registrations" in captured.out
2138
2120
  assert captured.out.count(addr) == 1
2139
2121
 
2140
- @patch(
2141
- "ipor_fusion.cli.vault_markets_view.get_contract_name",
2142
- return_value="MorphoSupplyFuse",
2143
- )
2144
- def test_no_registrations_suffix_when_unique(self, _mock_name, capsys):
2122
+ def test_no_registrations_suffix_when_unique(self, capsys):
2145
2123
  _print_fuse_section(
2146
2124
  "Fuses",
2147
2125
  [ADDR_1, ADDR_2],
2148
2126
  fuse_markets=None,
2149
2127
  market_substrates=None,
2150
- chain_id=1,
2151
- api_key=None,
2128
+ resolve_name=lambda _address: "MorphoSupplyFuse",
2152
2129
  )
2153
2130
  captured = capsys.readouterr()
2154
2131
  assert "registrations" not in captured.out
@@ -2160,8 +2137,7 @@ class TestPrintFuseSection:
2160
2137
  [],
2161
2138
  fuse_markets=None,
2162
2139
  market_substrates=None,
2163
- chain_id=1,
2164
- api_key=None,
2140
+ resolve_name=_no_name,
2165
2141
  )
2166
2142
  captured = capsys.readouterr()
2167
2143
  assert "(none)" in captured.out