ipor-fusion 4.0.0.dev293__tar.gz → 4.0.0.dev303__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.
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/AGENTS.md +13 -5
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/MIGRATION_4.md +6 -2
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/PKG-INFO +22 -1
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/README.md +21 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/pyproject.toml +1 -1
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/__init__.py +42 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/cli/main.py +2 -0
- ipor_fusion-4.0.0.dev303/src/ipor_fusion/cli/preflight_cmd.py +128 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/core/__init__.py +12 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/core/access.py +22 -1
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/core/external_state_executor.py +46 -3
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/core/fuse.py +22 -0
- ipor_fusion-4.0.0.dev303/src/ipor_fusion/core/fuse_decoding.py +109 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/crosschain/fuses/base.py +2 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/crosschain/fuses/ccip.py +2 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/crosschain/fuses/stargate.py +2 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/errors.py +5 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/fuses/aave_v3.py +10 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/fuses/async_action.py +1 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/fuses/compound_v3.py +5 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/fuses/erc4626.py +5 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/fuses/euler_v2.py +36 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/fuses/external_state.py +6 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/fuses/fluid_instadapp.py +5 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/fuses/gearbox_v3.py +5 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/fuses/hypercore.py +18 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/fuses/merkl.py +2 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/fuses/morpho.py +19 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/fuses/ramses_v2.py +14 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/fuses/uniswap_v3.py +14 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/fuses/universal.py +6 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/mcp/models.py +63 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/mcp/server.py +49 -0
- ipor_fusion-4.0.0.dev303/src/ipor_fusion/preflight.py +464 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/types.py +2 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/conftest.py +18 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_access_manager.py +16 -0
- ipor_fusion-4.0.0.dev303/tests/test_cli_preflight.py +108 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_external_state_executor.py +30 -1
- ipor_fusion-4.0.0.dev303/tests/test_fuse_decoding.py +103 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_mcp_models.py +7 -0
- ipor_fusion-4.0.0.dev303/tests/test_mcp_preflight.py +86 -0
- ipor_fusion-4.0.0.dev303/tests/test_preflight.py +282 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_public_api.py +44 -2
- ipor_fusion-4.0.0.dev303/tests/test_simulate_preflight_base.py +46 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/.claude-plugin/marketplace.json +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/.claude-plugin/plugin.json +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/.env.example +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/.gemini/settings.json +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/.git-blame-ignore-revs +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/.github/workflows/cd.yml +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/.github/workflows/ci.yml +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/.github/workflows/commit-lint.yml +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/.github/workflows/notify-slack.yml +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/.github/workflows/python-build.yml +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/.github/workflows/python-publish.yml +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/.github/workflows/release.yml +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/.github/workflows/report-slack.yml +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/.gitignore +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/.mcp.json +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/.pre-commit-config.yaml +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/.python-version +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/CHANGELOG.md +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/CLAUDE.md +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/CODEOWNERS +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/LICENSE +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/build.sh +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/examples/CONTRIBUTING.md +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/examples/README.md +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/examples/advanced_euler_v2_credit_market_base.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/examples/advanced_euler_v2_looping_arbitrum.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/examples/composed_hypercore_flow_arbitrum_hyperevm.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/examples/crosschain_ccip_usdc_arbitrum_hyperevm.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/examples/external_state_margin_leg_base.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/examples/simple_aave_v3_supply_base.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/pytest.ini +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/scripts/commit-check.sh +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/scripts/crosschain_readiness.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/skills/ipor-deploy-vault/SKILL.md +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/about.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/cache.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/chains.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/cli/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/cli/address_cmd.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/cli/changelog_cmd.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/cli/config_cmd.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/cli/config_store.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/cli/market_cmd.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/cli/options.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/cli/oracle_mapping_view.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/cli/vault_cmd.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/cli/vault_health.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/cli/vault_info_view.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/cli/vault_markets_view.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/cli/vault_rendering.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/config/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/config/roles.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/core/address.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/core/context.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/core/context_manager.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/core/contract.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/core/erc20.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/core/fee_manager.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/core/fuse_action.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/core/fusion_factory.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/core/logs.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/core/multicall.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/core/oracle.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/core/plasma_vault.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/core/rewards_manager.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/core/simulation.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/core/simulation_result.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/core/withdraw_manager.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/crosschain/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/crosschain/ccip/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/crosschain/ccip/chainlink.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/crosschain/ccip/codec.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/crosschain/ccip/contracts.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/crosschain/ccip/events.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/crosschain/ccip/indexer.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/crosschain/ccip/lane.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/crosschain/ccip/quote.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/crosschain/ccip/transport.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/crosschain/contracts.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/crosschain/discovery.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/crosschain/errors.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/crosschain/fuses/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/crosschain/lane.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/crosschain/logs.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/crosschain/messages.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/crosschain/simulation.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/crosschain/stargate/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/crosschain/stargate/contracts.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/crosschain/stargate/lane.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/crosschain/stargate/layerzero.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/crosschain/stargate/transport.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/crosschain/transport.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/dependency_graph.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/entry_cli.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/entry_mcp.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/explorer.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/field_docs.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/fuses/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/fuses/base.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/fuses/events.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/fuses/external_state_event_decoding.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/fuses/hypercore_event_decoding.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/guide/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/guide/architecture.md +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/guide/glossary.md +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/guide/invariants.md +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/guide/quickstart.md +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/hypercore/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/market_ids.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/market_info.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/mcp/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/mcp/guide.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/morpho_api.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/py.typed +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/readers/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/readers/aave_v3.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/readers/aave_v4.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/readers/compound_v3.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/readers/hypercore.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/readers/hypercore_outcome.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/readers/lending_health.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/readers/morpho.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/readers/oracle_mapping.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/readers/oracle_mapping_nodes.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/readers/oracle_mapping_reader.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/readers/position_manager.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/readers/ramses_v2.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/readers/uniswap_v3.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/readers/unpriceable.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/registry.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/substrates.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/validation.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/vault_data.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/vault_erc20.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/vault_info.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/vault_managers.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/vault_positions.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/vault_reads.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/UniswapV3UniversalRouter.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/_crosschain.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/_crosschain_lifecycle.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/_crosschain_recovery.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/_euler_v2.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/_event_args.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/_fake_rpc.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/_foundry.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/_golden.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/_guide.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/_hypercore_model.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/_hypercore_shadow.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/_live.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/_multicall.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/_oracle_mapping_fake.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/_simulate.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/_validation.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/addresses.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/constants.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/fixtures/hypercore_hip3_flow.json +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/fixtures/hypercore_market55.json +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/fixtures/hypercore_official.json +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/fixtures/market_golden/meta_morpho_v1.json +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/fixtures/market_golden/meta_morpho_v1.mcp.json +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/fixtures/market_golden/meta_morpho_v2.json +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/fixtures/market_golden/meta_morpho_v2.mcp.json +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/fixtures/market_golden/morpho_api/market.json +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/fixtures/market_golden/morpho_api/vault_v1.json +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/fixtures/market_golden/morpho_api/vault_v2.json +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/fixtures/market_golden/morpho_api/vault_v2_not_found.json +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/fixtures/market_golden/morpho_blue.json +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/fixtures/market_golden/morpho_blue.mcp.json +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/fixtures/market_golden/morpho_blue_api_error.json +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/fixtures/market_golden/morpho_blue_api_error.mcp.json +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/fixtures/market_golden/morpho_blue_no_api.json +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/fixtures/market_golden/morpho_blue_no_api.mcp.json +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/fixtures/oracle_mapping_golden/mapping.json +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/fixtures/oracle_mapping_golden/mapping.mcp.json +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/fixtures/simulate_v1_request.json +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/fixtures/vault_info_golden/clean.json +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/fixtures/vault_info_golden/clean.mcp.json +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/fixtures/vault_info_golden/clean.txt +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/fixtures/vault_info_golden/deployment_error.json +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/fixtures/vault_info_golden/deployment_error.mcp.json +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/fixtures/vault_info_golden/deployment_error.txt +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/fixtures/vault_info_golden/fetched.cli.json +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/fixtures/vault_info_golden/fetched.json +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/fixtures/vault_info_golden/fetched.mcp.json +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/fixtures/vault_info_golden/full.json +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/fixtures/vault_info_golden/full.mcp.json +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/fixtures/vault_info_golden/full.txt +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/fixtures/vault_info_golden/rich.json +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/fixtures/vault_info_golden/rich.mcp.json +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/fixtures/vault_info_golden/rich.txt +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_about.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_cache.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_chains.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_cli_address.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_cli_changelog.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_cli_commands.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_cli_config_store.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_cli_helpers.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_cli_hypercore.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_cli_lending_health.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_cli_market_golden.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_cli_market_morpho_blue.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_cli_mcp_parity.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_cli_oracle_mapping.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_cli_oracle_mapping_golden.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_cli_pending_requests_scan.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_cli_role_accounts.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_cli_vault_info_golden.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_cli_vault_predates_getters.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_cli_vault_validate.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_contract_call.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_contract_encoder.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_crosschain_ccip_events.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_crosschain_ccip_indexer.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_crosschain_ccip_quote.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_crosschain_chainlink.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_crosschain_contracts.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_crosschain_encoding.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_crosschain_errors.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_crosschain_lane.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_crosschain_layering.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_crosschain_readiness.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_crosschain_readiness_report.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_crosschain_transport.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_docs_typing.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_erc20.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_error_hierarchy.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_examples_composed_hypercore.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_examples_crosschain.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_examples_euler_looping_arbitrum.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_examples_vaults.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_explorer.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_external_state_events.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_fee_manager.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_fuse_encoding.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_fusion_instance.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_governance_wrappers.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_guide.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_hypercore_deployment.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_hypercore_events.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_hypercore_flow.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_hypercore_live.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_hypercore_outcome.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_hypercore_readers.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_hypercore_simulation.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_logs.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_mcp_address.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_mcp_hypercore.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_mcp_server.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_mcp_vault_validate.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_multicall.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_oracle.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_oracle_mapping.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_plasma_vault.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_public_type_reachability.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_reader_encoding.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_readiness_selection.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_registry.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_revert_decoding.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_rewards_manager.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_roles.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_sdk_layering.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_simulate_borrow_aave_v3_mainnet.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_simulate_claim_merkl_wrapper_base.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_simulate_claim_morpho_rewards.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_simulate_crosschain_canary.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_simulate_crosschain_hypercore.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_simulate_crosschain_hyperevm.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_simulate_crosschain_lifecycle.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_simulate_euler_v2_batch_base.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_simulate_euler_v2_credit_market_base.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_simulate_euler_v2_swap_deploy_base.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_simulate_euler_v2_swap_reconfigure_base.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_simulate_euler_v2_swap_registry_base.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_simulate_examples_arbitrum.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_simulate_examples_base.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_simulate_examples_crosschain.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_simulate_external_state_executor_base.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_simulate_fusion_factory_clone_base.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_simulate_looping_aave_v3_base.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_simulate_looping_morpho_blue_base.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_simulate_morpho_blue.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_simulate_pilot_v3.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_simulate_pilot_v4_uniswap.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_simulate_pilot_v5_ramses.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_simulate_pilot_v5_scheduled.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_simulate_plasma_vault_setters_base.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_simulate_readers_integration.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_simulate_release_funds.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_simulate_swap_on_base.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_simulate_vault_from_scratch_base.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_simulation_errors.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_simulation_observation.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_solidity_compiler.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_solidity_mirrors.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_substrates.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_types_and_events.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_unpriceable_tokens.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_validation.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_vault_data_block.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_vault_dep_graph.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_vault_info_fetched_golden.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_vault_info_onchain_base.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_vault_info_onchain_base_no_withdraw_manager.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_vault_reconciliation_onchain_base.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_vault_simulator_blocks.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_web3_context.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_withdraw_manager.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/typed_client/consumer.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/typed_client/rejected.py +0 -0
- {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/uv.lock +0 -0
|
@@ -207,7 +207,7 @@ the lifecycle matrix follows that.
|
|
|
207
207
|
subpackages do not list them). Their exceptions stay in `errors` and the top
|
|
208
208
|
level. `ipor_fusion.guide` keeps its own namespace. The
|
|
209
209
|
public modules (`about`, `cache`, `chains`, `dependency_graph`, `errors`, `explorer`,
|
|
210
|
-
`field_docs`, `market_ids`, `market_info`, `morpho_api`, `registry`, `substrates`, `types`,
|
|
210
|
+
`field_docs`, `market_ids`, `market_info`, `morpho_api`, `preflight`, `registry`, `substrates`, `types`,
|
|
211
211
|
`validation`, `vault_data`, `vault_info`) stay importable for the names they
|
|
212
212
|
define and the moved names README lists; `cli`, `mcp`, every other module and
|
|
213
213
|
every submodule of a public subpackage (`core.contract`, `fuses.base`, ...) are
|
|
@@ -268,6 +268,9 @@ the lifecycle matrix follows that.
|
|
|
268
268
|
`ctx.cache` (`Web3Context(cache=...)`, default `default_contract_cache()` under
|
|
269
269
|
`$XDG_CACHE_HOME/ipor-fusion/`) or `get_contract_name(cache=...)`. Never cache an
|
|
270
270
|
absence (no code, no answer): it can change at a later block.
|
|
271
|
+
`preflight.py` — `preflight_execute`: an execute batch checked as its sender would
|
|
272
|
+
send it (canCall, registered fuses, MARKET_ID, balance fuses, one eth_simulateV1
|
|
273
|
+
run), `ExecutePreflight` and the `fusion vault preflight` document;
|
|
271
274
|
`registry.py` the ipor-abi address registry (`fetch_address_registry` →
|
|
272
275
|
`AddressRegistry`, one raw-GitHub file per chain in `REGISTRY_CHAIN_IDS`, cached ten
|
|
273
276
|
minutes in the process, for good at a commit ref) and the `fusion address` documents;
|
|
@@ -295,7 +298,9 @@ the lifecycle matrix follows that.
|
|
|
295
298
|
of the node's response), `oracle`, `fusion_factory`,
|
|
296
299
|
`external_state_executor` (NAV marks for market 50), `erc20`, `fuse_action`
|
|
297
300
|
(`FuseAction`), `fuse` (the `Fuse` base, `StakeFuse`, the shared validators and
|
|
298
|
-
substrate packers), `
|
|
301
|
+
substrate packers), `fuse_decoding` (`decode_execute_calldata`, `decode_fuse_action`,
|
|
302
|
+
`fuse_classes_for`: an action back to the function its class encoded, from each
|
|
303
|
+
class's `SIGNATURES`), `address` (`_validate_not_zero_address`). `core` imports neither
|
|
299
304
|
`fuses` nor `crosschain` (`tests/test_sdk_layering.py`)
|
|
300
305
|
- `fuses/` — per-protocol fuse encoders (aave_v3, async_action, compound_v3,
|
|
301
306
|
erc4626, euler_v2, external_state, fluid_instadapp, gearbox_v3, merkl, morpho, ramses_v2,
|
|
@@ -459,7 +464,10 @@ or examples.
|
|
|
459
464
|
`ipor-abi` `addresses.json`; a fuse must be registered on the vault
|
|
460
465
|
(`PlasmaVault.get_fuses()`) and its market granted substrates before an
|
|
461
466
|
action can succeed.
|
|
462
|
-
- Adding a protocol: new module in `fuses
|
|
467
|
+
- Adding a protocol: new module in `fuses/` whose classes list every Solidity
|
|
468
|
+
function they encode in `SIGNATURES` (`tests/conftest.py` fails any call an
|
|
469
|
+
encoder builds under test that its class does not declare; it cannot catch an
|
|
470
|
+
extra or never-exercised entry), re-export from `fuses/__init__.py`
|
|
463
471
|
and the top-level `__all__` (and both snapshots in `test_public_api.py`),
|
|
464
472
|
encoding test in `test_fuse_encoding.py`, and a row in the README protocol
|
|
465
473
|
table.
|
|
@@ -497,7 +505,7 @@ byte, e.g. Ebisu ZAPPER/REGISTRY).
|
|
|
497
505
|
`fusion address` (lookup, names, list; the ipor-abi registry through `registry.py`),
|
|
498
506
|
`fusion config` (set-provider, set-etherscan-key, show), `fusion market`
|
|
499
507
|
(morpho-blue, meta-morpho), `fusion vault` (add, remove, list, info, validate,
|
|
500
|
-
role-accounts, oracle-mapping). `vault info` runs independent fetch pipelines
|
|
508
|
+
role-accounts, oracle-mapping, preflight). `vault info` runs independent fetch pipelines
|
|
501
509
|
(vault/asset, fees, WithdrawManager, markets, roles) concurrently on a
|
|
502
510
|
`ThreadPoolExecutor`, each a short chain of `Multicall3` batches; keep new
|
|
503
511
|
reads inside a batch rather than adding per-call round trips. A `from_url` context
|
|
@@ -510,7 +518,7 @@ keys); tests redirect it with `monkeypatch.setenv("XDG_CACHE_HOME", ...)` or
|
|
|
510
518
|
inject a cache.
|
|
511
519
|
|
|
512
520
|
Every CLI command has an MCP tool (`fusion changelog` maps to `server_info`;
|
|
513
|
-
the rest share names). Read-only: `vault_info`, `vault_validate`,
|
|
521
|
+
the rest share names). Read-only: `vault_info`, `vault_validate`, `vault_preflight`,
|
|
514
522
|
`vault_list`, `vault_role_accounts`, `vault_oracle_mapping`, `market_morpho_blue`,
|
|
515
523
|
`market_meta_morpho`, `address_lookup`, `address_names`, `address_list`,
|
|
516
524
|
`config_show`, `server_info`. Mutating: `vault_add`,
|
|
@@ -438,8 +438,12 @@ exist; the comparison is equality either way.
|
|
|
438
438
|
- `ExternalStateVaultState.last_checked_custodian_timestamp` is
|
|
439
439
|
`last_checked_custodian_marker` and `BalanceFuseSnapshot.last_custodian_update_timestamp`
|
|
440
440
|
is `custodian_marker`, both a `CustodianMarker` (an opaque value the
|
|
441
|
-
contracts only compare for equality): a timestamp on an earlier
|
|
442
|
-
|
|
441
|
+
contracts only compare for equality): a unix timestamp on an earlier
|
|
442
|
+
executor, a confirmation count on a released one.
|
|
443
|
+
`ExternalStateVaultState.custodian_marker_kind` (`CustodianMarkerKind`, read
|
|
444
|
+
from the executor at the snapshot block; `None` without an executor) and
|
|
445
|
+
`ExternalStateExecutor.read_custodian_marker_kind(ctx)` say which; never
|
|
446
|
+
compare a count with a clock. `ExternalStateExecutor.confirm_count()`
|
|
443
447
|
exists only on the released executor (it reverts on an earlier one), and
|
|
444
448
|
`last_custodian_update_timestamp()` reads the timestamp on both.
|
|
445
449
|
- New on the released build: `ExternalStateExecutor.unforwarded_credit(asset)`,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: ipor-fusion
|
|
3
|
-
Version: 4.0.0.
|
|
3
|
+
Version: 4.0.0.dev303
|
|
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
|
|
@@ -201,6 +201,23 @@ if result.all_success:
|
|
|
201
201
|
print(after.value - before.value)
|
|
202
202
|
```
|
|
203
203
|
|
|
204
|
+
Read a batch back from calldata, e.g. to review a multisig transaction:
|
|
205
|
+
|
|
206
|
+
```python
|
|
207
|
+
from ipor_fusion import AaveV3SupplyFuse, decode_execute_calldata, decode_fuse_action, fuse_classes_for
|
|
208
|
+
|
|
209
|
+
# The class behind each fuse address you know (from the registry or your config);
|
|
210
|
+
# fuse_classes_for(action) lists the candidates when you do not.
|
|
211
|
+
known = {fuse.address: AaveV3SupplyFuse}
|
|
212
|
+
for action in decode_execute_calldata(payload): # execute / claimRewards calldata
|
|
213
|
+
cls = known.get(action.fuse)
|
|
214
|
+
if cls is None:
|
|
215
|
+
print(action.fuse, "one of", [c.__name__ for c in fuse_classes_for(action)])
|
|
216
|
+
continue
|
|
217
|
+
decoded = decode_fuse_action(action, cls)
|
|
218
|
+
print(decoded.label, decoded.args) # AaveV3SupplyFuse.enter ((asset, amount, e_mode),)
|
|
219
|
+
```
|
|
220
|
+
|
|
204
221
|
## CLI Quickstart
|
|
205
222
|
|
|
206
223
|
The SDK ships with a `fusion` CLI for inspecting and managing Plasma Vaults from the terminal.
|
|
@@ -223,6 +240,9 @@ fusion vault validate 0xB8a451107A9f87FDe481D4D686247D6e43Ed715e --chain-id ethe
|
|
|
223
240
|
# List saved vaults
|
|
224
241
|
fusion vault list
|
|
225
242
|
|
|
243
|
+
# Check an execute batch before signing it (role, fuses, balance fuses, simulation)
|
|
244
|
+
fusion vault preflight 0xVAULT_ADDRESS --calldata 0xEXECUTE_CALLDATA --sender 0xALPHA
|
|
245
|
+
|
|
226
246
|
# Inspect a Morpho Blue market or MetaMorpho vault
|
|
227
247
|
fusion market morpho-blue 0xMARKET_ID --chain ethereum
|
|
228
248
|
fusion market meta-morpho 0xVAULT_ADDRESS --chain ethereum
|
|
@@ -289,6 +309,7 @@ Available tools:
|
|
|
289
309
|
| `config_set_etherscan_key` | Set Etherscan API key (enables contract name resolution) |
|
|
290
310
|
| `vault_info` | Full on-chain vault state — assets, fuses, balances, fees, lending health, reconciliation |
|
|
291
311
|
| `vault_validate` | Health verdict (ok / warning / critical) with the reconciliation, lending-health and ERC20 numbers behind it |
|
|
312
|
+
| `vault_preflight` | Check execute calldata as its sender would send it: permission, fuses, balance fuses, simulation |
|
|
292
313
|
| `vault_role_accounts` | Accounts holding each AccessManager role on a vault |
|
|
293
314
|
| `vault_oracle_mapping` | Price-oracle sources per asset for a vault |
|
|
294
315
|
| `vault_list` | List all saved vaults |
|
|
@@ -152,6 +152,23 @@ if result.all_success:
|
|
|
152
152
|
print(after.value - before.value)
|
|
153
153
|
```
|
|
154
154
|
|
|
155
|
+
Read a batch back from calldata, e.g. to review a multisig transaction:
|
|
156
|
+
|
|
157
|
+
```python
|
|
158
|
+
from ipor_fusion import AaveV3SupplyFuse, decode_execute_calldata, decode_fuse_action, fuse_classes_for
|
|
159
|
+
|
|
160
|
+
# The class behind each fuse address you know (from the registry or your config);
|
|
161
|
+
# fuse_classes_for(action) lists the candidates when you do not.
|
|
162
|
+
known = {fuse.address: AaveV3SupplyFuse}
|
|
163
|
+
for action in decode_execute_calldata(payload): # execute / claimRewards calldata
|
|
164
|
+
cls = known.get(action.fuse)
|
|
165
|
+
if cls is None:
|
|
166
|
+
print(action.fuse, "one of", [c.__name__ for c in fuse_classes_for(action)])
|
|
167
|
+
continue
|
|
168
|
+
decoded = decode_fuse_action(action, cls)
|
|
169
|
+
print(decoded.label, decoded.args) # AaveV3SupplyFuse.enter ((asset, amount, e_mode),)
|
|
170
|
+
```
|
|
171
|
+
|
|
155
172
|
## CLI Quickstart
|
|
156
173
|
|
|
157
174
|
The SDK ships with a `fusion` CLI for inspecting and managing Plasma Vaults from the terminal.
|
|
@@ -174,6 +191,9 @@ fusion vault validate 0xB8a451107A9f87FDe481D4D686247D6e43Ed715e --chain-id ethe
|
|
|
174
191
|
# List saved vaults
|
|
175
192
|
fusion vault list
|
|
176
193
|
|
|
194
|
+
# Check an execute batch before signing it (role, fuses, balance fuses, simulation)
|
|
195
|
+
fusion vault preflight 0xVAULT_ADDRESS --calldata 0xEXECUTE_CALLDATA --sender 0xALPHA
|
|
196
|
+
|
|
177
197
|
# Inspect a Morpho Blue market or MetaMorpho vault
|
|
178
198
|
fusion market morpho-blue 0xMARKET_ID --chain ethereum
|
|
179
199
|
fusion market meta-morpho 0xVAULT_ADDRESS --chain ethereum
|
|
@@ -240,6 +260,7 @@ Available tools:
|
|
|
240
260
|
| `config_set_etherscan_key` | Set Etherscan API key (enables contract name resolution) |
|
|
241
261
|
| `vault_info` | Full on-chain vault state — assets, fuses, balances, fees, lending health, reconciliation |
|
|
242
262
|
| `vault_validate` | Health verdict (ok / warning / critical) with the reconciliation, lending-health and ERC20 numbers behind it |
|
|
263
|
+
| `vault_preflight` | Check execute calldata as its sender would send it: permission, fuses, balance fuses, simulation |
|
|
243
264
|
| `vault_role_accounts` | Accounts holding each AccessManager role on a vault |
|
|
244
265
|
| `vault_oracle_mapping` | Price-oracle sources per asset for a vault |
|
|
245
266
|
| `vault_list` | List all saved vaults |
|
|
@@ -42,6 +42,7 @@ from ipor_fusion.core.erc20 import ERC20
|
|
|
42
42
|
from ipor_fusion.core.external_state_executor import (
|
|
43
43
|
BalanceFuseSnapshot,
|
|
44
44
|
BalanceProposal,
|
|
45
|
+
CustodianMarkerKind,
|
|
45
46
|
ExternalStateExecutor,
|
|
46
47
|
ExternalStateVaultState,
|
|
47
48
|
read_external_state,
|
|
@@ -52,6 +53,12 @@ from ipor_fusion.core.fee_manager import (
|
|
|
52
53
|
HighWaterMarkPerformanceFee,
|
|
53
54
|
RecipientFee,
|
|
54
55
|
)
|
|
56
|
+
from ipor_fusion.core.fuse_decoding import (
|
|
57
|
+
DecodedFuseAction,
|
|
58
|
+
decode_execute_calldata,
|
|
59
|
+
decode_fuse_action,
|
|
60
|
+
fuse_classes_for,
|
|
61
|
+
)
|
|
55
62
|
from ipor_fusion.core.fusion_factory import CloneArgs, FusionFactory, FusionInstance
|
|
56
63
|
from ipor_fusion.core.logs import (
|
|
57
64
|
LogSource,
|
|
@@ -113,6 +120,7 @@ from ipor_fusion.errors import (
|
|
|
113
120
|
ContractNotFoundError,
|
|
114
121
|
CrosschainDiscoveryError,
|
|
115
122
|
EmptyCallResultError,
|
|
123
|
+
ExecutePreflightError,
|
|
116
124
|
ExternalStateError,
|
|
117
125
|
ExternalStateEventDecodeError,
|
|
118
126
|
HyperCoreEventDecodeError,
|
|
@@ -255,6 +263,20 @@ from ipor_fusion.morpho_api import (
|
|
|
255
263
|
fetch_market,
|
|
256
264
|
fetch_vault,
|
|
257
265
|
)
|
|
266
|
+
from ipor_fusion.preflight import (
|
|
267
|
+
ExecutePreflight,
|
|
268
|
+
ExecutePreflightJson,
|
|
269
|
+
PreflightAction,
|
|
270
|
+
PreflightActionJson,
|
|
271
|
+
PreflightCheck,
|
|
272
|
+
PreflightFinding,
|
|
273
|
+
PreflightFindingJson,
|
|
274
|
+
PreflightSimulation,
|
|
275
|
+
PreflightSimulationJson,
|
|
276
|
+
build_execute_preflight_json,
|
|
277
|
+
parse_execute_actions,
|
|
278
|
+
preflight_execute,
|
|
279
|
+
)
|
|
258
280
|
from ipor_fusion.readers import (
|
|
259
281
|
HYPEREVM_CHAIN_ID,
|
|
260
282
|
AaveV3FuseReader,
|
|
@@ -351,6 +373,7 @@ from ipor_fusion.types import (
|
|
|
351
373
|
Price,
|
|
352
374
|
ProposalHash,
|
|
353
375
|
RoleId,
|
|
376
|
+
ScheduleId,
|
|
354
377
|
Severity,
|
|
355
378
|
Shares,
|
|
356
379
|
Tick,
|
|
@@ -477,6 +500,23 @@ __all__ = [
|
|
|
477
500
|
"repository_url",
|
|
478
501
|
"Web3Context",
|
|
479
502
|
"InFlightLimit",
|
|
503
|
+
"ExecutePreflightError",
|
|
504
|
+
"ExecutePreflight",
|
|
505
|
+
"ExecutePreflightJson",
|
|
506
|
+
"PreflightAction",
|
|
507
|
+
"PreflightActionJson",
|
|
508
|
+
"PreflightCheck",
|
|
509
|
+
"PreflightFinding",
|
|
510
|
+
"PreflightFindingJson",
|
|
511
|
+
"PreflightSimulation",
|
|
512
|
+
"PreflightSimulationJson",
|
|
513
|
+
"build_execute_preflight_json",
|
|
514
|
+
"parse_execute_actions",
|
|
515
|
+
"preflight_execute",
|
|
516
|
+
"DecodedFuseAction",
|
|
517
|
+
"decode_execute_calldata",
|
|
518
|
+
"decode_fuse_action",
|
|
519
|
+
"fuse_classes_for",
|
|
480
520
|
"AddressNotInRegistryError",
|
|
481
521
|
"AddressRegistryError",
|
|
482
522
|
"REGISTRY_CHAIN_IDS",
|
|
@@ -545,6 +585,7 @@ __all__ = [
|
|
|
545
585
|
"CloneArgs",
|
|
546
586
|
"ExternalStateExecutor",
|
|
547
587
|
"BalanceFuseSnapshot",
|
|
588
|
+
"CustodianMarkerKind",
|
|
548
589
|
"ExternalStateVaultState",
|
|
549
590
|
"read_external_state",
|
|
550
591
|
"erc20_balance_slot",
|
|
@@ -655,6 +696,7 @@ __all__ = [
|
|
|
655
696
|
"Liquidity",
|
|
656
697
|
"RoleId",
|
|
657
698
|
"ChainId",
|
|
699
|
+
"ScheduleId",
|
|
658
700
|
"Wad",
|
|
659
701
|
"MorphoBlueMarketId",
|
|
660
702
|
"ProposalHash",
|
|
@@ -9,6 +9,7 @@ from ipor_fusion.cli.address_cmd import address
|
|
|
9
9
|
from ipor_fusion.cli.changelog_cmd import changelog
|
|
10
10
|
from ipor_fusion.cli.config_cmd import config
|
|
11
11
|
from ipor_fusion.cli.market_cmd import market
|
|
12
|
+
from ipor_fusion.cli.preflight_cmd import preflight
|
|
12
13
|
from ipor_fusion.cli.vault_cmd import vault
|
|
13
14
|
|
|
14
15
|
|
|
@@ -35,6 +36,7 @@ cli.add_command(changelog)
|
|
|
35
36
|
cli.add_command(config)
|
|
36
37
|
cli.add_command(market)
|
|
37
38
|
cli.add_command(vault)
|
|
39
|
+
vault.add_command(preflight)
|
|
38
40
|
|
|
39
41
|
|
|
40
42
|
def main() -> None:
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"""`fusion vault preflight`: check an execute batch before sending it."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import sys
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
import click
|
|
10
|
+
from eth_typing import ChecksumAddress
|
|
11
|
+
|
|
12
|
+
from ipor_fusion.cli.config_store import load_config
|
|
13
|
+
from ipor_fusion.cli.options import (
|
|
14
|
+
ADDRESS,
|
|
15
|
+
BLOCK_NUMBER,
|
|
16
|
+
CHAIN,
|
|
17
|
+
VAULT_ADDRESS,
|
|
18
|
+
_build_ctx,
|
|
19
|
+
)
|
|
20
|
+
from ipor_fusion.cli.vault_rendering import _print_table
|
|
21
|
+
from ipor_fusion.preflight import (
|
|
22
|
+
ExecutePreflight,
|
|
23
|
+
build_execute_preflight_json,
|
|
24
|
+
parse_execute_actions,
|
|
25
|
+
preflight_execute,
|
|
26
|
+
)
|
|
27
|
+
from ipor_fusion.types import ChainId, PlasmaVaultAddress
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _read_calldata(calldata: str) -> str:
|
|
31
|
+
if calldata.startswith("@"):
|
|
32
|
+
try:
|
|
33
|
+
return Path(calldata[1:]).read_text()
|
|
34
|
+
except OSError as exc:
|
|
35
|
+
raise click.BadParameter(str(exc), param_hint="--calldata") from exc
|
|
36
|
+
return calldata
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@click.command("preflight")
|
|
40
|
+
@click.argument("vault_address", type=VAULT_ADDRESS)
|
|
41
|
+
@click.option(
|
|
42
|
+
"--calldata",
|
|
43
|
+
required=True,
|
|
44
|
+
help="execute((address,bytes)[]) calldata as hex, or @file holding it.",
|
|
45
|
+
)
|
|
46
|
+
@click.option(
|
|
47
|
+
"--sender",
|
|
48
|
+
type=ADDRESS,
|
|
49
|
+
required=True,
|
|
50
|
+
help="The account that will send execute (an ALPHA).",
|
|
51
|
+
)
|
|
52
|
+
@click.option(
|
|
53
|
+
"--chain-id", type=CHAIN, default=None, help="Chain ID or name (e.g. 1, ethereum)."
|
|
54
|
+
)
|
|
55
|
+
@click.option(
|
|
56
|
+
"--block-number", type=BLOCK_NUMBER, default=None, help="Block (default: latest)."
|
|
57
|
+
)
|
|
58
|
+
@click.option(
|
|
59
|
+
"--simulate/--no-simulate",
|
|
60
|
+
default=True,
|
|
61
|
+
help="Run the batch through eth_simulateV1 (default) or only the static checks.",
|
|
62
|
+
)
|
|
63
|
+
@click.option(
|
|
64
|
+
"--json", "json_output", is_flag=True, default=False, help="Output as JSON."
|
|
65
|
+
)
|
|
66
|
+
def preflight(
|
|
67
|
+
vault_address: PlasmaVaultAddress,
|
|
68
|
+
calldata: str,
|
|
69
|
+
sender: ChecksumAddress,
|
|
70
|
+
chain_id: ChainId | None,
|
|
71
|
+
block_number: int | None,
|
|
72
|
+
simulate: bool,
|
|
73
|
+
json_output: bool,
|
|
74
|
+
) -> None:
|
|
75
|
+
"""Check an execute batch as SENDER would send it: role, fuse registration,
|
|
76
|
+
balance fuses, and a simulation of the exact calldata. Read-only. Exits with
|
|
77
|
+
status 1 when a check is critical."""
|
|
78
|
+
try:
|
|
79
|
+
actions = parse_execute_actions(_read_calldata(calldata))
|
|
80
|
+
except ValueError as exc:
|
|
81
|
+
raise click.BadParameter(str(exc), param_hint="--calldata") from exc
|
|
82
|
+
if not actions:
|
|
83
|
+
raise click.BadParameter("the batch has no actions", param_hint="--calldata")
|
|
84
|
+
_, ctx = _build_ctx(load_config(), vault_address, chain_id, block_number)
|
|
85
|
+
result = preflight_execute(
|
|
86
|
+
ctx, vault_address, actions, sender=sender, simulate=simulate
|
|
87
|
+
)
|
|
88
|
+
if json_output:
|
|
89
|
+
click.echo(json.dumps(build_execute_preflight_json(result), indent=2))
|
|
90
|
+
else:
|
|
91
|
+
_print_preflight(result)
|
|
92
|
+
if result.verdict == "critical":
|
|
93
|
+
sys.exit(1)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _print_preflight(result: ExecutePreflight) -> None:
|
|
97
|
+
click.echo(f"Vault: {result.vault}")
|
|
98
|
+
click.echo(f"Block: {result.block_number}")
|
|
99
|
+
click.echo(f"Sender: {result.sender}")
|
|
100
|
+
click.echo(f"Verdict: {result.verdict}")
|
|
101
|
+
click.echo("\nActions")
|
|
102
|
+
_print_table(
|
|
103
|
+
("#", "Fuse", "Registered", "Market", "Encoder"),
|
|
104
|
+
[
|
|
105
|
+
(
|
|
106
|
+
str(a.index),
|
|
107
|
+
a.fuse,
|
|
108
|
+
"yes" if a.registered else "NO",
|
|
109
|
+
"?" if a.market_id is None else str(a.market_id),
|
|
110
|
+
", ".join(a.candidates) or "unknown",
|
|
111
|
+
)
|
|
112
|
+
for a in result.actions
|
|
113
|
+
],
|
|
114
|
+
)
|
|
115
|
+
click.echo("\nFindings")
|
|
116
|
+
_print_table(
|
|
117
|
+
("Severity", "Check", "Message"),
|
|
118
|
+
[(f.severity, f.check, f.message) for f in result.findings],
|
|
119
|
+
)
|
|
120
|
+
if result.simulation is not None:
|
|
121
|
+
sim = result.simulation
|
|
122
|
+
click.echo("\nSimulation")
|
|
123
|
+
click.echo(f" success: {sim.success} gas: {sim.gas_used}")
|
|
124
|
+
click.echo(
|
|
125
|
+
f" totalAssets: {sim.total_assets_before} -> {sim.total_assets_after}"
|
|
126
|
+
)
|
|
127
|
+
if sim.revert_reason:
|
|
128
|
+
click.echo(f" revert: {sim.revert_reason}")
|
|
@@ -18,6 +18,7 @@ from ipor_fusion.core.erc20 import ERC20
|
|
|
18
18
|
from ipor_fusion.core.external_state_executor import (
|
|
19
19
|
BalanceFuseSnapshot,
|
|
20
20
|
BalanceProposal,
|
|
21
|
+
CustodianMarkerKind,
|
|
21
22
|
ExternalStateExecutor,
|
|
22
23
|
ExternalStateVaultState,
|
|
23
24
|
read_external_state,
|
|
@@ -35,6 +36,12 @@ from ipor_fusion.core.fuse import (
|
|
|
35
36
|
from ipor_fusion.core.fuse_action import (
|
|
36
37
|
FuseAction,
|
|
37
38
|
)
|
|
39
|
+
from ipor_fusion.core.fuse_decoding import (
|
|
40
|
+
DecodedFuseAction,
|
|
41
|
+
decode_execute_calldata,
|
|
42
|
+
decode_fuse_action,
|
|
43
|
+
fuse_classes_for,
|
|
44
|
+
)
|
|
38
45
|
from ipor_fusion.core.fusion_factory import CloneArgs, FusionFactory, FusionInstance
|
|
39
46
|
from ipor_fusion.core.logs import (
|
|
40
47
|
LogSource,
|
|
@@ -86,6 +93,10 @@ from ipor_fusion.core.withdraw_manager import (
|
|
|
86
93
|
__all__ = [
|
|
87
94
|
"Web3Context",
|
|
88
95
|
"InFlightLimit",
|
|
96
|
+
"DecodedFuseAction",
|
|
97
|
+
"decode_execute_calldata",
|
|
98
|
+
"decode_fuse_action",
|
|
99
|
+
"fuse_classes_for",
|
|
89
100
|
"ContextManager",
|
|
90
101
|
"Multicall3",
|
|
91
102
|
"Multicall3Batch",
|
|
@@ -122,6 +133,7 @@ __all__ = [
|
|
|
122
133
|
"BalanceFuseSnapshot",
|
|
123
134
|
"BalanceProposal",
|
|
124
135
|
"ExternalStateExecutor",
|
|
136
|
+
"CustodianMarkerKind",
|
|
125
137
|
"ExternalStateVaultState",
|
|
126
138
|
"read_external_state",
|
|
127
139
|
"Fuse",
|
|
@@ -2,7 +2,7 @@ from collections.abc import Callable, Sequence
|
|
|
2
2
|
from dataclasses import dataclass
|
|
3
3
|
from typing import TypedDict
|
|
4
4
|
|
|
5
|
-
from eth_abi import decode
|
|
5
|
+
from eth_abi import decode, encode
|
|
6
6
|
from eth_abi.exceptions import InsufficientDataBytes
|
|
7
7
|
from eth_typing import ChecksumAddress
|
|
8
8
|
from hexbytes import HexBytes
|
|
@@ -27,6 +27,7 @@ from ipor_fusion.types import (
|
|
|
27
27
|
Period,
|
|
28
28
|
PlasmaVaultAddress,
|
|
29
29
|
RoleId,
|
|
30
|
+
ScheduleId,
|
|
30
31
|
)
|
|
31
32
|
|
|
32
33
|
|
|
@@ -183,6 +184,26 @@ class AccessManager(ContractWrapper):
|
|
|
183
184
|
decoder=_role_status_decoder,
|
|
184
185
|
)
|
|
185
186
|
|
|
187
|
+
def get_schedule(self, operation: ScheduleId) -> Call[Timestamp]:
|
|
188
|
+
"""When the scheduled ``operation`` becomes executable; 0 when it is not
|
|
189
|
+
scheduled, was executed or cancelled, or expired (a week after that time)."""
|
|
190
|
+
return self._view(
|
|
191
|
+
"getSchedule(bytes32)",
|
|
192
|
+
operation,
|
|
193
|
+
output_types=["uint48"],
|
|
194
|
+
decoder=Timestamp,
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
@staticmethod
|
|
198
|
+
def hash_operation(
|
|
199
|
+
caller: ChecksumAddress, target: ChecksumAddress, data: bytes
|
|
200
|
+
) -> ScheduleId:
|
|
201
|
+
"""The id `schedule` and `get_schedule` use for ``caller`` calling
|
|
202
|
+
``target`` with ``data``, computed offline as the manager does."""
|
|
203
|
+
return ScheduleId(
|
|
204
|
+
Web3.keccak(encode(["address", "address", "bytes"], [caller, target, data]))
|
|
205
|
+
)
|
|
206
|
+
|
|
186
207
|
def can_call(
|
|
187
208
|
self, caller: ChecksumAddress, target: ChecksumAddress, selector: bytes
|
|
188
209
|
) -> Call[CallPermission]:
|
|
@@ -18,13 +18,15 @@ from __future__ import annotations
|
|
|
18
18
|
|
|
19
19
|
from collections.abc import Iterable
|
|
20
20
|
from dataclasses import dataclass, replace
|
|
21
|
+
from enum import Enum
|
|
21
22
|
|
|
22
23
|
from eth_abi import decode, encode
|
|
23
24
|
from eth_abi.exceptions import DecodingError
|
|
24
25
|
from eth_typing import ChecksumAddress
|
|
25
|
-
from eth_utils import keccak
|
|
26
|
+
from eth_utils import function_signature_to_4byte_selector, keccak
|
|
26
27
|
from hexbytes import HexBytes
|
|
27
28
|
from web3 import Web3
|
|
29
|
+
from web3.exceptions import ContractLogicError
|
|
28
30
|
from web3.types import BlockIdentifier, Timestamp
|
|
29
31
|
|
|
30
32
|
from ipor_fusion.core.context import Web3Context
|
|
@@ -118,6 +120,35 @@ def _require_matching_hash(
|
|
|
118
120
|
)
|
|
119
121
|
|
|
120
122
|
|
|
123
|
+
class CustodianMarkerKind(Enum):
|
|
124
|
+
"""What an executor's custodian marker counts: the last confirmed update's
|
|
125
|
+
unix timestamp (executors before the 2026-10-21 contracts release) or the
|
|
126
|
+
number of confirmed updates (executors of that release)."""
|
|
127
|
+
|
|
128
|
+
TIMESTAMP = "timestamp"
|
|
129
|
+
CONFIRM_COUNT = "confirm_count"
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
_CONFIRM_COUNT_SELECTOR = function_signature_to_4byte_selector("confirmCount()")
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def _read_marker_kind(
|
|
136
|
+
ctx: Web3Context, executor: ChecksumAddress, block: BlockIdentifier
|
|
137
|
+
) -> CustodianMarkerKind:
|
|
138
|
+
"""Decided from the executor, not guessed from a value: a released
|
|
139
|
+
executor answers ``confirmCount()``, an earlier one has no such function
|
|
140
|
+
and the call reverts. Any other failure raises."""
|
|
141
|
+
try:
|
|
142
|
+
raw = ctx.call(executor, _CONFIRM_COUNT_SELECTOR, block)
|
|
143
|
+
except ContractLogicError:
|
|
144
|
+
return CustodianMarkerKind.TIMESTAMP
|
|
145
|
+
if len(raw) != 32:
|
|
146
|
+
raise ExternalStateError(
|
|
147
|
+
f"confirmCount() of {executor} returned {len(raw)} bytes, not a uint256"
|
|
148
|
+
)
|
|
149
|
+
return CustodianMarkerKind.CONFIRM_COUNT
|
|
150
|
+
|
|
151
|
+
|
|
121
152
|
@dataclass(frozen=True, slots=True)
|
|
122
153
|
class ExternalStateVaultState:
|
|
123
154
|
"""The external-state market's vault-scoped state, read from the vault's
|
|
@@ -136,7 +167,9 @@ class ExternalStateVaultState:
|
|
|
136
167
|
and not the pre-hook's staleness clock, which is
|
|
137
168
|
`ExternalStateExecutor.get_oldest_update_timestamp`. The fuse and the
|
|
138
169
|
pre-hook only compare it for equality with
|
|
139
|
-
`BalanceFuseSnapshot.custodian_marker
|
|
170
|
+
`BalanceFuseSnapshot.custodian_marker`. `custodian_marker_kind` says
|
|
171
|
+
which, read from the executor at the same block (``None`` without an
|
|
172
|
+
executor); `paused` is the flag
|
|
140
173
|
the balance fuse sets on a big change and `ExternalStateUnpauseFuse` clears
|
|
141
174
|
with an atomist signature. `ExternalStatePausePreHook` blocks the vault's
|
|
142
175
|
gated user operations while the flag is set, and also, flag or not, while
|
|
@@ -148,6 +181,7 @@ class ExternalStateVaultState:
|
|
|
148
181
|
executor: ChecksumAddress | None
|
|
149
182
|
last_total_balance: Amount
|
|
150
183
|
last_checked_custodian_marker: CustodianMarker
|
|
184
|
+
custodian_marker_kind: CustodianMarkerKind | None
|
|
151
185
|
paused: bool
|
|
152
186
|
block: int
|
|
153
187
|
|
|
@@ -232,10 +266,14 @@ def read_external_state(
|
|
|
232
266
|
raise ExternalStateError(
|
|
233
267
|
f"malformed pause flag in the external-state storage of {checksum}"
|
|
234
268
|
)
|
|
269
|
+
executor = Web3.to_checksum_address(f"0x{words[0]:040x}") if words[0] else None
|
|
235
270
|
return ExternalStateVaultState(
|
|
236
|
-
executor=
|
|
271
|
+
executor=executor,
|
|
237
272
|
last_total_balance=Amount(words[1]),
|
|
238
273
|
last_checked_custodian_marker=CustodianMarker(words[2]),
|
|
274
|
+
custodian_marker_kind=(
|
|
275
|
+
_read_marker_kind(ctx, executor, read_at) if executor else None
|
|
276
|
+
),
|
|
239
277
|
paused=words[3] == 1,
|
|
240
278
|
block=block,
|
|
241
279
|
)
|
|
@@ -365,6 +403,11 @@ class ExternalStateExecutor(ContractWrapper):
|
|
|
365
403
|
)
|
|
366
404
|
)
|
|
367
405
|
|
|
406
|
+
def read_custodian_marker_kind(self, ctx: Web3Context) -> CustodianMarkerKind:
|
|
407
|
+
"""Whether this executor's custodian marker is a timestamp or a
|
|
408
|
+
confirm count, at ``ctx``'s default block (see `CustodianMarkerKind`)."""
|
|
409
|
+
return _read_marker_kind(ctx, self._require_address(), ctx.default_block)
|
|
410
|
+
|
|
368
411
|
def confirm_count(self) -> Call[CustodianMarker]:
|
|
369
412
|
"""Executors of the 2026-10-21 contracts release only: the count of
|
|
370
413
|
confirmed custodian updates across every balance account, the marker
|
|
@@ -9,6 +9,7 @@ without importing that package, whose ``__init__`` loads every encoder.
|
|
|
9
9
|
from abc import ABC
|
|
10
10
|
from collections.abc import Sequence
|
|
11
11
|
from types import MemberDescriptorType
|
|
12
|
+
from typing import ClassVar
|
|
12
13
|
|
|
13
14
|
from eth_abi import encode
|
|
14
15
|
from eth_typing import ChecksumAddress
|
|
@@ -27,8 +28,15 @@ class Fuse(ABC): # noqa: B024 # ABC marks intent; no shared abstract method
|
|
|
27
28
|
same class and carry the same constructor state (the fuse address, plus e.g.
|
|
28
29
|
a `StakeFuse`'s staking contract), so a subclass that stores more state is
|
|
29
30
|
compared on it without overriding anything. That state must be hashable.
|
|
31
|
+
|
|
32
|
+
``SIGNATURES`` lists the Solidity functions a class's own methods encode;
|
|
33
|
+
`signatures` adds the inherited ones. The decoder reads them to recognize an
|
|
34
|
+
action; the test suite fails any call an encoder builds under test that its
|
|
35
|
+
class does not declare.
|
|
30
36
|
"""
|
|
31
37
|
|
|
38
|
+
SIGNATURES: ClassVar[tuple[str, ...]] = ()
|
|
39
|
+
|
|
32
40
|
def __init__(self, address: FuseAddress):
|
|
33
41
|
_validate_not_zero_address(address, "fuse address")
|
|
34
42
|
self._address = address
|
|
@@ -37,6 +45,15 @@ class Fuse(ABC): # noqa: B024 # ABC marks intent; no shared abstract method
|
|
|
37
45
|
def address(self) -> FuseAddress:
|
|
38
46
|
return self._address
|
|
39
47
|
|
|
48
|
+
@classmethod
|
|
49
|
+
def signatures(cls) -> tuple[str, ...]:
|
|
50
|
+
"""Every Solidity function this class encodes, inherited ones included."""
|
|
51
|
+
seen: dict[str, None] = {}
|
|
52
|
+
for klass in reversed(cls.__mro__):
|
|
53
|
+
for signature in vars(klass).get("SIGNATURES", ()):
|
|
54
|
+
seen[signature] = None
|
|
55
|
+
return tuple(seen)
|
|
56
|
+
|
|
40
57
|
@staticmethod
|
|
41
58
|
def _validate_amount(value: Amount, name: str) -> None:
|
|
42
59
|
if value <= 0:
|
|
@@ -94,6 +111,11 @@ class Fuse(ABC): # noqa: B024 # ABC marks intent; no shared abstract method
|
|
|
94
111
|
class StakeFuse(Fuse):
|
|
95
112
|
"""Base fuse for stake/unstake operations on a staking contract."""
|
|
96
113
|
|
|
114
|
+
SIGNATURES = (
|
|
115
|
+
"enter((uint256,address))",
|
|
116
|
+
"exit((uint256,address))",
|
|
117
|
+
)
|
|
118
|
+
|
|
97
119
|
def __init__(self, fuse_address: FuseAddress, staking_address: ChecksumAddress):
|
|
98
120
|
super().__init__(fuse_address)
|
|
99
121
|
self._staking_address = staking_address
|