ipor-fusion 4.1.0.dev26__tar.gz → 4.1.0.dev38__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.1.0.dev26 → ipor_fusion-4.1.0.dev38}/PKG-INFO +1 -1
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/pyproject.toml +1 -1
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/cli/options.py +16 -20
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/core/fusion_factory.py +4 -3
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/mcp/server.py +16 -6
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/readers/oracle_mapping.py +26 -9
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/vault_data.py +21 -3
- ipor_fusion-4.1.0.dev38/tests/_addresses.py +17 -0
- ipor_fusion-4.1.0.dev38/tests/test_addresses_helper.py +20 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_cli_commands.py +36 -9
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_cli_helpers.py +15 -12
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_fusion_instance.py +3 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_mcp_preflight.py +9 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_mcp_server.py +15 -5
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_oracle_mapping.py +20 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_types_and_events.py +2 -3
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_vault_data_block.py +11 -1
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/.claude-plugin/marketplace.json +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/.claude-plugin/plugin.json +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/.env.example +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/.gemini/settings.json +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/.git-blame-ignore-revs +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/.github/workflows/cd.yml +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/.github/workflows/ci.yml +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/.github/workflows/commit-lint.yml +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/.github/workflows/notify-slack.yml +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/.github/workflows/python-build.yml +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/.github/workflows/python-publish.yml +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/.github/workflows/release.yml +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/.github/workflows/report-slack.yml +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/.gitignore +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/.mcp.json +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/.pre-commit-config.yaml +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/.python-version +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/AGENTS.md +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/CHANGELOG.md +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/CLAUDE.md +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/CODEOWNERS +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/LICENSE +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/MIGRATION_4.md +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/README.md +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/build.sh +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/examples/CONTRIBUTING.md +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/examples/README.md +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/examples/advanced_euler_v2_credit_market_base.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/examples/advanced_euler_v2_looping_arbitrum.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/examples/crosschain_ccip_usdc_arbitrum_hyperevm.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/examples/external_state_margin_leg_base.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/examples/hypercore_vault_official_deployment_hyperevm.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/examples/simple_aave_v3_supply_base.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/pytest.ini +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/scripts/commit-check.sh +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/scripts/fusion_error_signatures.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/skills/ipor-deploy-vault/SKILL.md +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/__init__.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/about.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/cache.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/chains.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/cli/__init__.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/cli/address_cmd.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/cli/changelog_cmd.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/cli/config_cmd.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/cli/config_store.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/cli/main.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/cli/market_cmd.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/cli/oracle_mapping_view.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/cli/preflight_cmd.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/cli/vault_cmd.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/cli/vault_health.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/cli/vault_info_view.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/cli/vault_markets_view.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/cli/vault_rendering.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/config/__init__.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/config/roles.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/core/__init__.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/core/access.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/core/address.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/core/context.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/core/context_manager.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/core/contract.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/core/erc20.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/core/external_state_executor.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/core/fee_manager.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/core/fuse.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/core/fuse_action.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/core/fuse_decoding.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/core/logs.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/core/multicall.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/core/oracle.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/core/plasma_vault.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/core/rewards_manager.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/core/simulation.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/core/simulation_result.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/core/withdraw_manager.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/crosschain/__init__.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/crosschain/ccip/__init__.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/crosschain/ccip/chainlink.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/crosschain/ccip/codec.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/crosschain/ccip/contracts.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/crosschain/ccip/events.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/crosschain/ccip/indexer.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/crosschain/ccip/lane.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/crosschain/ccip/quote.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/crosschain/ccip/transport.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/crosschain/contracts.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/crosschain/discovery.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/crosschain/errors.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/crosschain/fuses/__init__.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/crosschain/fuses/base.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/crosschain/fuses/ccip.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/crosschain/fuses/stargate.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/crosschain/lane.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/crosschain/logs.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/crosschain/messages.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/crosschain/simulation.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/crosschain/stargate/__init__.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/crosschain/stargate/contracts.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/crosschain/stargate/lane.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/crosschain/stargate/layerzero.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/crosschain/stargate/transport.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/crosschain/transport.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/dependency_graph.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/entry_cli.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/entry_mcp.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/errors.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/explorer.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/field_docs.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/fuses/__init__.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/fuses/aave_v3.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/fuses/async_action.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/fuses/base.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/fuses/compound_v3.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/fuses/erc4626.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/fuses/euler_v2.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/fuses/events.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/fuses/external_state.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/fuses/external_state_event_decoding.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/fuses/fluid_instadapp.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/fuses/gearbox_v3.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/fuses/hypercore.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/fuses/hypercore_event_decoding.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/fuses/hypercore_pre_hooks.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/fuses/merkl.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/fuses/morpho.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/fuses/ramses_v2.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/fuses/uniswap_v3.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/fuses/universal.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/fusion_errors.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/guide/__init__.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/guide/architecture.md +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/guide/glossary.md +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/guide/invariants.md +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/guide/quickstart.md +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/hypercore/__init__.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/market_ids.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/market_info.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/mcp/__init__.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/mcp/guide.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/mcp/models.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/morpho_api.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/preflight.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/py.typed +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/readers/__init__.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/readers/aave_v3.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/readers/aave_v4.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/readers/compound_v3.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/readers/euler_v2.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/readers/hypercore.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/readers/hypercore_outcome.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/readers/lending_health.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/readers/morpho.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/readers/oracle_mapping_nodes.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/readers/oracle_mapping_reader.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/readers/position_manager.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/readers/ramses_v2.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/readers/uniswap_v3.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/readers/unpriceable.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/registry.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/substrates.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/types.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/validation.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/vault_erc20.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/vault_info.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/vault_managers.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/vault_positions.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/vault_reads.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/UniswapV3UniversalRouter.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/_crosschain.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/_crosschain_lifecycle.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/_crosschain_recovery.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/_euler_v2.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/_event_args.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/_fake_rpc.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/_foundry.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/_golden.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/_guide.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/_hypercore_model.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/_hypercore_shadow.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/_live.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/_multicall.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/_oracle_mapping_fake.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/_simulate.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/_validation.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/addresses.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/conftest.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/constants.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/fixtures/hypercore_hip3_flow.json +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/fixtures/hypercore_market55.json +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/fixtures/hypercore_official.json +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/fixtures/market_golden/meta_morpho_v1.json +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/fixtures/market_golden/meta_morpho_v1.mcp.json +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/fixtures/market_golden/meta_morpho_v2.json +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/fixtures/market_golden/meta_morpho_v2.mcp.json +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/fixtures/market_golden/morpho_api/market.json +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/fixtures/market_golden/morpho_api/vault_v1.json +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/fixtures/market_golden/morpho_api/vault_v2.json +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/fixtures/market_golden/morpho_api/vault_v2_not_found.json +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/fixtures/market_golden/morpho_blue.json +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/fixtures/market_golden/morpho_blue.mcp.json +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/fixtures/market_golden/morpho_blue_api_error.json +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/fixtures/market_golden/morpho_blue_api_error.mcp.json +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/fixtures/market_golden/morpho_blue_no_api.json +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/fixtures/market_golden/morpho_blue_no_api.mcp.json +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/fixtures/oracle_mapping_golden/mapping.json +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/fixtures/oracle_mapping_golden/mapping.mcp.json +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/fixtures/simulate_v1_request.json +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/fixtures/vault_info_golden/clean.json +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/fixtures/vault_info_golden/clean.mcp.json +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/fixtures/vault_info_golden/clean.txt +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/fixtures/vault_info_golden/deployment_error.json +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/fixtures/vault_info_golden/deployment_error.mcp.json +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/fixtures/vault_info_golden/deployment_error.txt +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/fixtures/vault_info_golden/fetched.cli.json +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/fixtures/vault_info_golden/fetched.json +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/fixtures/vault_info_golden/fetched.mcp.json +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/fixtures/vault_info_golden/full.json +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/fixtures/vault_info_golden/full.mcp.json +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/fixtures/vault_info_golden/full.txt +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/fixtures/vault_info_golden/rich.json +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/fixtures/vault_info_golden/rich.mcp.json +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/fixtures/vault_info_golden/rich.txt +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_about.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_access_manager.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_cache.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_chains.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_cli_address.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_cli_changelog.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_cli_config_store.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_cli_hypercore.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_cli_lending_health.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_cli_market_golden.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_cli_market_morpho_blue.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_cli_mcp_parity.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_cli_oracle_mapping.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_cli_oracle_mapping_golden.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_cli_pending_requests_scan.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_cli_preflight.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_cli_role_accounts.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_cli_vault_info_golden.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_cli_vault_predates_getters.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_cli_vault_validate.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_contract_call.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_contract_encoder.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_crosschain_ccip_events.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_crosschain_ccip_indexer.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_crosschain_ccip_quote.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_crosschain_chainlink.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_crosschain_contracts.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_crosschain_encoding.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_crosschain_errors.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_crosschain_lane.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_crosschain_layering.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_crosschain_transport.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_docs_typing.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_erc20.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_error_hierarchy.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_examples_crosschain.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_examples_euler_looping_arbitrum.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_examples_hypercore_hyperevm.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_examples_vaults.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_explorer.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_external_state_events.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_external_state_executor.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_fee_manager.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_fuse_decoding.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_fuse_encoding.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_fusion_errors.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_governance_wrappers.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_guide.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_hypercore_deployment.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_hypercore_events.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_hypercore_flow.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_hypercore_live.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_hypercore_outcome.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_hypercore_pre_hooks.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_hypercore_readers.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_hypercore_simulation.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_logs.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_mcp_address.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_mcp_hypercore.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_mcp_models.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_mcp_vault_validate.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_multicall.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_oracle.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_plasma_vault.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_preflight.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_public_api.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_public_type_reachability.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_reader_encoding.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_registry.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_revert_decoding.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_rewards_manager.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_roles.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_sdk_layering.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_simulate_borrow_aave_v3_mainnet.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_simulate_claim_merkl_wrapper_base.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_simulate_claim_morpho_rewards.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_simulate_crosschain_hyperevm.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_simulate_euler_v2_batch_base.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_simulate_euler_v2_credit_market_base.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_simulate_euler_v2_swap_deploy_base.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_simulate_euler_v2_swap_reconfigure_base.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_simulate_euler_v2_swap_registry_base.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_simulate_examples_arbitrum.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_simulate_examples_base.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_simulate_examples_crosschain.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_simulate_examples_hyperevm.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_simulate_external_state_executor_base.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_simulate_fusion_factory_clone_base.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_simulate_looping_aave_v3_base.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_simulate_looping_morpho_blue_base.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_simulate_morpho_blue.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_simulate_pilot_v3.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_simulate_pilot_v4_uniswap.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_simulate_pilot_v5_ramses.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_simulate_pilot_v5_scheduled.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_simulate_plasma_vault_setters_base.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_simulate_preflight_base.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_simulate_readers_integration.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_simulate_release_funds.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_simulate_swap_on_base.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_simulate_timelock_base.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_simulate_vault_from_scratch_base.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_simulation_errors.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_simulation_observation.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_solidity_compiler.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_solidity_mirrors.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_substrates.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_unpriceable_tokens.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_validation.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_vault_dep_graph.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_vault_info_fetched_golden.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_vault_info_onchain_base.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_vault_info_onchain_base_no_withdraw_manager.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_vault_reconciliation_onchain_base.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_vault_simulator_blocks.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_web3_context.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/test_withdraw_manager.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/typed_client/consumer.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/tests/typed_client/rejected.py +0 -0
- {ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/uv.lock +0 -0
|
@@ -2,7 +2,6 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import click
|
|
4
4
|
from eth_typing import ChecksumAddress
|
|
5
|
-
from web3 import Web3
|
|
6
5
|
|
|
7
6
|
from ipor_fusion.chains import CHAIN_NAME_TO_ID, ensure_supported_chain
|
|
8
7
|
from ipor_fusion.cli.config_store import (
|
|
@@ -13,7 +12,12 @@ from ipor_fusion.core.context import Web3Context
|
|
|
13
12
|
from ipor_fusion.errors import (
|
|
14
13
|
UnsupportedChainError,
|
|
15
14
|
)
|
|
16
|
-
from ipor_fusion.types import
|
|
15
|
+
from ipor_fusion.types import (
|
|
16
|
+
ChainId,
|
|
17
|
+
PlasmaVaultAddress,
|
|
18
|
+
parse_checksum_address,
|
|
19
|
+
parse_hex_address,
|
|
20
|
+
)
|
|
17
21
|
|
|
18
22
|
|
|
19
23
|
# click < 8.4 does not make ParamType generic, so it cannot be subscripted.
|
|
@@ -27,26 +31,18 @@ class AddressType(click.ParamType): # pyright: ignore[reportMissingTypeArgument
|
|
|
27
31
|
self.fail(f"expected string, got {type(value).__name__}", param, ctx)
|
|
28
32
|
|
|
29
33
|
value = value.strip()
|
|
30
|
-
if
|
|
31
|
-
value =
|
|
34
|
+
if value[:2] in ("0x", "0X"):
|
|
35
|
+
value = value[2:]
|
|
36
|
+
value = "0x" + value
|
|
32
37
|
|
|
33
|
-
|
|
34
|
-
|
|
38
|
+
try:
|
|
39
|
+
parse_hex_address(value)
|
|
40
|
+
except ValueError:
|
|
35
41
|
self.fail(f"invalid Ethereum address: {value}", param, ctx)
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
except Exception:
|
|
41
|
-
self.fail(f"invalid address checksum: {value}", param, ctx)
|
|
42
|
-
if checksum != value:
|
|
43
|
-
self.fail(
|
|
44
|
-
f"bad checksum — expected {checksum}, got {value}",
|
|
45
|
-
param,
|
|
46
|
-
ctx,
|
|
47
|
-
)
|
|
48
|
-
|
|
49
|
-
return Web3.to_checksum_address(value)
|
|
42
|
+
try:
|
|
43
|
+
return parse_checksum_address(value)
|
|
44
|
+
except ValueError as exc:
|
|
45
|
+
self.fail(str(exc), param, ctx)
|
|
50
46
|
|
|
51
47
|
|
|
52
48
|
ADDRESS = AddressType()
|
|
@@ -38,6 +38,7 @@ from ipor_fusion.types import (
|
|
|
38
38
|
Period,
|
|
39
39
|
PlasmaVaultAddress,
|
|
40
40
|
parse_access_manager_address,
|
|
41
|
+
parse_checksum_address,
|
|
41
42
|
parse_plasma_vault_address,
|
|
42
43
|
)
|
|
43
44
|
|
|
@@ -82,8 +83,8 @@ class FusionInstance:
|
|
|
82
83
|
def from_dict(cls, data: Mapping[str, object]) -> FusionInstance:
|
|
83
84
|
"""Rebuild an instance from its JSON form (``dataclasses.asdict``),
|
|
84
85
|
parsing every field: role addresses through their parsers, the rest
|
|
85
|
-
|
|
86
|
-
on a missing, extra or malformed field."""
|
|
86
|
+
through `parse_checksum_address`, integers and strings type-checked.
|
|
87
|
+
Raises ``ValueError`` on a missing, extra or malformed field."""
|
|
87
88
|
names = {f.name for f in fields(cls)}
|
|
88
89
|
missing = names - data.keys()
|
|
89
90
|
extra = data.keys() - names
|
|
@@ -128,7 +129,7 @@ def _text(value: object) -> str:
|
|
|
128
129
|
|
|
129
130
|
|
|
130
131
|
def _address(value: object) -> ChecksumAddress:
|
|
131
|
-
return
|
|
132
|
+
return parse_checksum_address(_text(value))
|
|
132
133
|
|
|
133
134
|
|
|
134
135
|
_FUSION_INSTANCE_OUTPUT_TYPES: list[str] = [
|
|
@@ -8,6 +8,7 @@ from collections.abc import Callable
|
|
|
8
8
|
from functools import partial
|
|
9
9
|
from typing import Annotated, Literal
|
|
10
10
|
|
|
11
|
+
from eth_typing import ChecksumAddress
|
|
11
12
|
from mcp.server.fastmcp import FastMCP
|
|
12
13
|
from pydantic import AfterValidator, BeforeValidator, Field
|
|
13
14
|
from web3 import Web3
|
|
@@ -76,8 +77,8 @@ from ipor_fusion.registry import (
|
|
|
76
77
|
from ipor_fusion.types import (
|
|
77
78
|
ChainId,
|
|
78
79
|
PlasmaVaultAddress,
|
|
80
|
+
parse_checksum_address,
|
|
79
81
|
parse_morpho_market_id,
|
|
80
|
-
parse_plasma_vault_address,
|
|
81
82
|
)
|
|
82
83
|
from ipor_fusion.validation import validate_vault
|
|
83
84
|
from ipor_fusion.vault_data import (
|
|
@@ -117,14 +118,19 @@ def _resolve_provider(cfg: FusionConfig, chain_id: ChainId) -> str:
|
|
|
117
118
|
)
|
|
118
119
|
|
|
119
120
|
|
|
120
|
-
def
|
|
121
|
-
"""
|
|
121
|
+
def _address_arg(raw: str) -> ChecksumAddress:
|
|
122
|
+
"""An address as a tool caller sends it: surrounding whitespace and a
|
|
122
123
|
missing ``0x`` are accepted, as in 3.x; letter case follows
|
|
123
124
|
`parse_checksum_address`, so a wrong EIP-55 checksum is refused."""
|
|
124
125
|
value = raw.strip()
|
|
125
126
|
if value[:2] not in ("0x", "0X"):
|
|
126
127
|
value = "0x" + value
|
|
127
|
-
return
|
|
128
|
+
return parse_checksum_address(value)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def _vault_arg(raw: str) -> PlasmaVaultAddress:
|
|
132
|
+
"""`_address_arg`, labeled as a Plasma Vault's address."""
|
|
133
|
+
return PlasmaVaultAddress(_address_arg(raw))
|
|
128
134
|
|
|
129
135
|
|
|
130
136
|
def _resolve_chain_id(
|
|
@@ -341,6 +347,10 @@ def vault_preflight(
|
|
|
341
347
|
raise ValueError(f"calldata: {exc}") from exc
|
|
342
348
|
if not actions:
|
|
343
349
|
raise ValueError("calldata: the batch has no actions")
|
|
350
|
+
try:
|
|
351
|
+
sender_address = _address_arg(sender)
|
|
352
|
+
except ValueError as exc:
|
|
353
|
+
raise ValueError(f"sender: {exc}") from exc
|
|
344
354
|
cfg = load_config()
|
|
345
355
|
checksum = _vault_arg(vault_address)
|
|
346
356
|
chain_id = _resolve_chain_id(cfg, checksum, chain_id)
|
|
@@ -350,7 +360,7 @@ def vault_preflight(
|
|
|
350
360
|
ctx,
|
|
351
361
|
checksum,
|
|
352
362
|
actions,
|
|
353
|
-
sender=
|
|
363
|
+
sender=sender_address,
|
|
354
364
|
simulate=simulate,
|
|
355
365
|
)
|
|
356
366
|
return ExecutePreflightResponse.model_validate(build_execute_preflight_json(result))
|
|
@@ -752,7 +762,7 @@ def market_meta_morpho(
|
|
|
752
762
|
vault_address: MetaMorpho V1 or Vault V2 address.
|
|
753
763
|
chain_id: EVM chain ID.
|
|
754
764
|
"""
|
|
755
|
-
info = fetch_vault(
|
|
765
|
+
info = fetch_vault(_address_arg(vault_address), chain_id)
|
|
756
766
|
payload = build_meta_morpho_json(info, chain_id)
|
|
757
767
|
return MetaMorphoVaultResponse.model_validate(payload)
|
|
758
768
|
|
{ipor_fusion-4.1.0.dev26 → ipor_fusion-4.1.0.dev38}/src/ipor_fusion/readers/oracle_mapping.py
RENAMED
|
@@ -236,15 +236,32 @@ def _resolve_middleware_fallback(
|
|
|
236
236
|
node.source_type = TYPE_MIDDLEWARE_FALLBACK
|
|
237
237
|
node.status = "resolved"
|
|
238
238
|
if underlying_middleware is not None:
|
|
239
|
-
# Same asset, different oracle
|
|
240
|
-
#
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
239
|
+
# Same asset, different oracle: the asset key is not in `visited` yet,
|
|
240
|
+
# so the hop is keyed by (oracle, asset) — a manager chain that loops
|
|
241
|
+
# back ends as a cycle instead of running to max_depth.
|
|
242
|
+
hop = f"{underlying_middleware.lower()}/{asset.lower()}"
|
|
243
|
+
if hop in visited:
|
|
244
|
+
return _partial(
|
|
245
|
+
asset,
|
|
246
|
+
symbol,
|
|
247
|
+
decimals,
|
|
248
|
+
source,
|
|
249
|
+
TYPE_UNKNOWN,
|
|
250
|
+
"cycle_detected",
|
|
251
|
+
[label],
|
|
252
|
+
price,
|
|
253
|
+
)
|
|
254
|
+
visited.add(hop)
|
|
255
|
+
try:
|
|
256
|
+
dep = _resolve(
|
|
257
|
+
reader.delegate(underlying_middleware),
|
|
258
|
+
asset,
|
|
259
|
+
depth + 1,
|
|
260
|
+
visited,
|
|
261
|
+
max_depth,
|
|
262
|
+
)
|
|
263
|
+
finally:
|
|
264
|
+
visited.discard(hop)
|
|
248
265
|
node.dependencies = [dep]
|
|
249
266
|
# dep re-resolves the same asset, so drop its leading label from the path.
|
|
250
267
|
node.path = [label, "middleware fallback", *dep.path[1:]]
|
|
@@ -312,7 +312,21 @@ def resolve_token_decimals(ctx: Web3Context, address: HexAddress) -> Decimals |
|
|
|
312
312
|
return decimals
|
|
313
313
|
|
|
314
314
|
|
|
315
|
-
def
|
|
315
|
+
def _latest_head(ctx: Web3Context) -> tuple[int, Timestamp]:
|
|
316
|
+
"""The latest block's number and timestamp from one read.
|
|
317
|
+
|
|
318
|
+
Reading the head number first and the block by number second can reach a
|
|
319
|
+
node behind a load balancer that has not seen that block yet.
|
|
320
|
+
"""
|
|
321
|
+
head = ctx.web3.eth.get_block("latest")
|
|
322
|
+
return head["number"], head["timestamp"]
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
def _block_timestamp(
|
|
326
|
+
ctx: Web3Context, block: int, known: Timestamp | None
|
|
327
|
+
) -> Timestamp:
|
|
328
|
+
if known is not None:
|
|
329
|
+
return known
|
|
316
330
|
return ctx.web3.eth.get_block(block)["timestamp"]
|
|
317
331
|
|
|
318
332
|
|
|
@@ -634,14 +648,18 @@ def fetch_vault_data(
|
|
|
634
648
|
ensure_supported_chain(ctx.chain_id)
|
|
635
649
|
if block is None:
|
|
636
650
|
block = _pinned_default_block(ctx)
|
|
637
|
-
resolved_block = ctx.web3.eth.block_number if block is None else block
|
|
638
651
|
is_latest = block is None
|
|
652
|
+
head_timestamp: Timestamp | None = None
|
|
653
|
+
if block is None:
|
|
654
|
+
resolved_block, head_timestamp = _latest_head(ctx)
|
|
655
|
+
else:
|
|
656
|
+
resolved_block = block
|
|
639
657
|
ctx = ctx.at_block(resolved_block)
|
|
640
658
|
plasma_vault = PlasmaVault(vault)
|
|
641
659
|
with ThreadPoolExecutor() as pool:
|
|
642
660
|
# Independent pipelines, each a short chain of batched reads; none of
|
|
643
661
|
# them waits on a pool future, so they cannot starve the pool.
|
|
644
|
-
f_timestamp = pool.submit(_block_timestamp, ctx, resolved_block)
|
|
662
|
+
f_timestamp = pool.submit(_block_timestamp, ctx, resolved_block, head_timestamp)
|
|
645
663
|
f_vault = pool.submit(_fetch_vault_reads, ctx, plasma_vault)
|
|
646
664
|
f_fees = pool.submit(_fetch_fee_data, ctx, plasma_vault)
|
|
647
665
|
f_withdraw = pool.submit(_fetch_withdraw_manager, ctx, plasma_vault)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Address spellings that EIP-55 must reject."""
|
|
2
|
+
|
|
3
|
+
from web3 import Web3
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def miscased(address: str) -> str:
|
|
7
|
+
"""``address`` checksummed with one letter's case flipped: the same 20 bytes,
|
|
8
|
+
a wrong checksum. Only a mixed-case result carries a checksum, so the first
|
|
9
|
+
flip that leaves the digits mixed case is taken."""
|
|
10
|
+
checksummed = Web3.to_checksum_address(address)
|
|
11
|
+
for i, c in enumerate(checksummed[2:], start=2):
|
|
12
|
+
if c.isalpha():
|
|
13
|
+
flipped = checksummed[:i] + c.swapcase() + checksummed[i + 1 :]
|
|
14
|
+
digits = flipped[2:]
|
|
15
|
+
if digits not in (digits.lower(), digits.upper()):
|
|
16
|
+
return flipped
|
|
17
|
+
raise AssertionError(f"no wrong checksum is possible for {address}")
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""The ``miscased`` test helper returns a spelling EIP-55 rejects, or fails."""
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
from _addresses import miscased
|
|
5
|
+
|
|
6
|
+
from ipor_fusion.types import parse_checksum_address
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def test_skips_a_flip_that_leaves_one_case():
|
|
10
|
+
# Flipping B gives all lowercase, which carries no checksum.
|
|
11
|
+
address = "0xBca0000000000000000000000000000000000000"
|
|
12
|
+
bad = miscased(address)
|
|
13
|
+
assert bad.lower() == address.lower()
|
|
14
|
+
with pytest.raises(ValueError, match="invalid address checksum"):
|
|
15
|
+
parse_checksum_address(bad)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def test_fails_when_no_flip_leaves_mixed_case():
|
|
19
|
+
with pytest.raises(AssertionError, match="no wrong checksum"):
|
|
20
|
+
miscased("0xCc00000000000000000000000000000000000000")
|
|
@@ -443,7 +443,10 @@ class TestVaultInfo:
|
|
|
443
443
|
mock_ctx.cache = NullCache()
|
|
444
444
|
mock_ctx.web3.eth.block_number = 12345678
|
|
445
445
|
mock_ctx.default_block = "latest"
|
|
446
|
-
mock_ctx.web3.eth.get_block.return_value = {
|
|
446
|
+
mock_ctx.web3.eth.get_block.return_value = {
|
|
447
|
+
"number": 12345678,
|
|
448
|
+
"timestamp": 1700000000,
|
|
449
|
+
}
|
|
447
450
|
mock_ctx.chain_id = 1
|
|
448
451
|
mock_ctx.at_block.return_value = mock_ctx
|
|
449
452
|
mock_ctx_cls.from_url.return_value = mock_ctx
|
|
@@ -573,7 +576,10 @@ class TestVaultInfo:
|
|
|
573
576
|
mock_ctx.cache = NullCache()
|
|
574
577
|
mock_ctx.web3.eth.block_number = 12345678
|
|
575
578
|
mock_ctx.default_block = "latest"
|
|
576
|
-
mock_ctx.web3.eth.get_block.return_value = {
|
|
579
|
+
mock_ctx.web3.eth.get_block.return_value = {
|
|
580
|
+
"number": 12345678,
|
|
581
|
+
"timestamp": 1700000000,
|
|
582
|
+
}
|
|
577
583
|
mock_ctx.chain_id = 1
|
|
578
584
|
mock_ctx.at_block.return_value = mock_ctx
|
|
579
585
|
mock_ctx_cls.from_url.return_value = mock_ctx
|
|
@@ -654,7 +660,10 @@ class TestVaultInfo:
|
|
|
654
660
|
mock_ctx.cache = NullCache()
|
|
655
661
|
mock_ctx.web3.eth.block_number = 100
|
|
656
662
|
mock_ctx.default_block = "latest"
|
|
657
|
-
mock_ctx.web3.eth.get_block.return_value = {
|
|
663
|
+
mock_ctx.web3.eth.get_block.return_value = {
|
|
664
|
+
"number": 100,
|
|
665
|
+
"timestamp": 1700000000,
|
|
666
|
+
}
|
|
658
667
|
mock_ctx.chain_id = 1
|
|
659
668
|
mock_ctx.at_block.return_value = mock_ctx
|
|
660
669
|
mock_ctx_cls.from_url.return_value = mock_ctx
|
|
@@ -723,7 +732,10 @@ class TestVaultInfo:
|
|
|
723
732
|
mock_ctx.cache = NullCache()
|
|
724
733
|
mock_ctx.web3.eth.block_number = 100
|
|
725
734
|
mock_ctx.default_block = "latest"
|
|
726
|
-
mock_ctx.web3.eth.get_block.return_value = {
|
|
735
|
+
mock_ctx.web3.eth.get_block.return_value = {
|
|
736
|
+
"number": 100,
|
|
737
|
+
"timestamp": 1700000000,
|
|
738
|
+
}
|
|
727
739
|
mock_ctx.chain_id = 1
|
|
728
740
|
mock_ctx.at_block.return_value = mock_ctx
|
|
729
741
|
mock_ctx_cls.from_url.return_value = mock_ctx
|
|
@@ -804,7 +816,10 @@ class TestVaultInfo:
|
|
|
804
816
|
mock_ctx.cache = NullCache()
|
|
805
817
|
mock_ctx.web3.eth.block_number = 100
|
|
806
818
|
mock_ctx.default_block = "latest"
|
|
807
|
-
mock_ctx.web3.eth.get_block.return_value = {
|
|
819
|
+
mock_ctx.web3.eth.get_block.return_value = {
|
|
820
|
+
"number": 100,
|
|
821
|
+
"timestamp": 1700000000,
|
|
822
|
+
}
|
|
808
823
|
mock_ctx.web3.eth.get_transaction.return_value = {"blockNumber": 18500000}
|
|
809
824
|
mock_ctx.chain_id = 1
|
|
810
825
|
mock_ctx.at_block.return_value = mock_ctx
|
|
@@ -875,7 +890,10 @@ class TestVaultInfo:
|
|
|
875
890
|
mock_ctx.cache = NullCache()
|
|
876
891
|
mock_ctx.web3.eth.block_number = 100
|
|
877
892
|
mock_ctx.default_block = "latest"
|
|
878
|
-
mock_ctx.web3.eth.get_block.return_value = {
|
|
893
|
+
mock_ctx.web3.eth.get_block.return_value = {
|
|
894
|
+
"number": 100,
|
|
895
|
+
"timestamp": 1700000000,
|
|
896
|
+
}
|
|
879
897
|
mock_ctx.chain_id = 1
|
|
880
898
|
mock_ctx.at_block.return_value = mock_ctx
|
|
881
899
|
mock_ctx_cls.from_url.return_value = mock_ctx
|
|
@@ -986,7 +1004,10 @@ class TestVaultInfoJson:
|
|
|
986
1004
|
mock_ctx.chain_id = 1
|
|
987
1005
|
mock_ctx.web3.eth.block_number = 12345678
|
|
988
1006
|
mock_ctx.default_block = "latest"
|
|
989
|
-
mock_ctx.web3.eth.get_block.return_value = {
|
|
1007
|
+
mock_ctx.web3.eth.get_block.return_value = {
|
|
1008
|
+
"number": 12345678,
|
|
1009
|
+
"timestamp": 1700000000,
|
|
1010
|
+
}
|
|
990
1011
|
mock_ctx.chain_id = 1
|
|
991
1012
|
mock_ctx.at_block.return_value = mock_ctx
|
|
992
1013
|
mock_ctx_cls.from_url.return_value = mock_ctx
|
|
@@ -1165,7 +1186,10 @@ class TestVaultInfoJson:
|
|
|
1165
1186
|
mock_ctx.cache = NullCache()
|
|
1166
1187
|
mock_ctx.web3.eth.block_number = 100
|
|
1167
1188
|
mock_ctx.default_block = "latest"
|
|
1168
|
-
mock_ctx.web3.eth.get_block.return_value = {
|
|
1189
|
+
mock_ctx.web3.eth.get_block.return_value = {
|
|
1190
|
+
"number": 100,
|
|
1191
|
+
"timestamp": 1700000000,
|
|
1192
|
+
}
|
|
1169
1193
|
mock_ctx.web3.eth.get_transaction.return_value = {"blockNumber": 18500000}
|
|
1170
1194
|
mock_ctx.chain_id = 1
|
|
1171
1195
|
mock_ctx.at_block.return_value = mock_ctx
|
|
@@ -1246,7 +1270,10 @@ class TestVaultInfoJson:
|
|
|
1246
1270
|
mock_ctx.cache = NullCache()
|
|
1247
1271
|
mock_ctx.web3.eth.block_number = 100
|
|
1248
1272
|
mock_ctx.default_block = "latest"
|
|
1249
|
-
mock_ctx.web3.eth.get_block.return_value = {
|
|
1273
|
+
mock_ctx.web3.eth.get_block.return_value = {
|
|
1274
|
+
"number": 100,
|
|
1275
|
+
"timestamp": 1700000000,
|
|
1276
|
+
}
|
|
1250
1277
|
mock_ctx.chain_id = 1
|
|
1251
1278
|
mock_ctx.at_block.return_value = mock_ctx
|
|
1252
1279
|
mock_ctx_cls.from_url.return_value = mock_ctx
|
|
@@ -6,6 +6,7 @@ from unittest.mock import MagicMock, patch
|
|
|
6
6
|
|
|
7
7
|
import click
|
|
8
8
|
import pytest
|
|
9
|
+
from _addresses import miscased
|
|
9
10
|
from _multicall import decode_aggregate3, multicall_aware
|
|
10
11
|
from _validation import holding, validate
|
|
11
12
|
from click.testing import CliRunner
|
|
@@ -131,9 +132,20 @@ class TestAddressType:
|
|
|
131
132
|
checksum = Web3.to_checksum_address("0x" + "1a2b3c4d5e" * 4)
|
|
132
133
|
assert ADDRESS.convert(checksum, None, None) == checksum
|
|
133
134
|
|
|
135
|
+
@pytest.mark.parametrize(
|
|
136
|
+
"raw",
|
|
137
|
+
[
|
|
138
|
+
"0X" + "ab" * 20,
|
|
139
|
+
"0X" + "AB" * 20,
|
|
140
|
+
"0X" + VALID_CHECKSUM[2:],
|
|
141
|
+
],
|
|
142
|
+
)
|
|
143
|
+
def test_valid_with_uppercase_0x_prefix(self, raw: str):
|
|
144
|
+
assert _convert_via_cli(raw) == VALID_CHECKSUM
|
|
145
|
+
|
|
134
146
|
def test_bad_checksum_mixed_case(self):
|
|
135
147
|
good = str(Web3.to_checksum_address("0x" + "1a2b3c4d5e" * 4))
|
|
136
|
-
bad = good
|
|
148
|
+
bad = miscased(good)
|
|
137
149
|
runner = CliRunner()
|
|
138
150
|
|
|
139
151
|
@click.command()
|
|
@@ -143,6 +155,8 @@ class TestAddressType:
|
|
|
143
155
|
|
|
144
156
|
result = runner.invoke(cmd, [bad])
|
|
145
157
|
assert result.exit_code != 0
|
|
158
|
+
assert "invalid address checksum" in result.output
|
|
159
|
+
assert good not in result.output
|
|
146
160
|
|
|
147
161
|
def test_too_short(self):
|
|
148
162
|
runner = CliRunner()
|
|
@@ -1158,17 +1172,6 @@ class TestPrintBalanceFusesTable:
|
|
|
1158
1172
|
assert "Zero-Balance Fuses" not in out
|
|
1159
1173
|
|
|
1160
1174
|
|
|
1161
|
-
class TestAddressTypeChecksumException:
|
|
1162
|
-
@patch("ipor_fusion.cli.options.Web3.to_checksum_address")
|
|
1163
|
-
def test_checksum_exception_caught(self, mock_checksum):
|
|
1164
|
-
mock_checksum.side_effect = Exception("unexpected error")
|
|
1165
|
-
mixed = "0xAbCdEf1234567890aBcDeF1234567890AbCdEf12"
|
|
1166
|
-
with pytest.raises(
|
|
1167
|
-
click.exceptions.BadParameter, match="invalid address checksum"
|
|
1168
|
-
):
|
|
1169
|
-
ADDRESS.convert(mixed, None, None)
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
1175
|
class TestPrintReconciliation:
|
|
1173
1176
|
def test_within_threshold(self, capsys):
|
|
1174
1177
|
data = VaultData(
|
|
@@ -6,6 +6,7 @@ from __future__ import annotations
|
|
|
6
6
|
from dataclasses import asdict
|
|
7
7
|
|
|
8
8
|
import pytest
|
|
9
|
+
from _addresses import miscased
|
|
9
10
|
from web3 import Web3
|
|
10
11
|
|
|
11
12
|
from ipor_fusion.core.fusion_factory import FusionInstance
|
|
@@ -58,6 +59,8 @@ def test_from_dict_checksums_every_address_and_round_trips():
|
|
|
58
59
|
(lambda d: d.update(extra=1), "extra"),
|
|
59
60
|
(lambda d: d.update(plasma_vault="0x1234"), "address"),
|
|
60
61
|
(lambda d: d.update(plasma_vault=" " + d["plasma_vault"]), "address"),
|
|
62
|
+
(lambda d: d.update(plasma_vault=miscased(d["plasma_vault"])), "checksum"),
|
|
63
|
+
(lambda d: d.update(price_manager=miscased(d["price_manager"])), "checksum"),
|
|
61
64
|
(lambda d: d.update(asset_decimals="8"), "integer"),
|
|
62
65
|
(lambda d: d.update(index=True), "integer"),
|
|
63
66
|
(lambda d: d.update(asset_symbol=None), "string"),
|
|
@@ -4,6 +4,7 @@ import asyncio
|
|
|
4
4
|
from unittest.mock import MagicMock, patch
|
|
5
5
|
|
|
6
6
|
import pytest
|
|
7
|
+
from _addresses import miscased
|
|
7
8
|
from mcp.server.fastmcp.exceptions import ToolError
|
|
8
9
|
from web3 import Web3
|
|
9
10
|
|
|
@@ -26,6 +27,8 @@ from ipor_fusion.types import (
|
|
|
26
27
|
|
|
27
28
|
VAULT = PlasmaVaultAddress(Web3.to_checksum_address("0x" + "11" * 20))
|
|
28
29
|
SENDER = Web3.to_checksum_address("0x" + "33" * 20)
|
|
30
|
+
# Hex letters, so it can carry a wrong checksum; SENDER has none.
|
|
31
|
+
SENDER_HEX = "0x" + "ab" * 20
|
|
29
32
|
FUSE = FuseAddress(Web3.to_checksum_address("0x" + "55" * 20))
|
|
30
33
|
ACTION = AaveV3SupplyFuse(FUSE).supply(
|
|
31
34
|
asset=Web3.to_checksum_address("0x" + "44" * 20), amount=Amount(10)
|
|
@@ -84,3 +87,9 @@ def test_bad_calldata_is_a_tool_error(preflight):
|
|
|
84
87
|
with pytest.raises(ToolError, match="no actions"):
|
|
85
88
|
_call(vault_address=VAULT, calldata=empty, sender=SENDER)
|
|
86
89
|
preflight.assert_not_called()
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def test_wrong_sender_checksum_is_a_tool_error(preflight):
|
|
93
|
+
with pytest.raises(ToolError, match="sender: invalid address checksum"):
|
|
94
|
+
_call(vault_address=VAULT, calldata=CALLDATA, sender=miscased(SENDER_HEX))
|
|
95
|
+
preflight.assert_not_called()
|
|
@@ -5,6 +5,7 @@ import importlib.metadata
|
|
|
5
5
|
from unittest.mock import ANY, MagicMock, patch
|
|
6
6
|
|
|
7
7
|
import pytest
|
|
8
|
+
from _addresses import miscased
|
|
8
9
|
from mcp.server.fastmcp.exceptions import ToolError
|
|
9
10
|
from web3 import Web3
|
|
10
11
|
|
|
@@ -770,7 +771,7 @@ _LOAN = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
|
|
|
770
771
|
_COLLAT = "0xD2af830E8CBdFed6CC11Bab697bB25496ed6FA62"
|
|
771
772
|
_ORACLE = "0x7C65985C35181D51eF7571FA40211B57659B7d80"
|
|
772
773
|
_IRM = "0x870Ac11D48B15db9a138Cf899d20F13F79Ba00BC"
|
|
773
|
-
_VAULT = "
|
|
774
|
+
_VAULT = Web3.to_checksum_address("0xf9bddd4a9b3a45f980e11fdde96e16364ddbec49")
|
|
774
775
|
|
|
775
776
|
|
|
776
777
|
def _stub_reader_instance():
|
|
@@ -1085,6 +1086,17 @@ class TestMarketMetaMorpho:
|
|
|
1085
1086
|
with pytest.raises(MorphoApiError, match="not found"):
|
|
1086
1087
|
market_meta_morpho(vault_address=_VAULT, chain_id=1)
|
|
1087
1088
|
|
|
1089
|
+
@patch("ipor_fusion.mcp.server.fetch_vault", return_value=_v1_info())
|
|
1090
|
+
def test_accepts_padded_bare_lowercase_address(self, fetch):
|
|
1091
|
+
market_meta_morpho(vault_address=f" {_VAULT[2:].lower()} ", chain_id=1)
|
|
1092
|
+
fetch.assert_called_once_with(_VAULT, 1)
|
|
1093
|
+
|
|
1094
|
+
@patch("ipor_fusion.mcp.server.fetch_vault", return_value=_v1_info())
|
|
1095
|
+
def test_rejects_a_wrong_checksum(self, fetch):
|
|
1096
|
+
with pytest.raises(ValueError, match="invalid address checksum"):
|
|
1097
|
+
market_meta_morpho(vault_address=miscased(_VAULT), chain_id=1)
|
|
1098
|
+
fetch.assert_not_called()
|
|
1099
|
+
|
|
1088
1100
|
|
|
1089
1101
|
class TestVaultAddressArgument:
|
|
1090
1102
|
"""Tool callers may pad an address with whitespace or omit its ``0x``; the
|
|
@@ -1114,10 +1126,8 @@ class TestVaultAddressArgument:
|
|
|
1114
1126
|
_vault_arg(raw)
|
|
1115
1127
|
|
|
1116
1128
|
def test_rejects_a_wrong_checksum(self):
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
miscased = checksum[:i] + checksum[i].swapcase() + checksum[i + 1 :]
|
|
1120
|
-
for raw in (miscased, "0X" + miscased[2:], miscased[2:]):
|
|
1129
|
+
bad = miscased(self.VAULT)
|
|
1130
|
+
for raw in (bad, "0X" + bad[2:], bad[2:]):
|
|
1121
1131
|
with pytest.raises(ValueError, match="invalid address checksum"):
|
|
1122
1132
|
_vault_arg(raw)
|
|
1123
1133
|
|
|
@@ -636,6 +636,26 @@ class TestResolve:
|
|
|
636
636
|
"chainlink_feed_registry": None,
|
|
637
637
|
}
|
|
638
638
|
|
|
639
|
+
def test_manager_delegating_to_itself_ends_as_a_cycle(self):
|
|
640
|
+
r = FakeReader()
|
|
641
|
+
weth = addr(1)
|
|
642
|
+
mw = addr(0x51)
|
|
643
|
+
zero = Web3.to_checksum_address(om.ZERO_ADDRESS)
|
|
644
|
+
r.symbols[weth] = "WETH"
|
|
645
|
+
r.decimals[weth] = 18
|
|
646
|
+
r.sources[weth] = zero
|
|
647
|
+
r.prices[weth] = Price(asset=weth, amount=2_000 * 10**18, decimals=18)
|
|
648
|
+
r.underlying_mw = mw
|
|
649
|
+
r.delegated = r # the manager names itself as its underlying middleware
|
|
650
|
+
|
|
651
|
+
node = om.resolve_asset(r, weth, max_depth=50)
|
|
652
|
+
|
|
653
|
+
# one hop back to the same manager, not 50 down to max_depth
|
|
654
|
+
(dep,) = node.dependencies
|
|
655
|
+
assert dep.reason == "cycle_detected"
|
|
656
|
+
assert not dep.dependencies
|
|
657
|
+
assert node.status == "partially_resolved"
|
|
658
|
+
|
|
639
659
|
def test_zero_underlying_middleware_treated_as_absent(self):
|
|
640
660
|
r = FakeReader()
|
|
641
661
|
asset = addr(1)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from dataclasses import dataclass
|
|
2
2
|
|
|
3
3
|
import pytest
|
|
4
|
+
from _addresses import miscased
|
|
4
5
|
from eth_abi import encode
|
|
5
6
|
from hexbytes import HexBytes
|
|
6
7
|
from web3 import Web3
|
|
@@ -161,10 +162,8 @@ class TestChecksumParsers:
|
|
|
161
162
|
for bad in ("0x1234", " " + self.LOWER):
|
|
162
163
|
with pytest.raises(ValueError):
|
|
163
164
|
parser(bad)
|
|
164
|
-
i = next(i for i, c in enumerate(checksum) if c in "abcdefABCDEF")
|
|
165
|
-
miscased = checksum[:i] + checksum[i].swapcase() + checksum[i + 1 :]
|
|
166
165
|
with pytest.raises(ValueError, match="invalid address checksum"):
|
|
167
|
-
parser(miscased)
|
|
166
|
+
parser(miscased(checksum))
|
|
168
167
|
# Two characters swapped: a different address that looks right.
|
|
169
168
|
swapped = checksum[:2] + checksum[3] + checksum[2] + checksum[4:]
|
|
170
169
|
assert swapped[2:4] != checksum[2:4]
|
|
@@ -5,7 +5,7 @@ import pytest
|
|
|
5
5
|
from ipor_fusion import Web3Context
|
|
6
6
|
from ipor_fusion.cache import NullCache
|
|
7
7
|
from ipor_fusion.types import ChainId
|
|
8
|
-
from ipor_fusion.vault_data import _pinned_default_block
|
|
8
|
+
from ipor_fusion.vault_data import _latest_head, _pinned_default_block
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
def _ctx() -> tuple[Web3Context, MagicMock]:
|
|
@@ -32,3 +32,13 @@ def test_tag_or_hash_resolves_to_its_number_once(tag: str):
|
|
|
32
32
|
ctx, web3 = _ctx()
|
|
33
33
|
assert _pinned_default_block(ctx.at_block(tag)) == 4242 # type: ignore[arg-type]
|
|
34
34
|
web3.eth.get_block.assert_called_once_with(tag)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def test_latest_head_reads_number_and_timestamp_in_one_call():
|
|
38
|
+
# The head number and then get_block(number) can reach a node behind a
|
|
39
|
+
# load balancer that has not seen that block yet.
|
|
40
|
+
web3 = MagicMock()
|
|
41
|
+
web3.eth.get_block.return_value = {"number": 4242, "timestamp": 1_700_000_000}
|
|
42
|
+
ctx = Web3Context(web3, ChainId(1), cache=NullCache())
|
|
43
|
+
assert _latest_head(ctx) == (4242, 1_700_000_000)
|
|
44
|
+
web3.eth.get_block.assert_called_once_with("latest")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|