ipor-fusion 4.0.0.dev63__tar.gz → 4.0.0.dev68__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 (292) hide show
  1. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/MIGRATION_4.md +21 -4
  2. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/PKG-INFO +1 -1
  3. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/pyproject.toml +1 -1
  4. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/cli/vault_info_view.py +2 -2
  5. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/core/fusion_factory.py +64 -1
  6. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/core/simulation.py +15 -12
  7. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/crosschain/ccip/quote.py +1 -3
  8. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/crosschain/simulation.py +2 -6
  9. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/fuses/external_state_events.py +6 -1
  10. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/mcp/server.py +2 -2
  11. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/substrates.py +21 -16
  12. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/types.py +16 -9
  13. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/test_cli_vault_info_golden.py +2 -2
  14. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/test_external_state_events.py +7 -0
  15. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/test_external_state_executor.py +1 -1
  16. ipor_fusion-4.0.0.dev68/tests/test_fusion_instance.py +70 -0
  17. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/test_simulate_crosschain_hyperevm.py +1 -3
  18. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/test_types_and_events.py +16 -5
  19. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/test_vault_simulator_blocks.py +11 -0
  20. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/typed_client/consumer.py +1 -1
  21. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/.claude-plugin/marketplace.json +0 -0
  22. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/.claude-plugin/plugin.json +0 -0
  23. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/.env.example +0 -0
  24. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/.gemini/settings.json +0 -0
  25. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/.git-blame-ignore-revs +0 -0
  26. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/.github/workflows/cd.yml +0 -0
  27. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/.github/workflows/ci.yml +0 -0
  28. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/.github/workflows/commit-lint.yml +0 -0
  29. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/.github/workflows/notify-slack.yml +0 -0
  30. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/.github/workflows/python-build.yml +0 -0
  31. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/.github/workflows/python-publish.yml +0 -0
  32. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/.github/workflows/release.yml +0 -0
  33. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/.github/workflows/report-slack.yml +0 -0
  34. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/.gitignore +0 -0
  35. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/.mcp.json +0 -0
  36. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/.pre-commit-config.yaml +0 -0
  37. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/.python-version +0 -0
  38. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/AGENTS.md +0 -0
  39. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/CHANGELOG.md +0 -0
  40. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/CLAUDE.md +0 -0
  41. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/CODEOWNERS +0 -0
  42. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/LICENSE +0 -0
  43. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/README.md +0 -0
  44. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/build.sh +0 -0
  45. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/examples/CONTRIBUTING.md +0 -0
  46. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/examples/README.md +0 -0
  47. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/examples/advanced_euler_v2_credit_market_base.py +0 -0
  48. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/examples/advanced_euler_v2_looping_arbitrum.py +0 -0
  49. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/examples/composed_hypercore_flow_arbitrum_hyperevm.py +0 -0
  50. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/examples/crosschain_ccip_usdc_arbitrum_hyperevm.py +0 -0
  51. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/examples/external_state_margin_leg_base.py +0 -0
  52. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/examples/simple_aave_v3_supply_base.py +0 -0
  53. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/pytest.ini +0 -0
  54. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/scripts/commit-check.sh +0 -0
  55. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/scripts/crosschain_readiness.py +0 -0
  56. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/skills/ipor-deploy-vault/SKILL.md +0 -0
  57. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/__init__.py +0 -0
  58. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/about.py +0 -0
  59. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/cache.py +0 -0
  60. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/chains.py +0 -0
  61. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/cli/__init__.py +0 -0
  62. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/cli/changelog_cmd.py +0 -0
  63. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/cli/config_cmd.py +0 -0
  64. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/cli/config_store.py +0 -0
  65. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/cli/main.py +0 -0
  66. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/cli/market_cmd.py +0 -0
  67. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/cli/options.py +0 -0
  68. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/cli/oracle_mapping_view.py +0 -0
  69. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/cli/vault_cmd.py +0 -0
  70. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/cli/vault_health.py +0 -0
  71. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/cli/vault_markets_view.py +0 -0
  72. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/cli/vault_rendering.py +0 -0
  73. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/config/__init__.py +0 -0
  74. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/config/roles.py +0 -0
  75. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/core/__init__.py +0 -0
  76. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/core/access.py +0 -0
  77. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/core/address.py +0 -0
  78. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/core/context.py +0 -0
  79. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/core/contract.py +0 -0
  80. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/core/erc20.py +0 -0
  81. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/core/external_state_executor.py +0 -0
  82. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/core/fee_manager.py +0 -0
  83. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/core/fuse_action.py +0 -0
  84. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/core/logs.py +0 -0
  85. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/core/multicall.py +0 -0
  86. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/core/oracle.py +0 -0
  87. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/core/plasma_vault.py +0 -0
  88. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/core/rewards_manager.py +0 -0
  89. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/core/withdraw_manager.py +0 -0
  90. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/crosschain/__init__.py +0 -0
  91. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/crosschain/ccip/__init__.py +0 -0
  92. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/crosschain/ccip/chainlink.py +0 -0
  93. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/crosschain/ccip/codec.py +0 -0
  94. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/crosschain/ccip/contracts.py +0 -0
  95. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/crosschain/ccip/events.py +0 -0
  96. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/crosschain/ccip/indexer.py +0 -0
  97. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/crosschain/ccip/lane.py +0 -0
  98. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/crosschain/ccip/transport.py +0 -0
  99. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/crosschain/contracts.py +0 -0
  100. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/crosschain/discovery.py +0 -0
  101. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/crosschain/errors.py +0 -0
  102. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/crosschain/lane.py +0 -0
  103. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/crosschain/logs.py +0 -0
  104. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/crosschain/messages.py +0 -0
  105. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/crosschain/stargate/__init__.py +0 -0
  106. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/crosschain/stargate/contracts.py +0 -0
  107. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/crosschain/stargate/lane.py +0 -0
  108. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/crosschain/stargate/layerzero.py +0 -0
  109. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/crosschain/stargate/transport.py +0 -0
  110. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/crosschain/transport.py +0 -0
  111. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/dependency_graph.py +0 -0
  112. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/entry_cli.py +0 -0
  113. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/entry_mcp.py +0 -0
  114. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/errors.py +0 -0
  115. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/explorer.py +0 -0
  116. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/field_docs.py +0 -0
  117. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/fuses/__init__.py +0 -0
  118. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/fuses/aave_v3.py +0 -0
  119. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/fuses/async_action.py +0 -0
  120. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/fuses/base.py +0 -0
  121. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/fuses/compound_v3.py +0 -0
  122. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/fuses/crosschain/__init__.py +0 -0
  123. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/fuses/crosschain/base.py +0 -0
  124. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/fuses/crosschain/ccip.py +0 -0
  125. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/fuses/crosschain/stargate.py +0 -0
  126. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/fuses/erc4626.py +0 -0
  127. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/fuses/euler_v2.py +0 -0
  128. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/fuses/events.py +0 -0
  129. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/fuses/external_state.py +0 -0
  130. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/fuses/fluid_instadapp.py +0 -0
  131. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/fuses/gearbox_v3.py +0 -0
  132. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/fuses/hypercore.py +0 -0
  133. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/fuses/hypercore_events.py +0 -0
  134. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/fuses/merkl.py +0 -0
  135. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/fuses/morpho.py +0 -0
  136. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/fuses/ramses_v2.py +0 -0
  137. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/fuses/uniswap_v3.py +0 -0
  138. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/fuses/universal.py +0 -0
  139. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/guide/__init__.py +0 -0
  140. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/guide/architecture.md +0 -0
  141. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/guide/glossary.md +0 -0
  142. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/guide/invariants.md +0 -0
  143. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/guide/quickstart.md +0 -0
  144. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/market_ids.py +0 -0
  145. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/market_info.py +0 -0
  146. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/mcp/__init__.py +0 -0
  147. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/mcp/guide.py +0 -0
  148. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/mcp/models.py +0 -0
  149. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/morpho_api.py +0 -0
  150. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/py.typed +0 -0
  151. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/readers/__init__.py +0 -0
  152. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/readers/aave_v3.py +0 -0
  153. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/readers/aave_v4.py +0 -0
  154. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/readers/compound_v3.py +0 -0
  155. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/readers/hypercore.py +0 -0
  156. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/readers/hypercore_outcome.py +0 -0
  157. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/readers/lending_health.py +0 -0
  158. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/readers/morpho.py +0 -0
  159. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/readers/oracle_mapping.py +0 -0
  160. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/readers/oracle_mapping_nodes.py +0 -0
  161. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/readers/oracle_mapping_reader.py +0 -0
  162. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/readers/position_manager.py +0 -0
  163. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/readers/ramses_v2.py +0 -0
  164. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/readers/uniswap_v3.py +0 -0
  165. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/readers/unpriceable.py +0 -0
  166. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/validation.py +0 -0
  167. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/vault_data.py +0 -0
  168. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/vault_info.py +1 -1
  169. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/vault_managers.py +0 -0
  170. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/vault_positions.py +0 -0
  171. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/src/ipor_fusion/vault_reads.py +0 -0
  172. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/UniswapV3UniversalRouter.py +0 -0
  173. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/_crosschain.py +0 -0
  174. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/_crosschain_lifecycle.py +0 -0
  175. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/_crosschain_recovery.py +0 -0
  176. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/_euler_v2.py +0 -0
  177. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/_foundry.py +0 -0
  178. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/_guide.py +0 -0
  179. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/_hypercore_model.py +0 -0
  180. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/_hypercore_shadow.py +0 -0
  181. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/_multicall.py +0 -0
  182. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/_simulate.py +0 -0
  183. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/_validation.py +0 -0
  184. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/addresses.py +0 -0
  185. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/conftest.py +0 -0
  186. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/constants.py +0 -0
  187. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/fixtures/hypercore_hip3_flow.json +0 -0
  188. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/fixtures/hypercore_market55.json +0 -0
  189. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/fixtures/vault_info_golden/clean.json +0 -0
  190. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/fixtures/vault_info_golden/clean.txt +0 -0
  191. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/fixtures/vault_info_golden/rich.json +0 -0
  192. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/fixtures/vault_info_golden/rich.txt +0 -0
  193. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/test_about.py +0 -0
  194. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/test_access_manager.py +0 -0
  195. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/test_cache.py +0 -0
  196. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/test_chains.py +0 -0
  197. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/test_cli_changelog.py +0 -0
  198. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/test_cli_commands.py +0 -0
  199. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/test_cli_config_store.py +0 -0
  200. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/test_cli_helpers.py +0 -0
  201. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/test_cli_hypercore.py +0 -0
  202. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/test_cli_lending_health.py +0 -0
  203. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/test_cli_market_morpho_blue.py +0 -0
  204. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/test_cli_oracle_mapping.py +0 -0
  205. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/test_cli_pending_requests_scan.py +0 -0
  206. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/test_cli_role_accounts.py +0 -0
  207. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/test_cli_vault_predates_getters.py +0 -0
  208. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/test_cli_vault_validate.py +0 -0
  209. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/test_contract_call.py +0 -0
  210. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/test_contract_encoder.py +0 -0
  211. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/test_crosschain_ccip_events.py +0 -0
  212. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/test_crosschain_ccip_indexer.py +0 -0
  213. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/test_crosschain_ccip_quote.py +0 -0
  214. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/test_crosschain_chainlink.py +0 -0
  215. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/test_crosschain_contracts.py +0 -0
  216. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/test_crosschain_encoding.py +0 -0
  217. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/test_crosschain_errors.py +0 -0
  218. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/test_crosschain_lane.py +0 -0
  219. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/test_crosschain_layering.py +0 -0
  220. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/test_crosschain_readiness.py +0 -0
  221. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/test_crosschain_readiness_report.py +0 -0
  222. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/test_crosschain_transport.py +0 -0
  223. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/test_erc20.py +0 -0
  224. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/test_examples_composed_hypercore.py +0 -0
  225. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/test_examples_crosschain.py +0 -0
  226. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/test_examples_euler_looping_arbitrum.py +0 -0
  227. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/test_examples_vaults.py +0 -0
  228. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/test_explorer.py +0 -0
  229. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/test_fee_manager.py +0 -0
  230. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/test_fuse_encoding.py +0 -0
  231. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/test_guide.py +0 -0
  232. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/test_hypercore_deployment.py +0 -0
  233. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/test_hypercore_events.py +0 -0
  234. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/test_hypercore_flow.py +0 -0
  235. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/test_hypercore_live.py +0 -0
  236. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/test_hypercore_outcome.py +0 -0
  237. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/test_hypercore_readers.py +0 -0
  238. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/test_hypercore_simulation.py +0 -0
  239. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/test_logs.py +0 -0
  240. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/test_mcp_hypercore.py +0 -0
  241. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/test_mcp_models.py +0 -0
  242. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/test_mcp_server.py +0 -0
  243. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/test_mcp_vault_validate.py +0 -0
  244. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/test_multicall.py +0 -0
  245. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/test_oracle.py +0 -0
  246. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/test_oracle_mapping.py +0 -0
  247. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/test_plasma_vault.py +0 -0
  248. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/test_reader_encoding.py +0 -0
  249. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/test_readiness_selection.py +0 -0
  250. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/test_revert_decoding.py +0 -0
  251. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/test_rewards_manager.py +0 -0
  252. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/test_roles.py +0 -0
  253. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/test_sdk_layering.py +0 -0
  254. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/test_simulate_borrow_aave_v3_mainnet.py +0 -0
  255. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/test_simulate_claim_merkl_wrapper_base.py +0 -0
  256. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/test_simulate_claim_morpho_rewards.py +0 -0
  257. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/test_simulate_crosschain_canary.py +0 -0
  258. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/test_simulate_crosschain_hypercore.py +0 -0
  259. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/test_simulate_crosschain_lifecycle.py +0 -0
  260. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/test_simulate_euler_v2_batch_base.py +0 -0
  261. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/test_simulate_euler_v2_credit_market_base.py +0 -0
  262. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/test_simulate_euler_v2_swap_deploy_base.py +0 -0
  263. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/test_simulate_euler_v2_swap_reconfigure_base.py +0 -0
  264. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/test_simulate_euler_v2_swap_registry_base.py +0 -0
  265. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/test_simulate_examples_arbitrum.py +0 -0
  266. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/test_simulate_examples_base.py +0 -0
  267. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/test_simulate_examples_crosschain.py +0 -0
  268. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/test_simulate_external_state_executor_base.py +0 -0
  269. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/test_simulate_fusion_factory_clone_base.py +0 -0
  270. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/test_simulate_looping_aave_v3_base.py +0 -0
  271. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/test_simulate_looping_morpho_blue_base.py +0 -0
  272. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/test_simulate_morpho_blue.py +0 -0
  273. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/test_simulate_pilot_v3.py +0 -0
  274. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/test_simulate_pilot_v4_uniswap.py +0 -0
  275. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/test_simulate_pilot_v5_ramses.py +0 -0
  276. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/test_simulate_pilot_v5_scheduled.py +0 -0
  277. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/test_simulate_plasma_vault_setters_base.py +0 -0
  278. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/test_simulate_readers_integration.py +0 -0
  279. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/test_simulate_release_funds.py +0 -0
  280. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/test_simulate_swap_on_base.py +0 -0
  281. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/test_simulate_vault_from_scratch_base.py +0 -0
  282. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/test_solidity_compiler.py +0 -0
  283. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/test_solidity_mirrors.py +0 -0
  284. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/test_substrates.py +0 -0
  285. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/test_unpriceable_tokens.py +0 -0
  286. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/test_validation.py +0 -0
  287. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/test_vault_dep_graph.py +0 -0
  288. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/test_vault_info_onchain_base.py +0 -0
  289. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/test_vault_info_onchain_base_no_withdraw_manager.py +0 -0
  290. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/test_web3_context.py +0 -0
  291. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/tests/test_withdraw_manager.py +0 -0
  292. {ipor_fusion-4.0.0.dev63 → ipor_fusion-4.0.0.dev68}/uv.lock +0 -0
@@ -29,15 +29,22 @@ widths or address spelling.
29
29
  | `RoleId` | a `Roles` member (accepted as is), or `RoleId(n)`; `Roles.resolve(name)` returns one |
30
30
  | `Decimals`, `Period`, `Amount`, ... | `Decimals(8)`, `Period(0)`, `Amount(n)` (unchanged) |
31
31
  | `ChecksumAddress` | `Web3.to_checksum_address(value)` |
32
- | `HexAddress` | any `ChecksumAddress`, or `parse_hex_address(value)` (20 bytes or hex in any spelling → lowercase `0x` form) |
32
+ | `HexAddress` | any `ChecksumAddress`, or `parse_hex_address(value)` (20 bytes or `0x`/`0X`-prefixed hex in any case → lowercase `0x` form) |
33
33
  | `MorphoBlueMarketId` | `parse_morpho_market_id(value)`: 32 bytes or hex in any spelling, always `0x` + 64 lowercase digits |
34
- | `TxHash` | `parse_tx_hash(value)` (32 bytes or hex in any spelling) |
34
+ | `TxHash` | `parse_tx_hash(value)` (32 bytes or `0x`/`0X`-prefixed hex in any case) |
35
35
  | `PlasmaVaultAddress`, `FuseAddress`, `AccessManagerAddress` | from the SDK (`PlasmaVault.address`, `get_fuses()`, `get_access_manager_address()`, `FusionInstance`, discovery), or `parse_plasma_vault_address` / `parse_fuse_address` / `parse_access_manager_address(value)` for a raw address; they label the role and check nothing on chain |
36
36
  | crosschain ids | `CommandId(b)`, `MessageId(b)`, `AssetId(b)`, `OperationId(b)`, `TransferId(b)`, `ReturnRequestId(b)`, `PositionSetHash(b)` (bytes32); `ProposalId(n)`, `DeploymentRequestId(n)`, `StargateAssetId(n)` (int) |
37
37
  | HyperCore ids | `HyperCoreTokenIndex(n)` (or `USDC_TOKEN_INDEX`), `HyperCoreAsset(n)` (or `hip3_action_asset(dex, index)`), `HyperCoreOrderId(n)`, `HyperCoreClientOrderId(n)`, `MarginTableId(n)`, `HyperCoreActionId(n)`, `PayloadHash(b)` |
38
38
  | external state | `ProposalHash(b)` |
39
39
  | `BlockIdentifier` | a block number, `"latest"` / `"pending"` / ..., or a block hash |
40
40
 
41
+ The address and hash parsers (`parse_hex_address`, `parse_tx_hash`,
42
+ `parse_plasma_vault_address`, `parse_fuse_address`, `parse_access_manager_address`)
43
+ validate strictly: a string needs exactly one `0x`/`0X` prefix and the exact number of
44
+ hex digits, with no surrounding whitespace; anything else raises `ValueError`. Strip
45
+ user input yourself before parsing it. `parse_morpho_market_id` still accepts hex
46
+ without `0x`.
47
+
41
48
  `MessageId` covers both a CCIP `messageId` and a LayerZero `guid`. `AssetId` is the
42
49
  crosschain executor's `bytes32` asset id; a Stargate pool's `uint16` asset id is
43
50
  `StargateAssetId`. `DeploymentRequestId` is a factory's `uint256` dispatcher request;
@@ -49,8 +56,9 @@ a `bytes32` return (recall) request is `ReturnRequestId`.
49
56
  `build_vault_info_json`, `ensure_supported_chain` and `fetch_vault_lending_health`
50
57
  take `ChainId`. The vault is a `PlasmaVaultAddress` (see *Address roles*) in
51
58
  `validate_vault` and `fetch_deployment_info`; `build_vault_info_json(ctx, plasma_vault,
52
- data, chain_id, chain_label)` takes no address and labels the document with
53
- `plasma_vault.address`. `fetch_unpriceable_priced_tokens` takes the oracle and
59
+ data, *, chain_id, chain_label)` takes no address, takes the chain by keyword, and
60
+ labels the document with `plasma_vault.address` (a test that mocks `PlasmaVault`
61
+ sets `.address`). `fetch_unpriceable_priced_tokens` takes the oracle and
54
62
  underlying as `ChecksumAddress`. `resolve_token_symbol`, `resolve_token_decimals`,
55
63
  `get_contract_name` and `get_deployment_tx` take `HexAddress` (a checksum address
56
64
  fits), and the `resolve_name` / `resolve_symbol` callbacks receive one (a callback
@@ -102,6 +110,15 @@ Every fuse constructor, `FuseAction.fuse`, `BalanceFuse.fuse`,
102
110
  `FusionInstance.access_manager` return one. `resolve_access_manager` still takes any
103
111
  `ChecksumAddress`: it is what checks that the address is a vault.
104
112
 
113
+ **Simulation and factory records.** `VaultSimulator(web3, *, vault=None, alpha=None,
114
+ block=...)` takes every argument after `web3` by keyword, and `vault` and `alpha` are
115
+ optional: a simulator that only observes, deploys or sends `execute_call(call,
116
+ from_=...)` needs neither, so drop a zero-address placeholder. `execute()` without
117
+ `vault` and `alpha` raises `ValueError`, as does `execute_call` without `from_` or
118
+ `alpha`. `FusionInstance.from_dict(data)` rebuilds a stored instance (the
119
+ `dataclasses.asdict` form) with every field parsed; use it instead of
120
+ `FusionInstance(**data)`, which passes raw strings into the role-typed fields.
121
+
105
122
  **Markets and substrates.** Every `IporFusionMarkets` member is a `MarketId`.
106
123
  `decode_substrate`, `market_name`, `format_market_label` take `MarketId`.
107
124
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: ipor-fusion
3
- Version: 4.0.0.dev63
3
+ Version: 4.0.0.dev68
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.dev63"
3
+ version = "4.0.0.dev68"
4
4
  description = "The IPOR Fusion SDK for Python"
5
5
  authors = [
6
6
  { name = "Adam Mydlarz", email = "amydlarz@gmail.com" },
@@ -98,8 +98,8 @@ def _print_vault_info(
98
98
  ctx,
99
99
  plasma_vault,
100
100
  data,
101
- chain_id,
102
- chain_label,
101
+ chain_id=chain_id,
102
+ chain_label=chain_label,
103
103
  resolve_name=partial(get_contract_name, chain_id, api_key=api_key),
104
104
  )
105
105
  click.echo(json.dumps(result, indent=2))
@@ -22,7 +22,9 @@ For external-signer flows, grab the bytes directly:
22
22
 
23
23
  from __future__ import annotations
24
24
 
25
+ from collections.abc import Callable, Mapping
25
26
  from dataclasses import dataclass
27
+ from typing import Any
26
28
 
27
29
  from eth_abi import decode as abi_decode
28
30
  from eth_typing import ChecksumAddress
@@ -31,7 +33,13 @@ from web3 import Web3
31
33
  from web3.types import TxReceipt
32
34
 
33
35
  from ipor_fusion.core.contract import Call, ContractWrapper
34
- from ipor_fusion.types import AccessManagerAddress, Period, PlasmaVaultAddress
36
+ from ipor_fusion.types import (
37
+ AccessManagerAddress,
38
+ Period,
39
+ PlasmaVaultAddress,
40
+ parse_access_manager_address,
41
+ parse_plasma_vault_address,
42
+ )
35
43
 
36
44
 
37
45
  @dataclass(slots=True, frozen=True)
@@ -70,6 +78,61 @@ class FusionInstance:
70
78
  context_manager: ChecksumAddress
71
79
  price_manager: ChecksumAddress
72
80
 
81
+ @classmethod
82
+ def from_dict(
83
+ cls, data: Mapping[str, Any]
84
+ ) -> FusionInstance: # proctor: ignore[primitives-untyped-dict]
85
+ """Rebuild an instance from its JSON form (``dataclasses.asdict``),
86
+ parsing every field: role addresses through their parsers, the rest
87
+ checksummed, integers and strings type-checked. Raises ``ValueError``
88
+ on a missing, extra or malformed field."""
89
+ missing = _INSTANCE_FIELDS.keys() - data.keys()
90
+ extra = data.keys() - _INSTANCE_FIELDS.keys()
91
+ if missing or extra:
92
+ raise ValueError(
93
+ f"FusionInstance fields: missing {sorted(missing)}, extra {sorted(extra)}"
94
+ )
95
+ return cls(
96
+ **{name: parse(data[name]) for name, parse in _INSTANCE_FIELDS.items()}
97
+ )
98
+
99
+
100
+ def _integer(value: Any) -> int:
101
+ if type(value) is not int:
102
+ raise ValueError(f"expected an integer: {value!r}")
103
+ return value
104
+
105
+
106
+ def _text(value: Any) -> str:
107
+ if not isinstance(value, str):
108
+ raise ValueError(f"expected a string: {value!r}")
109
+ return value
110
+
111
+
112
+ def _address(value: Any) -> ChecksumAddress:
113
+ return Web3.to_checksum_address(_text(value))
114
+
115
+
116
+ _INSTANCE_FIELDS: dict[str, Callable[[Any], Any]] = {
117
+ "index": _integer,
118
+ "version": _integer,
119
+ "asset_name": _text,
120
+ "asset_symbol": _text,
121
+ "asset_decimals": _integer,
122
+ "underlying_token": _address,
123
+ "underlying_token_symbol": _text,
124
+ "underlying_token_decimals": _integer,
125
+ "initial_owner": _address,
126
+ "plasma_vault": lambda value: parse_plasma_vault_address(_text(value)),
127
+ "plasma_vault_base": _address,
128
+ "access_manager": lambda value: parse_access_manager_address(_text(value)),
129
+ "fee_manager": _address,
130
+ "rewards_manager": _address,
131
+ "withdraw_manager": _address,
132
+ "context_manager": _address,
133
+ "price_manager": _address,
134
+ }
135
+
73
136
 
74
137
  _FUSION_INSTANCE_OUTPUT_TYPES: list[str] = [
75
138
  "uint256", # index
@@ -16,7 +16,7 @@ from web3.utils.address import get_create_address
16
16
  from ipor_fusion.core.contract import Call, _encode_calldata
17
17
  from ipor_fusion.core.fuse_action import FuseAction
18
18
  from ipor_fusion.errors import SimulationError, decode_custom_error
19
- from ipor_fusion.types import ZERO_ADDRESS, PlasmaVaultAddress
19
+ from ipor_fusion.types import PlasmaVaultAddress
20
20
 
21
21
  #: ``n`` (the sent block's index in the run) -> account overrides for it.
22
22
  StateOverrideProvider = Callable[[int], Mapping[str, Mapping[str, Any]]]
@@ -190,15 +190,16 @@ class VaultSimulator:
190
190
  def __init__(
191
191
  self,
192
192
  web3: Web3,
193
- vault: PlasmaVaultAddress,
194
- alpha: ChecksumAddress,
193
+ *,
194
+ vault: PlasmaVaultAddress | None = None,
195
+ alpha: ChecksumAddress | None = None,
195
196
  block: BlockIdentifier = "latest",
196
197
  validation: bool = False,
197
198
  trace_transfers: bool = False,
198
199
  ):
199
200
  self._web3 = web3
200
- self._vault = Web3.to_checksum_address(vault)
201
- self._alpha = Web3.to_checksum_address(alpha)
201
+ self._vault = vault
202
+ self._alpha = alpha
202
203
  self._block: BlockIdentifier = block
203
204
  self._validation = validation
204
205
  self._trace_transfers = trace_transfers
@@ -341,6 +342,8 @@ class VaultSimulator:
341
342
 
342
343
  def execute(self, actions: list[FuseAction]) -> VaultSimulator:
343
344
  """Queue a default `execute((address,bytes)[])` batch on the vault, from alpha."""
345
+ if self._vault is None or self._alpha is None:
346
+ raise ValueError("execute needs a simulator built with vault= and alpha=")
344
347
  data = FuseAction.encode_execute_payload(actions, "execute((address,bytes)[])")
345
348
  return self._queue_execute(self._vault, data, self._alpha)
346
349
 
@@ -351,11 +354,12 @@ class VaultSimulator:
351
354
  e.g. `RewardsManager.claim_rewards(...)` where the wrapper differs from
352
355
  `PlasmaVault.execute`. `from_` defaults to alpha.
353
356
  """
354
- return self._queue_execute(
355
- call.to,
356
- call.data,
357
- Web3.to_checksum_address(from_) if from_ else self._alpha,
358
- )
357
+ sender = from_ or self._alpha
358
+ if sender is None:
359
+ raise ValueError(
360
+ "execute_call needs from_ or a simulator built with alpha="
361
+ )
362
+ return self._queue_execute(call.to, call.data, sender)
359
363
 
360
364
  def _queue_execute(
361
365
  self, to: ChecksumAddress, data: bytes, from_: ChecksumAddress
@@ -751,8 +755,7 @@ def erc20_balance_slot(
751
755
  if max_slot < 0:
752
756
  raise ValueError(no_slot)
753
757
 
754
- zero: Any = ZERO_ADDRESS
755
- sim = VaultSimulator(web3, vault=zero, alpha=zero, block=block)
758
+ sim = VaultSimulator(web3, block=block)
756
759
  for slot in range(max_slot + 1):
757
760
  holder = Web3.to_checksum_address(
758
761
  "0x" + keccak(encode(["uint256"], [slot]))[-20:].hex()
@@ -18,13 +18,11 @@ from web3.types import BlockIdentifier
18
18
 
19
19
  from ipor_fusion.core.contract import Call
20
20
  from ipor_fusion.core.simulation import VaultSimulator
21
- from ipor_fusion.types import parse_plasma_vault_address
22
21
 
23
22
  #: Selector of ``CcipInsufficientNativeFeeBalance(uint256 balance, uint256 required)``.
24
23
  INSUFFICIENT_NATIVE_FEE_SELECTOR: bytes = keccak(
25
24
  text="CcipInsufficientNativeFeeBalance(uint256,uint256)"
26
25
  )[:4]
27
- _NO_VAULT = parse_plasma_vault_address(bytes(20))
28
26
 
29
27
 
30
28
  def quote_ccip_native_fee(
@@ -45,7 +43,7 @@ def quote_ccip_native_fee(
45
43
  valid at ``block`` up to the fee check; a revert before it, or a call that
46
44
  sends no message, raises ``ValueError``.
47
45
  """
48
- simulator = VaultSimulator(web3, vault=_NO_VAULT, alpha=from_, block=block)
46
+ simulator = VaultSimulator(web3, alpha=from_, block=block)
49
47
  simulator.with_state_override(payer, balance=hex(0))
50
48
  simulator.add_call(call, from_=from_, label="quote", gas=gas)
51
49
  (result,) = simulator.run().calls
@@ -25,7 +25,6 @@ Example -- bridge from a source vault and let the dispatcher settle::
25
25
  from __future__ import annotations
26
26
 
27
27
  from collections.abc import Mapping
28
- from typing import Any
29
28
 
30
29
  from eth_typing import ChecksumAddress
31
30
  from web3 import Web3
@@ -41,7 +40,7 @@ from ipor_fusion.crosschain.transport import (
41
40
  CrosschainTransport,
42
41
  OutboundMessage,
43
42
  )
44
- from ipor_fusion.types import ZERO_ADDRESS, ChainId, PlasmaVaultAddress
43
+ from ipor_fusion.types import ChainId, PlasmaVaultAddress
45
44
 
46
45
  #: What the synthetic token source holds: enough for any test amount.
47
46
  SYNTHETIC_BALANCE = 2**96
@@ -89,10 +88,7 @@ class CrosschainSimulator:
89
88
  raise ValueError(f"chain {chain_id} is not configured on the transport")
90
89
  if chain_id in self._chains:
91
90
  raise ValueError(f"chain {chain_id} already added")
92
- zero: Any = ZERO_ADDRESS
93
- sim = VaultSimulator(
94
- web3, vault=vault or zero, alpha=alpha or zero, block=block
95
- )
91
+ sim = VaultSimulator(web3, vault=vault, alpha=alpha, block=block)
96
92
  # A chain without a real token holder to impersonate gets a synthetic
97
93
  # one, funded once by a storage override; deliveries transfer from it.
98
94
  for token in self._transport.synthetic_credit_tokens(chain_id):
@@ -196,7 +196,12 @@ def external_state_event_name(topic: bytes | str) -> str | None:
196
196
  def _hex(value: Any) -> TxHash | None:
197
197
  if value is None:
198
198
  return None
199
- return parse_tx_hash(_as_bytes(value, "transactionHash"))
199
+ raw = _as_bytes(value, "transactionHash")
200
+ if len(raw) != 32:
201
+ raise ExternalStateEventDecodeError(
202
+ f"transactionHash: {len(raw)} bytes, expected 32"
203
+ )
204
+ return parse_tx_hash(raw)
200
205
 
201
206
 
202
207
  def decode_external_state_event(log: Mapping[str, Any]) -> ExternalStateEvent | None:
@@ -216,8 +216,8 @@ def vault_info(
216
216
  ctx,
217
217
  plasma_vault,
218
218
  data,
219
- chain_id,
220
- chain_label,
219
+ chain_id=chain_id,
220
+ chain_label=chain_label,
221
221
  resolve_name=partial(get_contract_name, chain_id, api_key=api_key),
222
222
  )
223
223
  return VaultInfoResponse.model_validate(result)
@@ -47,10 +47,15 @@ class SubstrateInfo:
47
47
  # Slippage variant: [2 type chars][62 value chars]
48
48
 
49
49
 
50
+ def _address(hex_digits: str) -> HexAddress:
51
+ """The address in 40 hex digits sliced out of a substrate word."""
52
+ return parse_hex_address("0x" + hex_digits)
53
+
54
+
50
55
  def _decode_type_lshift160(hex_str: str, types: dict[int, str]) -> SubstrateInfo:
51
56
  """Decode type<<160 | address (Ebisu, Midas, Balancer, Velodrome)."""
52
57
  type_byte = int(hex_str[22:24], 16)
53
- addr = parse_hex_address(hex_str[24:])
58
+ addr = _address(hex_str[24:])
54
59
  label = types.get(type_byte, f"type={type_byte}")
55
60
  return SubstrateInfo(address=addr, type_label=label)
56
61
 
@@ -63,13 +68,13 @@ def _decode_type_lshift248(hex_str: str, types: dict[int, str]) -> SubstrateInfo
63
68
  return SubstrateInfo(
64
69
  raw_hex=f"0x{hex_str}", type_label=label, extra={"slippage": str(value)}
65
70
  )
66
- addr = parse_hex_address(hex_str[24:])
71
+ addr = _address(hex_str[24:])
67
72
  return SubstrateInfo(address=addr, type_label=label)
68
73
 
69
74
 
70
75
  def _decode_plain_address(hex_str: str) -> SubstrateInfo:
71
76
  """Decode zero-padded address: 12 zero bytes + 20 address bytes."""
72
- return SubstrateInfo(address=parse_hex_address(hex_str[24:]))
77
+ return SubstrateInfo(address=_address(hex_str[24:]))
73
78
 
74
79
 
75
80
  def _decode_morpho(hex_str: str) -> SubstrateInfo:
@@ -84,14 +89,14 @@ def _decode_uniswap_v4(hex_str: str) -> SubstrateInfo:
84
89
  substrate-as-asset (12 zero bytes + address)."""
85
90
  if hex_str[:24] == "0" * 24:
86
91
  return SubstrateInfo(
87
- address=parse_hex_address(hex_str[24:]), type_label="uniswap_v4_token"
92
+ address=_address(hex_str[24:]), type_label="uniswap_v4_token"
88
93
  )
89
94
  return SubstrateInfo(raw_hex=f"0x{hex_str}", type_label="uniswap_v4_pool_id")
90
95
 
91
96
 
92
97
  def _decode_enso(hex_str: str) -> SubstrateInfo:
93
98
  """Decode address<<96 | selector<<64 (Enso)."""
94
- addr = parse_hex_address(hex_str[0:40])
99
+ addr = _address(hex_str[0:40])
95
100
  selector = f"0x{hex_str[40:48]}"
96
101
  return SubstrateInfo(
97
102
  address=addr, type_label="ENSO_TARGET", extra={"selector": selector}
@@ -100,7 +105,7 @@ def _decode_enso(hex_str: str) -> SubstrateInfo:
100
105
 
101
106
  def _decode_dolomite(hex_str: str) -> SubstrateInfo:
102
107
  """Decode asset<<96 | subAccountId<<88 | canBorrow<<80 (Dolomite)."""
103
- addr = parse_hex_address(hex_str[0:40])
108
+ addr = _address(hex_str[0:40])
104
109
  sub_account_id = int(hex_str[40:42], 16)
105
110
  can_borrow = (int(hex_str[42:44], 16) & 0x01) == 1
106
111
  return SubstrateInfo(
@@ -127,7 +132,7 @@ def _decode_aave_v4(hex_str: str) -> SubstrateInfo:
127
132
  return SubstrateInfo(raw_hex=f"0x{hex_str}", type_label=label)
128
133
  flags = int(hex_str[50:52], 16)
129
134
  return SubstrateInfo(
130
- address=parse_hex_address(hex_str[2:42]),
135
+ address=_address(hex_str[2:42]),
131
136
  type_label="AAVE_V4_RESERVE",
132
137
  extra={
133
138
  "reserve_id": str(int(hex_str[42:50], 16)),
@@ -145,7 +150,7 @@ def _decode_euler_v2(hex_str: str) -> SubstrateInfo:
145
150
  plain-address path silently produces a malformed address (last 20 bytes are
146
151
  flag bytes + zero padding).
147
152
  """
148
- addr = parse_hex_address(hex_str[0:40])
153
+ addr = _address(hex_str[0:40])
149
154
  is_collateral = (int(hex_str[40:42], 16) & 0x01) == 1
150
155
  can_borrow = (int(hex_str[42:44], 16) & 0x01) == 1
151
156
  sub_account = f"0x{hex_str[44:46]}"
@@ -174,13 +179,13 @@ def _decode_async_action(hex_str: str) -> SubstrateInfo:
174
179
  type_byte = int(hex_str[0:2], 16)
175
180
  if type_byte == 0:
176
181
  return SubstrateInfo(
177
- address=parse_hex_address(hex_str[2:42]),
182
+ address=_address(hex_str[2:42]),
178
183
  type_label="ALLOWED_AMOUNT_TO_OUTSIDE",
179
184
  extra={"amount": str(int(hex_str[42:64], 16))},
180
185
  )
181
186
  if type_byte == 1:
182
187
  return SubstrateInfo(
183
- address=parse_hex_address(hex_str[16:56]),
188
+ address=_address(hex_str[16:56]),
184
189
  type_label="ALLOWED_TARGETS",
185
190
  extra={"selector": f"0x{hex_str[56:64]}"},
186
191
  )
@@ -225,10 +230,10 @@ def _decode_external_state(hex_str: str) -> SubstrateInfo:
225
230
  if type_byte == 0:
226
231
  return SubstrateInfo(raw_hex=f"0x{hex_str}", type_label="UNDEFINED")
227
232
  if label := _EXTERNAL_STATE_ADDRESS_TYPES.get(type_byte):
228
- return SubstrateInfo(address=parse_hex_address(hex_str[24:]), type_label=label)
233
+ return SubstrateInfo(address=_address(hex_str[24:]), type_label=label)
229
234
  if type_byte == 2:
230
235
  return SubstrateInfo(
231
- address=parse_hex_address(hex_str[24:]),
236
+ address=_address(hex_str[24:]),
232
237
  type_label="TARGET",
233
238
  extra={"selector": f"0x{hex_str[16:24]}"},
234
239
  )
@@ -266,7 +271,7 @@ def _decode_crosschain(hex_str: str) -> SubstrateInfo:
266
271
  is_error=True,
267
272
  extra={"chain_id": str(chain_id)},
268
273
  )
269
- info = SubstrateInfo(address=parse_hex_address(hex_str[24:]), type_label=label)
274
+ info = SubstrateInfo(address=_address(hex_str[24:]), type_label=label)
270
275
  if type_byte == 2:
271
276
  info.extra = {"chain_id": str(chain_id)}
272
277
  return info
@@ -326,7 +331,7 @@ def _decode_hypercore(hex_str: str) -> SubstrateInfo:
326
331
  return _hypercore_invalid(hex_str, label)
327
332
  if type_byte == 1:
328
333
  return SubstrateInfo(
329
- address=parse_hex_address(hex_str[24:]),
334
+ address=_address(hex_str[24:]),
330
335
  type_label=label,
331
336
  extra={"token_index": str(int(hex_str[8:24], 16))},
332
337
  )
@@ -347,10 +352,10 @@ def _decode_hypercore(hex_str: str) -> SubstrateInfo:
347
352
  extra["read_index"] = str(read_index_of(asset))
348
353
  return SubstrateInfo(raw_hex=f"0x{hex_str}", type_label=label, extra=extra)
349
354
  if type_byte == 3:
350
- return SubstrateInfo(address=parse_hex_address(hex_str[24:]), type_label=label)
355
+ return SubstrateInfo(address=_address(hex_str[24:]), type_label=label)
351
356
  if type_byte == 4:
352
357
  return SubstrateInfo(
353
- address=parse_hex_address(hex_str[24:]),
358
+ address=_address(hex_str[24:]),
354
359
  type_label=label,
355
360
  extra={"max_fee_rate_decibps": str(int(hex_str[8:24], 16))},
356
361
  )
@@ -145,22 +145,28 @@ def _hex_digits(value: str | bytes, size: int, what: str) -> str:
145
145
  if len(value) != size:
146
146
  raise ValueError(f"invalid {what} (expected {size} bytes): {value!r}")
147
147
  return value.hex()
148
- digits = value.strip().removeprefix("0x").removeprefix("0X").lower()
149
- if len(digits) != 2 * size or any(c not in "0123456789abcdef" for c in digits):
150
- raise ValueError(f"invalid {what} (expected {size}-byte hex): {value}")
148
+ digits = value[2:].lower()
149
+ if (
150
+ value[:2] not in ("0x", "0X")
151
+ or len(digits) != 2 * size
152
+ or any(c not in "0123456789abcdef" for c in digits)
153
+ ):
154
+ raise ValueError(f"invalid {what} (expected 0x and {size}-byte hex): {value!r}")
151
155
  return digits
152
156
 
153
157
 
154
158
  def parse_hex_address(value: str | bytes) -> HexAddress:
155
- """An address as ``0x`` and 40 lowercase hex digits, from 20 raw bytes or hex
156
- in any spelling; raises ``ValueError`` on anything else. For a checksummed
159
+ """An address as ``0x`` and 40 lowercase hex digits, from 20 raw bytes or
160
+ ``0x``-prefixed hex in any case; raises ``ValueError`` on anything else,
161
+ surrounding whitespace included. For a checksummed
157
162
  address use ``Web3.to_checksum_address``."""
158
163
  return HexAddress(HexStr("0x" + _hex_digits(value, 20, "address")))
159
164
 
160
165
 
161
166
  def parse_tx_hash(value: str | bytes) -> TxHash:
162
167
  """A transaction hash as ``0x`` and 64 lowercase hex digits, from 32 raw bytes
163
- or hex in any spelling; raises ``ValueError`` on anything else."""
168
+ or ``0x``-prefixed hex in any case; raises ``ValueError`` on anything else,
169
+ surrounding whitespace included."""
164
170
  return TxHash(HexStr("0x" + _hex_digits(value, 32, "transaction hash")))
165
171
 
166
172
 
@@ -169,9 +175,10 @@ def _checksum(value: str | bytes) -> ChecksumAddress:
169
175
 
170
176
 
171
177
  def parse_plasma_vault_address(value: str | bytes) -> PlasmaVaultAddress:
172
- """A Plasma Vault's address, checksummed from 20 raw bytes or hex in any
173
- spelling; raises ``ValueError`` on anything else. It labels the role and
174
- checks nothing on chain."""
178
+ """A Plasma Vault's address, checksummed from 20 raw bytes or ``0x``/``0X``
179
+ and 40 hex digits in any case; raises ``ValueError`` on anything else,
180
+ surrounding whitespace included. It labels the role and checks nothing on
181
+ chain."""
175
182
  return PlasmaVaultAddress(_checksum(value))
176
183
 
177
184
 
@@ -378,8 +378,8 @@ def test_public_builder_renders_the_cli_json(scenario: str) -> None:
378
378
  ctx,
379
379
  PlasmaVault(ctx, VAULT), # type: ignore[arg-type]
380
380
  data,
381
- 1,
382
- "ethereum",
381
+ chain_id=1,
382
+ chain_label="ethereum",
383
383
  resolve_name=lambda address: _NAMES_BY_ADDRESS.get(address.lower(), ""),
384
384
  )
385
385
  expected = (FIXTURES / f"{scenario}.json").read_text(encoding="utf-8")
@@ -96,6 +96,13 @@ def test_hex_text_data_and_topics_from_an_indexer_decode_too():
96
96
  assert event.transaction_hash == "0x" + "fb" * 32
97
97
 
98
98
 
99
+ def test_a_short_transaction_hash_is_a_decode_error():
100
+ log = _log("ExternalStateAssetRescued", (SIGNER,))
101
+ log["transactionHash"] = bytes(31)
102
+ with pytest.raises(ExternalStateEventDecodeError, match="transactionHash"):
103
+ decode_external_state_event(log)
104
+
105
+
99
106
  def test_an_unknown_topic_is_none_and_a_malformed_listed_one_raises():
100
107
  assert (
101
108
  decode_external_state_event(
@@ -64,7 +64,7 @@ def _observed(call, return_data: bytes):
64
64
  Feeds a synthetic `eth_simulateV1` response to `_parse_response`, so the
65
65
  result is whatever `observe` would actually report -- including its handling
66
66
  of a decoder that raises."""
67
- sim = VaultSimulator(MagicMock(), EXECUTOR_ADDR, EXECUTOR_ADDR)
67
+ sim = VaultSimulator(MagicMock())
68
68
  sim.observe("slot", call)
69
69
  result = sim._parse_response(
70
70
  simulate_response(
@@ -0,0 +1,70 @@
1
+ """`FusionInstance.from_dict` parses a stored instance back into its typed
2
+ fields instead of passing raw JSON strings into role-typed addresses."""
3
+
4
+ from __future__ import annotations
5
+
6
+ from dataclasses import asdict
7
+
8
+ import pytest
9
+ from web3 import Web3
10
+
11
+ from ipor_fusion.core.fusion_factory import FusionInstance
12
+
13
+
14
+ def _stored() -> dict:
15
+ address = {
16
+ name: "0x" + f"{i:02x}" * 20
17
+ for i, name in enumerate(
18
+ [
19
+ "underlying_token",
20
+ "initial_owner",
21
+ "plasma_vault",
22
+ "plasma_vault_base",
23
+ "access_manager",
24
+ "fee_manager",
25
+ "rewards_manager",
26
+ "withdraw_manager",
27
+ "context_manager",
28
+ "price_manager",
29
+ ],
30
+ start=0xA1,
31
+ )
32
+ }
33
+ return {
34
+ "index": 3,
35
+ "version": 2,
36
+ "asset_name": "IPOR USDC",
37
+ "asset_symbol": "ipUSDC",
38
+ "asset_decimals": 8,
39
+ "underlying_token_symbol": "USDC",
40
+ "underlying_token_decimals": 6,
41
+ **address,
42
+ }
43
+
44
+
45
+ def test_from_dict_checksums_every_address_and_round_trips():
46
+ stored = _stored()
47
+ instance = FusionInstance.from_dict(stored)
48
+ assert instance.plasma_vault == Web3.to_checksum_address(stored["plasma_vault"])
49
+ assert instance.access_manager == Web3.to_checksum_address(stored["access_manager"])
50
+ assert instance.price_manager == Web3.to_checksum_address(stored["price_manager"])
51
+ assert FusionInstance.from_dict(asdict(instance)) == instance
52
+
53
+
54
+ @pytest.mark.parametrize(
55
+ ("change", "match"),
56
+ [
57
+ (lambda d: d.pop("plasma_vault"), "missing"),
58
+ (lambda d: d.update(extra=1), "extra"),
59
+ (lambda d: d.update(plasma_vault="0x1234"), "address"),
60
+ (lambda d: d.update(plasma_vault=" " + d["plasma_vault"]), "address"),
61
+ (lambda d: d.update(asset_decimals="8"), "integer"),
62
+ (lambda d: d.update(index=True), "integer"),
63
+ (lambda d: d.update(asset_symbol=None), "string"),
64
+ ],
65
+ )
66
+ def test_from_dict_rejects_a_malformed_field(change, match):
67
+ stored = _stored()
68
+ change(stored)
69
+ with pytest.raises(ValueError, match=match):
70
+ FusionInstance.from_dict(stored)
@@ -343,7 +343,6 @@ def test_simulate_contract_creation(web3_arb):
343
343
  init_code = bytes.fromhex("600a600c600039600a6000f3") + runtime
344
344
  simulator = VaultSimulator(
345
345
  web3_arb,
346
- vault=ZERO_ADDRESS,
347
346
  alpha=SIMULATED_DEPLOYER,
348
347
  block=ARBITRUM_BLOCK,
349
348
  )
@@ -374,7 +373,6 @@ def test_compile_and_deploy_crosschain_fuses(web3_arb):
374
373
  )
375
374
  simulator = VaultSimulator(
376
375
  web3_arb,
377
- vault=ZERO_ADDRESS,
378
376
  alpha=SIMULATED_DEPLOYER,
379
377
  block=ARBITRUM_BLOCK,
380
378
  )
@@ -804,7 +802,7 @@ def test_simulate_clone_htest_vault(
804
802
  OWNER,
805
803
  )
806
804
  preview = clone.call()
807
- simulator = VaultSimulator(web3, vault=ZERO_ADDRESS, alpha=OWNER, block=block)
805
+ simulator = VaultSimulator(web3, alpha=OWNER, block=block)
808
806
  simulator.with_block_override(gasLimit=30_000_000)
809
807
  simulator.add_call(clone, from_=OWNER, label="clone_vault")
810
808
  vault = PlasmaVault(ctx, preview.plasma_vault)