ipor-fusion 4.0.0.dev222__tar.gz → 4.0.0.dev231__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.dev222 → ipor_fusion-4.0.0.dev231}/AGENTS.md +5 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/MIGRATION_4.md +25 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/PKG-INFO +1 -1
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/pyproject.toml +1 -1
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/__init__.py +6 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/cli/vault_markets_view.py +11 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/mcp/models.py +25 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/readers/__init__.py +4 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/readers/lending_health.py +104 -24
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/readers/unpriceable.py +33 -7
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/types.py +1 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/validation.py +40 -1
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/vault_data.py +27 -3
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/vault_info.py +21 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/vault_positions.py +10 -2
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/fixtures/vault_info_golden/full.json +1 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/fixtures/vault_info_golden/full.mcp.json +1 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/fixtures/vault_info_golden/rich.json +1 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/fixtures/vault_info_golden/rich.mcp.json +1 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_cli_helpers.py +27 -3
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_cli_lending_health.py +49 -26
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_mcp_models.py +9 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_public_api.py +6 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_unpriceable_tokens.py +11 -2
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_validation.py +145 -2
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_vault_info_fetched_golden.py +1 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/.claude-plugin/marketplace.json +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/.claude-plugin/plugin.json +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/.env.example +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/.gemini/settings.json +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/.git-blame-ignore-revs +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/.github/workflows/cd.yml +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/.github/workflows/ci.yml +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/.github/workflows/commit-lint.yml +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/.github/workflows/notify-slack.yml +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/.github/workflows/python-build.yml +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/.github/workflows/python-publish.yml +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/.github/workflows/release.yml +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/.github/workflows/report-slack.yml +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/.gitignore +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/.mcp.json +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/.pre-commit-config.yaml +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/.python-version +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/CHANGELOG.md +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/CLAUDE.md +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/CODEOWNERS +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/LICENSE +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/README.md +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/build.sh +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/examples/CONTRIBUTING.md +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/examples/README.md +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/examples/advanced_euler_v2_credit_market_base.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/examples/advanced_euler_v2_looping_arbitrum.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/examples/composed_hypercore_flow_arbitrum_hyperevm.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/examples/crosschain_ccip_usdc_arbitrum_hyperevm.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/examples/external_state_margin_leg_base.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/examples/simple_aave_v3_supply_base.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/pytest.ini +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/scripts/commit-check.sh +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/scripts/crosschain_readiness.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/skills/ipor-deploy-vault/SKILL.md +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/about.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/cache.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/chains.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/cli/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/cli/changelog_cmd.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/cli/config_cmd.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/cli/config_store.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/cli/main.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/cli/market_cmd.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/cli/options.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/cli/oracle_mapping_view.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/cli/vault_cmd.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/cli/vault_health.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/cli/vault_info_view.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/cli/vault_rendering.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/config/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/config/roles.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/core/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/core/access.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/core/address.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/core/context.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/core/contract.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/core/erc20.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/core/external_state_executor.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/core/fee_manager.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/core/fuse.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/core/fuse_action.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/core/fusion_factory.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/core/logs.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/core/multicall.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/core/oracle.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/core/plasma_vault.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/core/rewards_manager.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/core/simulation.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/core/simulation_result.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/core/withdraw_manager.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/crosschain/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/crosschain/ccip/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/crosschain/ccip/chainlink.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/crosschain/ccip/codec.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/crosschain/ccip/contracts.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/crosschain/ccip/events.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/crosschain/ccip/indexer.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/crosschain/ccip/lane.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/crosschain/ccip/quote.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/crosschain/ccip/transport.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/crosschain/contracts.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/crosschain/discovery.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/crosschain/errors.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/crosschain/fuses/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/crosschain/fuses/base.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/crosschain/fuses/ccip.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/crosschain/fuses/stargate.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/crosschain/lane.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/crosschain/logs.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/crosschain/messages.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/crosschain/simulation.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/crosschain/stargate/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/crosschain/stargate/contracts.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/crosschain/stargate/lane.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/crosschain/stargate/layerzero.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/crosschain/stargate/transport.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/crosschain/transport.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/dependency_graph.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/entry_cli.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/entry_mcp.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/errors.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/explorer.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/field_docs.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/fuses/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/fuses/aave_v3.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/fuses/async_action.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/fuses/base.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/fuses/compound_v3.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/fuses/erc4626.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/fuses/euler_v2.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/fuses/events.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/fuses/external_state.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/fuses/external_state_event_decoding.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/fuses/fluid_instadapp.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/fuses/gearbox_v3.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/fuses/hypercore.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/fuses/hypercore_event_decoding.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/fuses/merkl.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/fuses/morpho.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/fuses/ramses_v2.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/fuses/uniswap_v3.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/fuses/universal.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/guide/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/guide/architecture.md +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/guide/glossary.md +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/guide/invariants.md +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/guide/quickstart.md +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/hypercore/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/market_ids.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/market_info.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/mcp/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/mcp/guide.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/mcp/server.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/morpho_api.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/py.typed +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/readers/aave_v3.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/readers/aave_v4.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/readers/compound_v3.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/readers/hypercore.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/readers/hypercore_outcome.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/readers/morpho.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/readers/oracle_mapping.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/readers/oracle_mapping_nodes.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/readers/oracle_mapping_reader.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/readers/position_manager.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/readers/ramses_v2.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/readers/uniswap_v3.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/substrates.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/vault_erc20.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/vault_managers.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/vault_reads.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/UniswapV3UniversalRouter.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/_crosschain.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/_crosschain_lifecycle.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/_crosschain_recovery.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/_euler_v2.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/_event_args.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/_fake_rpc.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/_foundry.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/_golden.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/_guide.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/_hypercore_model.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/_hypercore_shadow.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/_live.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/_multicall.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/_oracle_mapping_fake.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/_simulate.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/_validation.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/addresses.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/conftest.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/constants.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/fixtures/hypercore_hip3_flow.json +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/fixtures/hypercore_market55.json +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/fixtures/market_golden/meta_morpho_v1.json +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/fixtures/market_golden/meta_morpho_v1.mcp.json +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/fixtures/market_golden/meta_morpho_v2.json +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/fixtures/market_golden/meta_morpho_v2.mcp.json +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/fixtures/market_golden/morpho_api/market.json +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/fixtures/market_golden/morpho_api/vault_v1.json +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/fixtures/market_golden/morpho_api/vault_v2.json +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/fixtures/market_golden/morpho_api/vault_v2_not_found.json +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/fixtures/market_golden/morpho_blue.json +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/fixtures/market_golden/morpho_blue.mcp.json +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/fixtures/market_golden/morpho_blue_api_error.json +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/fixtures/market_golden/morpho_blue_api_error.mcp.json +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/fixtures/market_golden/morpho_blue_no_api.json +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/fixtures/market_golden/morpho_blue_no_api.mcp.json +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/fixtures/oracle_mapping_golden/mapping.json +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/fixtures/oracle_mapping_golden/mapping.mcp.json +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/fixtures/simulate_v1_request.json +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/fixtures/vault_info_golden/clean.json +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/fixtures/vault_info_golden/clean.mcp.json +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/fixtures/vault_info_golden/clean.txt +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/fixtures/vault_info_golden/deployment_error.json +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/fixtures/vault_info_golden/deployment_error.mcp.json +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/fixtures/vault_info_golden/deployment_error.txt +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/fixtures/vault_info_golden/fetched.cli.json +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/fixtures/vault_info_golden/fetched.json +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/fixtures/vault_info_golden/fetched.mcp.json +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/fixtures/vault_info_golden/full.txt +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/fixtures/vault_info_golden/rich.txt +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_about.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_access_manager.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_cache.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_chains.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_cli_changelog.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_cli_commands.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_cli_config_store.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_cli_hypercore.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_cli_market_golden.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_cli_market_morpho_blue.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_cli_oracle_mapping.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_cli_oracle_mapping_golden.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_cli_pending_requests_scan.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_cli_role_accounts.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_cli_vault_info_golden.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_cli_vault_predates_getters.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_cli_vault_validate.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_contract_call.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_contract_encoder.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_crosschain_ccip_events.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_crosschain_ccip_indexer.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_crosschain_ccip_quote.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_crosschain_chainlink.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_crosschain_contracts.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_crosschain_encoding.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_crosschain_errors.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_crosschain_lane.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_crosschain_layering.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_crosschain_readiness.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_crosschain_readiness_report.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_crosschain_transport.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_docs_typing.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_erc20.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_error_hierarchy.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_examples_composed_hypercore.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_examples_crosschain.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_examples_euler_looping_arbitrum.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_examples_vaults.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_explorer.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_external_state_events.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_external_state_executor.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_fee_manager.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_fuse_encoding.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_fusion_instance.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_guide.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_hypercore_deployment.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_hypercore_events.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_hypercore_flow.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_hypercore_live.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_hypercore_outcome.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_hypercore_readers.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_hypercore_simulation.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_logs.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_mcp_hypercore.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_mcp_server.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_mcp_vault_validate.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_multicall.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_oracle.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_oracle_mapping.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_plasma_vault.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_public_type_reachability.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_reader_encoding.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_readiness_selection.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_revert_decoding.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_rewards_manager.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_roles.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_sdk_layering.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_simulate_borrow_aave_v3_mainnet.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_simulate_claim_merkl_wrapper_base.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_simulate_claim_morpho_rewards.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_simulate_crosschain_canary.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_simulate_crosschain_hypercore.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_simulate_crosschain_hyperevm.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_simulate_crosschain_lifecycle.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_simulate_euler_v2_batch_base.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_simulate_euler_v2_credit_market_base.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_simulate_euler_v2_swap_deploy_base.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_simulate_euler_v2_swap_reconfigure_base.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_simulate_euler_v2_swap_registry_base.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_simulate_examples_arbitrum.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_simulate_examples_base.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_simulate_examples_crosschain.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_simulate_external_state_executor_base.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_simulate_fusion_factory_clone_base.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_simulate_looping_aave_v3_base.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_simulate_looping_morpho_blue_base.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_simulate_morpho_blue.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_simulate_pilot_v3.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_simulate_pilot_v4_uniswap.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_simulate_pilot_v5_ramses.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_simulate_pilot_v5_scheduled.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_simulate_plasma_vault_setters_base.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_simulate_readers_integration.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_simulate_release_funds.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_simulate_swap_on_base.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_simulate_vault_from_scratch_base.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_simulation_errors.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_simulation_observation.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_solidity_compiler.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_solidity_mirrors.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_substrates.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_types_and_events.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_vault_data_block.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_vault_dep_graph.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_vault_info_onchain_base.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_vault_info_onchain_base_no_withdraw_manager.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_vault_reconciliation_onchain_base.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_vault_simulator_blocks.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_web3_context.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/test_withdraw_manager.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/typed_client/consumer.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/tests/typed_client/rejected.py +0 -0
- {ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/uv.lock +0 -0
|
@@ -533,6 +533,11 @@ decoder adds its own fields.
|
|
|
533
533
|
- `ZeroBalanceFuse` markets are capabilities (swap, flash-loan,
|
|
534
534
|
instant-withdrawal, admin) with structurally zero balance, not venues. They
|
|
535
535
|
go in `zero_balance_fuses` and are not counted as liquidity markets.
|
|
536
|
+
- A validation input that cannot be read is never a silent `ok`: a lending
|
|
537
|
+
position whose reads fail is an `UnreadableLendingMarket` (a `lending_health`
|
|
538
|
+
warning), a failed lending-health or unpriceable-token read is named in
|
|
539
|
+
`VaultData.failed_reads` (an `incomplete_read` warning). Catch the chain and
|
|
540
|
+
transport errors only; degrade-to-`None` is for display-only data.
|
|
536
541
|
- Reconciliation: `totalAssets = balance_fuses_sum + underlying_on_vault +
|
|
537
542
|
rewards_manager_balance` (`PlasmaVault._getGrossTotalAssets` adds
|
|
538
543
|
`IRewardsClaimManager.balanceOf()`, the vested rewards, when a manager is set;
|
|
@@ -990,6 +990,31 @@ and `log_index` was passed through as given (a hex string from a raw log); the e
|
|
|
990
990
|
These apply to every caller, type-checked or not. Changes described in full above
|
|
991
991
|
are listed with a link.
|
|
992
992
|
|
|
993
|
+
- **A failed lending read is a warning, never a silent `ok`.** 3.x caught every
|
|
994
|
+
exception in the lending-health readers and dropped the position, so a provider
|
|
995
|
+
outage could leave no lending finding and an `ok` verdict. Now:
|
|
996
|
+
- A position whose reads fail with a chain or transport error (revert, RPC,
|
|
997
|
+
timeout, no or malformed data) becomes an `UnreadableLendingMarket` in
|
|
998
|
+
`VaultLendingHealth.unreadable`.
|
|
999
|
+
- Each unreadable position is a `lending_health` warning.
|
|
1000
|
+
- An Aave V3 market whose Pool could not be resolved is listed the same way
|
|
1001
|
+
instead of being skipped.
|
|
1002
|
+
- Any other exception propagates.
|
|
1003
|
+
- When the whole lending-health read or the unpriceable-token check fails with
|
|
1004
|
+
a revert, an RPC error or a timeout, `VaultData.failed_reads` names it and
|
|
1005
|
+
validation reports an `incomplete_read` warning. A transport failure (no
|
|
1006
|
+
connection) still aborts the fetch rather than return a snapshot.
|
|
1007
|
+
- A substrate the unpriceable-token check could not cover is named the same
|
|
1008
|
+
way (`unpriceable_priced_tokens: <what>`). Examples: a Morpho position whose
|
|
1009
|
+
read failed, or a reverting `asset()` / `getSilos()`.
|
|
1010
|
+
`fetch_unpriceable_priced_tokens` returns an `UnpriceableCheck` (`tokens`,
|
|
1011
|
+
`unread`) instead of a bare list.
|
|
1012
|
+
|
|
1013
|
+
The `lending_health` JSON block of `vault info` and `vault validate` carries
|
|
1014
|
+
`unreadable`, and a custom `VaultState` adds the `failed_reads` property. A
|
|
1015
|
+
position missing from the breakdown tables is shown as absent, while the
|
|
1016
|
+
checks it feeds report it.
|
|
1017
|
+
|
|
993
1018
|
- **More markets decode their substrates.** VELODROME_SUPERCHAIN (31, Gauge /
|
|
994
1019
|
Pool), AGUA_GLOBAL_CARRY (51, VAULT / ASSET), TERM_FINANCE (52, SERVICER address
|
|
995
1020
|
or COLLATERAL_TOKEN pair key), META_MORPHO_0001, MERKL and
|
|
@@ -275,6 +275,8 @@ from ipor_fusion.readers import (
|
|
|
275
275
|
RamsesV2Reader,
|
|
276
276
|
UniswapV3Position,
|
|
277
277
|
UniswapV3Reader,
|
|
278
|
+
UnpriceableCheck,
|
|
279
|
+
UnreadableLendingMarket,
|
|
278
280
|
VaultLendingHealth,
|
|
279
281
|
build_oracle_mapping,
|
|
280
282
|
fetch_unpriceable_priced_tokens,
|
|
@@ -385,6 +387,7 @@ from ipor_fusion.vault_info import (
|
|
|
385
387
|
ReconciliationJson,
|
|
386
388
|
SharePriceJson,
|
|
387
389
|
SubstrateEntryJson,
|
|
390
|
+
UnreadableLendingMarketJson,
|
|
388
391
|
UsdAmountJson,
|
|
389
392
|
VaultAssetJson,
|
|
390
393
|
VaultInfoJson,
|
|
@@ -631,6 +634,7 @@ __all__ = [
|
|
|
631
634
|
"HYPEREVM_CHAIN_ID",
|
|
632
635
|
"LendingMarketHealth",
|
|
633
636
|
"VaultLendingHealth",
|
|
637
|
+
"UnreadableLendingMarket",
|
|
634
638
|
"fetch_vault_lending_health",
|
|
635
639
|
"OracleAsset",
|
|
636
640
|
"OracleMapping",
|
|
@@ -651,6 +655,7 @@ __all__ = [
|
|
|
651
655
|
"build_oracle_mapping",
|
|
652
656
|
"OracleMappingReader",
|
|
653
657
|
"MiddlewarePricedToken",
|
|
658
|
+
"UnpriceableCheck",
|
|
654
659
|
"fetch_unpriceable_priced_tokens",
|
|
655
660
|
"PositionData",
|
|
656
661
|
"UniswapV3Reader",
|
|
@@ -701,6 +706,7 @@ __all__ = [
|
|
|
701
706
|
"HyperCorePerpMarketJson",
|
|
702
707
|
"HyperCoreSpotLegJson",
|
|
703
708
|
"LendingHealthJson",
|
|
709
|
+
"UnreadableLendingMarketJson",
|
|
704
710
|
"LendingMarketJson",
|
|
705
711
|
"ManagersJson",
|
|
706
712
|
"MorphoBreakdownJson",
|
{ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/cli/vault_markets_view.py
RENAMED
|
@@ -95,6 +95,17 @@ def _print_lending_health(data: VaultData) -> None: # noqa: C901
|
|
|
95
95
|
if m.protocol == "aave_v4":
|
|
96
96
|
click.echo(f" spoke {m.substrate_id}:")
|
|
97
97
|
_print_health_lines(m, indent=" ")
|
|
98
|
+
_print_unreadable_health(lh)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _print_unreadable_health(lh: VaultLendingHealth | None) -> None:
|
|
102
|
+
for u in lh.unreadable if lh is not None else []:
|
|
103
|
+
where = f" {u.substrate_id}" if u.substrate_id else ""
|
|
104
|
+
click.secho(
|
|
105
|
+
f" {format_market_label(u.market_id)}: {u.protocol}{where} "
|
|
106
|
+
f"health unknown — {u.reason}",
|
|
107
|
+
fg="yellow",
|
|
108
|
+
)
|
|
98
109
|
|
|
99
110
|
|
|
100
111
|
def _index_lending_health(
|
|
@@ -431,8 +431,20 @@ class LendingMarketHealth(_Base):
|
|
|
431
431
|
is_critical: bool
|
|
432
432
|
|
|
433
433
|
|
|
434
|
+
class UnreadableLendingMarket(_Base):
|
|
435
|
+
"""A lending position whose health could not be read: unknown, not
|
|
436
|
+
healthy."""
|
|
437
|
+
|
|
438
|
+
protocol: str
|
|
439
|
+
market_id: MarketId
|
|
440
|
+
market_name: str
|
|
441
|
+
substrate_id: str | None
|
|
442
|
+
reason: str
|
|
443
|
+
|
|
444
|
+
|
|
434
445
|
class LendingHealth(_Base):
|
|
435
446
|
markets: list[LendingMarketHealth]
|
|
447
|
+
unreadable: list[UnreadableLendingMarket] = Field(default_factory=list)
|
|
436
448
|
worst_ltv_usage_percent: float | None
|
|
437
449
|
|
|
438
450
|
|
|
@@ -875,12 +887,25 @@ class LendingMarketModel(_Base):
|
|
|
875
887
|
|
|
876
888
|
class LendingHealthModel(_Base):
|
|
877
889
|
markets: list[LendingMarketModel]
|
|
890
|
+
unreadable: list[UnreadableLendingMarket] = Field(
|
|
891
|
+
description="Positions whose health could not be read: unknown, not healthy."
|
|
892
|
+
)
|
|
878
893
|
worst_ltv_usage_percent: float | None
|
|
879
894
|
|
|
880
895
|
@classmethod
|
|
881
896
|
def from_health(cls, health: VaultLendingHealth) -> LendingHealthModel:
|
|
882
897
|
return cls(
|
|
883
898
|
markets=[LendingMarketModel.from_market(m) for m in health.markets],
|
|
899
|
+
unreadable=[
|
|
900
|
+
UnreadableLendingMarket(
|
|
901
|
+
protocol=u.protocol,
|
|
902
|
+
market_id=u.market_id,
|
|
903
|
+
market_name=u.market_name,
|
|
904
|
+
substrate_id=u.substrate_id,
|
|
905
|
+
reason=u.reason,
|
|
906
|
+
)
|
|
907
|
+
for u in health.unreadable
|
|
908
|
+
],
|
|
884
909
|
worst_ltv_usage_percent=health.worst_ltv_usage,
|
|
885
910
|
)
|
|
886
911
|
|
|
@@ -17,6 +17,7 @@ from ipor_fusion.readers.hypercore import (
|
|
|
17
17
|
)
|
|
18
18
|
from ipor_fusion.readers.lending_health import (
|
|
19
19
|
LendingMarketHealth,
|
|
20
|
+
UnreadableLendingMarket,
|
|
20
21
|
VaultLendingHealth,
|
|
21
22
|
fetch_vault_lending_health,
|
|
22
23
|
)
|
|
@@ -56,6 +57,7 @@ from ipor_fusion.readers.ramses_v2 import RamsesV2Position, RamsesV2Reader
|
|
|
56
57
|
from ipor_fusion.readers.uniswap_v3 import UniswapV3Position, UniswapV3Reader
|
|
57
58
|
from ipor_fusion.readers.unpriceable import (
|
|
58
59
|
MiddlewarePricedToken,
|
|
60
|
+
UnpriceableCheck,
|
|
59
61
|
fetch_unpriceable_priced_tokens,
|
|
60
62
|
)
|
|
61
63
|
|
|
@@ -80,6 +82,7 @@ __all__ = [
|
|
|
80
82
|
"HYPEREVM_CHAIN_ID",
|
|
81
83
|
"LendingMarketHealth",
|
|
82
84
|
"VaultLendingHealth",
|
|
85
|
+
"UnreadableLendingMarket",
|
|
83
86
|
"fetch_vault_lending_health",
|
|
84
87
|
"OracleAsset",
|
|
85
88
|
"OracleMapping",
|
|
@@ -100,6 +103,7 @@ __all__ = [
|
|
|
100
103
|
"build_oracle_mapping",
|
|
101
104
|
"OracleMappingReader",
|
|
102
105
|
"MiddlewarePricedToken",
|
|
106
|
+
"UnpriceableCheck",
|
|
103
107
|
"fetch_unpriceable_priced_tokens",
|
|
104
108
|
"PositionData",
|
|
105
109
|
"UniswapV3Reader",
|
{ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/readers/lending_health.py
RENAMED
|
@@ -8,15 +8,25 @@ from __future__ import annotations
|
|
|
8
8
|
|
|
9
9
|
import logging
|
|
10
10
|
from concurrent.futures import ThreadPoolExecutor
|
|
11
|
-
from dataclasses import dataclass
|
|
11
|
+
from dataclasses import dataclass, field
|
|
12
12
|
|
|
13
|
+
import requests
|
|
13
14
|
from eth_abi import decode
|
|
15
|
+
from eth_abi.exceptions import DecodingError
|
|
14
16
|
from eth_typing import ChecksumAddress
|
|
15
17
|
from eth_utils import function_signature_to_4byte_selector
|
|
16
18
|
from web3 import Web3
|
|
19
|
+
from web3.exceptions import (
|
|
20
|
+
BadFunctionCallOutput,
|
|
21
|
+
ContractLogicError,
|
|
22
|
+
ProviderConnectionError,
|
|
23
|
+
TimeExhausted,
|
|
24
|
+
Web3RPCError,
|
|
25
|
+
)
|
|
17
26
|
|
|
18
27
|
from ipor_fusion.core.context import Web3Context
|
|
19
28
|
from ipor_fusion.core.multicall import Multicall3
|
|
29
|
+
from ipor_fusion.errors import EmptyCallResultError
|
|
20
30
|
from ipor_fusion.market_ids import IporFusionMarkets
|
|
21
31
|
from ipor_fusion.readers.aave_v3 import AaveV3Reader
|
|
22
32
|
from ipor_fusion.readers.aave_v4 import AaveV4OracleReader, AaveV4SpokeReader
|
|
@@ -36,6 +46,19 @@ from ipor_fusion.types import (
|
|
|
36
46
|
|
|
37
47
|
_logger = logging.getLogger(__name__)
|
|
38
48
|
|
|
49
|
+
# What a lending read can fail with that is about the chain, not the code:
|
|
50
|
+
# reverts, RPC and transport errors, a contract answering no or malformed data.
|
|
51
|
+
_READ_ERRORS = (
|
|
52
|
+
ContractLogicError,
|
|
53
|
+
Web3RPCError,
|
|
54
|
+
TimeExhausted,
|
|
55
|
+
BadFunctionCallOutput,
|
|
56
|
+
ProviderConnectionError,
|
|
57
|
+
requests.RequestException,
|
|
58
|
+
EmptyCallResultError,
|
|
59
|
+
DecodingError,
|
|
60
|
+
)
|
|
61
|
+
|
|
39
62
|
WAD = 10**18
|
|
40
63
|
RAY = 10**27
|
|
41
64
|
ORACLE_PRICE_SCALE = 10**36
|
|
@@ -121,15 +144,35 @@ class LendingMarketHealth:
|
|
|
121
144
|
return self.health_factor <= 1.05
|
|
122
145
|
|
|
123
146
|
|
|
147
|
+
@dataclass(slots=True)
|
|
148
|
+
class UnreadableLendingMarket:
|
|
149
|
+
"""A lending position whose health could not be read: it is unknown, not
|
|
150
|
+
healthy. ``substrate_id`` as in `LendingMarketHealth`."""
|
|
151
|
+
|
|
152
|
+
protocol: str
|
|
153
|
+
market_id: MarketId
|
|
154
|
+
market_name: str
|
|
155
|
+
reason: str
|
|
156
|
+
substrate_id: str | None = None
|
|
157
|
+
|
|
158
|
+
|
|
124
159
|
@dataclass(slots=True)
|
|
125
160
|
class VaultLendingHealth:
|
|
126
|
-
"""Aggregated lending health for all lending markets in a vault.
|
|
161
|
+
"""Aggregated lending health for all lending markets in a vault.
|
|
162
|
+
|
|
163
|
+
``unreadable`` lists the positions whose reads failed; the health is
|
|
164
|
+
complete only when it is empty."""
|
|
127
165
|
|
|
128
166
|
markets: list[LendingMarketHealth]
|
|
167
|
+
unreadable: list[UnreadableLendingMarket] = field(default_factory=list)
|
|
129
168
|
|
|
130
169
|
@property
|
|
131
170
|
def has_lending_positions(self) -> bool:
|
|
132
|
-
return len(self.markets) > 0
|
|
171
|
+
return len(self.markets) > 0 or len(self.unreadable) > 0
|
|
172
|
+
|
|
173
|
+
@property
|
|
174
|
+
def is_complete(self) -> bool:
|
|
175
|
+
return not self.unreadable
|
|
133
176
|
|
|
134
177
|
@property
|
|
135
178
|
def worst_ltv_usage(self) -> float | None:
|
|
@@ -137,6 +180,12 @@ class VaultLendingHealth:
|
|
|
137
180
|
return max(usages) if usages else None
|
|
138
181
|
|
|
139
182
|
|
|
183
|
+
def _reason(what: str, exc: Exception) -> str:
|
|
184
|
+
"""Why a read failed, for the unreadable entry and its finding."""
|
|
185
|
+
_logger.debug("lending %s read failed: %s: %s", what, type(exc).__name__, exc)
|
|
186
|
+
return f"{what} read failed: {type(exc).__name__}"
|
|
187
|
+
|
|
188
|
+
|
|
140
189
|
def _call_morpho_oracle_price(ctx: Web3Context, oracle_address: ChecksumAddress) -> int:
|
|
141
190
|
"""Call IOracle(oracle).price() -> uint256."""
|
|
142
191
|
selector = function_signature_to_4byte_selector("price()")
|
|
@@ -152,7 +201,7 @@ def _compute_morpho_market_health(
|
|
|
152
201
|
vault_address: PlasmaVaultAddress,
|
|
153
202
|
ipor_market_id: MarketId,
|
|
154
203
|
market_name: str,
|
|
155
|
-
) -> LendingMarketHealth |
|
|
204
|
+
) -> LendingMarketHealth | UnreadableLendingMarket:
|
|
156
205
|
"""Compute LTV health for a single Morpho Blue market position."""
|
|
157
206
|
batch = Multicall3(ctx).batch()
|
|
158
207
|
position_read = batch.add(reader.position(morpho_market_id, vault_address))
|
|
@@ -160,9 +209,14 @@ def _compute_morpho_market_health(
|
|
|
160
209
|
params_read = batch.add(reader.market_params(morpho_market_id))
|
|
161
210
|
try:
|
|
162
211
|
batch.run()
|
|
163
|
-
except
|
|
164
|
-
|
|
165
|
-
|
|
212
|
+
except _READ_ERRORS as exc:
|
|
213
|
+
return UnreadableLendingMarket(
|
|
214
|
+
protocol="morpho",
|
|
215
|
+
market_id=ipor_market_id,
|
|
216
|
+
market_name=market_name,
|
|
217
|
+
reason=_reason("market data", exc),
|
|
218
|
+
substrate_id=morpho_market_id,
|
|
219
|
+
)
|
|
166
220
|
position, market, params = (
|
|
167
221
|
position_read.value,
|
|
168
222
|
market_read.value,
|
|
@@ -186,9 +240,14 @@ def _compute_morpho_market_health(
|
|
|
186
240
|
|
|
187
241
|
try:
|
|
188
242
|
oracle_price = _call_morpho_oracle_price(ctx, params.oracle)
|
|
189
|
-
except
|
|
190
|
-
|
|
191
|
-
|
|
243
|
+
except _READ_ERRORS as exc:
|
|
244
|
+
return UnreadableLendingMarket(
|
|
245
|
+
protocol="morpho",
|
|
246
|
+
market_id=ipor_market_id,
|
|
247
|
+
market_name=market_name,
|
|
248
|
+
reason=_reason("oracle price", exc),
|
|
249
|
+
substrate_id=morpho_market_id,
|
|
250
|
+
)
|
|
192
251
|
|
|
193
252
|
borrowed = to_assets_up(
|
|
194
253
|
position.borrow_shares, market.total_borrow_assets, market.total_borrow_shares
|
|
@@ -246,13 +305,18 @@ def _compute_aave_market_health(
|
|
|
246
305
|
vault_address: PlasmaVaultAddress,
|
|
247
306
|
ipor_market_id: MarketId,
|
|
248
307
|
market_name: str,
|
|
249
|
-
) -> LendingMarketHealth |
|
|
308
|
+
) -> LendingMarketHealth | UnreadableLendingMarket:
|
|
250
309
|
"""Compute LTV health for the vault's Aave V3 position."""
|
|
251
310
|
try:
|
|
252
311
|
data = reader.get_user_account_data(vault_address).call(ctx)
|
|
253
|
-
except
|
|
254
|
-
|
|
255
|
-
|
|
312
|
+
except _READ_ERRORS as exc:
|
|
313
|
+
return UnreadableLendingMarket(
|
|
314
|
+
protocol="aave_v3",
|
|
315
|
+
market_id=ipor_market_id,
|
|
316
|
+
market_name=market_name,
|
|
317
|
+
reason=_reason("account data", exc),
|
|
318
|
+
substrate_id=None,
|
|
319
|
+
)
|
|
256
320
|
|
|
257
321
|
# No debt = no risk
|
|
258
322
|
if data.total_debt_base == 0:
|
|
@@ -300,16 +364,21 @@ def _compute_aave_v4_market_health(
|
|
|
300
364
|
vault_address: PlasmaVaultAddress,
|
|
301
365
|
ipor_market_id: MarketId,
|
|
302
366
|
market_name: str,
|
|
303
|
-
) -> LendingMarketHealth |
|
|
367
|
+
) -> LendingMarketHealth | UnreadableLendingMarket:
|
|
304
368
|
"""Compute LTV health for the vault's account on one Aave V4 Spoke."""
|
|
305
369
|
reader = AaveV4SpokeReader(spoke)
|
|
306
370
|
try:
|
|
307
371
|
data = reader.get_user_account_data(vault_address).call(ctx)
|
|
308
372
|
oracle_decimals = AaveV4OracleReader(reader.oracle().call(ctx)).decimals()
|
|
309
373
|
value_scale = 10 ** (oracle_decimals.call(ctx) + 18)
|
|
310
|
-
except
|
|
311
|
-
|
|
312
|
-
|
|
374
|
+
except _READ_ERRORS as exc:
|
|
375
|
+
return UnreadableLendingMarket(
|
|
376
|
+
protocol="aave_v4",
|
|
377
|
+
market_id=ipor_market_id,
|
|
378
|
+
market_name=market_name,
|
|
379
|
+
reason=_reason("account data", exc),
|
|
380
|
+
substrate_id=spoke,
|
|
381
|
+
)
|
|
313
382
|
|
|
314
383
|
max_ltv = data.avg_collateral_factor / WAD
|
|
315
384
|
current_ltv: float | None
|
|
@@ -355,6 +424,7 @@ class _LendingMarkets:
|
|
|
355
424
|
morpho: list[tuple[MarketId, str, MorphoBlueMarketId]]
|
|
356
425
|
aave_v3: list[tuple[MarketId, str, ChecksumAddress]]
|
|
357
426
|
aave_v4: list[tuple[MarketId, str, ChecksumAddress]]
|
|
427
|
+
unreadable: list[UnreadableLendingMarket]
|
|
358
428
|
|
|
359
429
|
|
|
360
430
|
def _collect_lending_markets(
|
|
@@ -364,7 +434,7 @@ def _collect_lending_markets(
|
|
|
364
434
|
) -> _LendingMarkets:
|
|
365
435
|
"""Pick the positions to read: Morpho substrates, Aave V3 markets with a Pool,
|
|
366
436
|
and the Spokes of Aave V4 markets."""
|
|
367
|
-
markets = _LendingMarkets(morpho=[], aave_v3=[], aave_v4=[])
|
|
437
|
+
markets = _LendingMarkets(morpho=[], aave_v3=[], aave_v4=[], unreadable=[])
|
|
368
438
|
|
|
369
439
|
for mid in balance_fuse_market_ids:
|
|
370
440
|
name = market_name(mid)
|
|
@@ -377,7 +447,14 @@ def _collect_lending_markets(
|
|
|
377
447
|
if aave_pool := aave_pools.get(mid):
|
|
378
448
|
markets.aave_v3.append((mid, name, aave_pool))
|
|
379
449
|
else:
|
|
380
|
-
|
|
450
|
+
markets.unreadable.append(
|
|
451
|
+
UnreadableLendingMarket(
|
|
452
|
+
protocol="aave_v3",
|
|
453
|
+
market_id=mid,
|
|
454
|
+
market_name=name,
|
|
455
|
+
reason="the balance fuse's Aave V3 Pool could not be read",
|
|
456
|
+
)
|
|
457
|
+
)
|
|
381
458
|
elif mid in AAVE_V4_MARKET_IDS:
|
|
382
459
|
for spoke in _aave_v4_spokes(market_substrates.get(mid, [])):
|
|
383
460
|
markets.aave_v4.append((mid, name, spoke))
|
|
@@ -402,8 +479,8 @@ def fetch_vault_lending_health(
|
|
|
402
479
|
market_substrates: Map of market_id -> list of raw substrate bytes.
|
|
403
480
|
Morpho market ids and Aave V4 Spokes are read from it.
|
|
404
481
|
aave_pools: Map of Aave V3 market_id -> the Pool its balance fuse
|
|
405
|
-
reads (`AaveV3FuseReader.pool`). Aave V3
|
|
406
|
-
|
|
482
|
+
reads (`AaveV3FuseReader.pool`). An Aave V3 market without an
|
|
483
|
+
entry is listed in ``unreadable``. Defaults to the chain's Core pool for AAVE_V3 only:
|
|
407
484
|
the Pools of Aave V3 Prime and SparkLend are known only to their
|
|
408
485
|
fuses.
|
|
409
486
|
"""
|
|
@@ -416,6 +493,7 @@ def fetch_vault_lending_health(
|
|
|
416
493
|
)
|
|
417
494
|
|
|
418
495
|
results: list[LendingMarketHealth] = []
|
|
496
|
+
unreadable = list(markets.unreadable)
|
|
419
497
|
|
|
420
498
|
with ThreadPoolExecutor() as pool:
|
|
421
499
|
futures = []
|
|
@@ -462,7 +540,9 @@ def fetch_vault_lending_health(
|
|
|
462
540
|
|
|
463
541
|
for fut in futures:
|
|
464
542
|
result = fut.result()
|
|
465
|
-
if result
|
|
543
|
+
if isinstance(result, UnreadableLendingMarket):
|
|
544
|
+
unreadable.append(result)
|
|
545
|
+
else:
|
|
466
546
|
results.append(result)
|
|
467
547
|
|
|
468
|
-
return VaultLendingHealth(markets=results)
|
|
548
|
+
return VaultLendingHealth(markets=results, unreadable=unreadable)
|
{ipor_fusion-4.0.0.dev222 → ipor_fusion-4.0.0.dev231}/src/ipor_fusion/readers/unpriceable.py
RENAMED
|
@@ -11,7 +11,7 @@ triggers it.
|
|
|
11
11
|
from __future__ import annotations
|
|
12
12
|
|
|
13
13
|
from collections.abc import Iterable
|
|
14
|
-
from dataclasses import dataclass, replace
|
|
14
|
+
from dataclasses import dataclass, field, replace
|
|
15
15
|
|
|
16
16
|
from eth_utils import function_signature_to_4byte_selector
|
|
17
17
|
from web3 import Web3
|
|
@@ -171,8 +171,21 @@ def _morpho_tokens(
|
|
|
171
171
|
]
|
|
172
172
|
|
|
173
173
|
|
|
174
|
+
@dataclass(frozen=True)
|
|
175
|
+
class UnpriceableCheck:
|
|
176
|
+
"""`fetch_unpriceable_priced_tokens`: the flagged ``tokens``, and in
|
|
177
|
+
``unread`` the granted substrates whose priced token could not be found
|
|
178
|
+
(a reverting ``asset()`` or ``getSilos()``): the check is complete only
|
|
179
|
+
when it is empty."""
|
|
180
|
+
|
|
181
|
+
tokens: list[MiddlewarePricedToken]
|
|
182
|
+
unread: list[str] = field(default_factory=list)
|
|
183
|
+
|
|
184
|
+
|
|
174
185
|
def _vault_asset_tokens(
|
|
175
|
-
ctx: Web3Context,
|
|
186
|
+
ctx: Web3Context,
|
|
187
|
+
market_substrates: dict[MarketId, list[bytes]],
|
|
188
|
+
unread: list[str],
|
|
176
189
|
) -> list[MiddlewarePricedToken]:
|
|
177
190
|
"""``asset()`` of granted ERC4626 vaults (Euler V2, generic ERC4626) and
|
|
178
191
|
of both silos of every granted Silo Config. All three fuses price it
|
|
@@ -196,6 +209,11 @@ def _vault_asset_tokens(
|
|
|
196
209
|
multicall = Multicall3(ctx)
|
|
197
210
|
if configs:
|
|
198
211
|
silo_pairs = multicall.try_aggregate([_silos_call(c) for c in configs])
|
|
212
|
+
unread += [
|
|
213
|
+
f"getSilos() of granted Silo Config {config}"
|
|
214
|
+
for config, silos in zip(configs, silo_pairs, strict=True)
|
|
215
|
+
if silos is None
|
|
216
|
+
]
|
|
199
217
|
vaults += [
|
|
200
218
|
(
|
|
201
219
|
IporFusionMarkets.SILO_V2,
|
|
@@ -211,6 +229,9 @@ def _vault_asset_tokens(
|
|
|
211
229
|
assets = multicall.try_aggregate(
|
|
212
230
|
[_address_call(vault, _ASSET_SELECTOR) for _, vault, _ in vaults]
|
|
213
231
|
)
|
|
232
|
+
unread += [
|
|
233
|
+
via for (_, _, via), asset in zip(vaults, assets, strict=True) if asset is None
|
|
234
|
+
]
|
|
214
235
|
return [
|
|
215
236
|
MiddlewarePricedToken(
|
|
216
237
|
market_id,
|
|
@@ -293,17 +314,20 @@ def fetch_unpriceable_priced_tokens(
|
|
|
293
314
|
balance_fuses: list[BalanceFuse],
|
|
294
315
|
market_substrates: dict[MarketId, list[bytes]],
|
|
295
316
|
morpho_positions: dict[MarketId, list[MorphoPositionBreakdown]] | None,
|
|
296
|
-
) ->
|
|
317
|
+
) -> UnpriceableCheck:
|
|
297
318
|
"""Every middleware-priced token of a granted substrate the middleware
|
|
298
|
-
cannot price, in market order
|
|
319
|
+
cannot price, in market order, and the substrates whose token could not
|
|
320
|
+
be found. ``morpho_positions`` must hold every Morpho substrate: a
|
|
321
|
+
position left out is a token left unchecked."""
|
|
322
|
+
unread: list[str] = []
|
|
299
323
|
priced = [
|
|
300
324
|
*_plain_tokens(market_substrates, Web3.to_checksum_address(underlying)),
|
|
301
325
|
*_morpho_tokens(morpho_positions),
|
|
302
|
-
*_vault_asset_tokens(ctx, market_substrates),
|
|
326
|
+
*_vault_asset_tokens(ctx, market_substrates, unread),
|
|
303
327
|
*_aave_v3_middleware_tokens(ctx, balance_fuses, market_substrates),
|
|
304
328
|
]
|
|
305
329
|
if not priced:
|
|
306
|
-
return []
|
|
330
|
+
return UnpriceableCheck(tokens=[], unread=unread)
|
|
307
331
|
oracle = price_oracle_middleware(ctx, Web3.to_checksum_address(price_oracle))
|
|
308
332
|
unpriceable = _unpriceable(ctx, oracle, (p.token for p in priced))
|
|
309
333
|
flagged = {
|
|
@@ -311,4 +335,6 @@ def fetch_unpriceable_priced_tokens(
|
|
|
311
335
|
for p in priced
|
|
312
336
|
if p.token in unpriceable
|
|
313
337
|
}
|
|
314
|
-
return
|
|
338
|
+
return UnpriceableCheck(
|
|
339
|
+
tokens=[flagged[key] for key in sorted(flagged)], unread=unread
|
|
340
|
+
)
|
|
@@ -153,6 +153,12 @@ class VaultState(Protocol):
|
|
|
153
153
|
def unpriceable_priced_tokens(self) -> Sequence[UnpriceableToken] | None: ...
|
|
154
154
|
@property
|
|
155
155
|
def unimplemented_getters(self) -> Sequence[str]: ...
|
|
156
|
+
@property
|
|
157
|
+
def failed_reads(self) -> Sequence[str]:
|
|
158
|
+
"""Validation inputs whose read failed (``lending_health``,
|
|
159
|
+
``unpriceable_priced_tokens``): their checks did not run."""
|
|
160
|
+
...
|
|
161
|
+
|
|
156
162
|
@property
|
|
157
163
|
def market_balances(self) -> Mapping[MarketId, Amount]: ...
|
|
158
164
|
@property
|
|
@@ -291,6 +297,7 @@ def validate_vault_state(state: VaultState) -> VaultValidation:
|
|
|
291
297
|
*_missing_erc20_dependency_findings(state),
|
|
292
298
|
*_unpriceable_token_findings(state),
|
|
293
299
|
*_lending_health_findings(state),
|
|
300
|
+
*_failed_read_findings(state),
|
|
294
301
|
*_reconciliation_findings(state, reconciliation),
|
|
295
302
|
*_erc20_findings(state, state.erc20),
|
|
296
303
|
]
|
|
@@ -550,11 +557,43 @@ def _unpriceable_token_finding(token: UnpriceableToken) -> Finding:
|
|
|
550
557
|
)
|
|
551
558
|
|
|
552
559
|
|
|
560
|
+
_FAILED_READ_IMPACT = {
|
|
561
|
+
"lending_health": "the liquidation risk of the lending positions is unknown",
|
|
562
|
+
"unpriceable_priced_tokens": "tokens the price oracle cannot price were not "
|
|
563
|
+
"looked for",
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
|
|
567
|
+
def _failed_read_findings(state: VaultState) -> list[Finding]:
|
|
568
|
+
findings = []
|
|
569
|
+
for read in state.failed_reads:
|
|
570
|
+
name, _, detail = read.partition(": ")
|
|
571
|
+
what = f"{name} ({detail})" if detail else name
|
|
572
|
+
impact = _FAILED_READ_IMPACT.get(name, "its check did not run")
|
|
573
|
+
findings.append(
|
|
574
|
+
Finding(
|
|
575
|
+
"incomplete_read",
|
|
576
|
+
"warning",
|
|
577
|
+
f"WARNING — {what} could not be read: {impact}",
|
|
578
|
+
)
|
|
579
|
+
)
|
|
580
|
+
return findings
|
|
581
|
+
|
|
582
|
+
|
|
553
583
|
def _lending_health_findings(state: VaultState) -> list[Finding]:
|
|
554
584
|
health = state.lending_health
|
|
555
585
|
if not health or not health.has_lending_positions:
|
|
556
586
|
return []
|
|
557
|
-
findings
|
|
587
|
+
findings = [
|
|
588
|
+
Finding(
|
|
589
|
+
"lending_health",
|
|
590
|
+
"warning",
|
|
591
|
+
f"WARNING — {u.protocol} {u.market_name}"
|
|
592
|
+
f"{f' ({u.substrate_id})' if u.substrate_id else ''}: health unknown, "
|
|
593
|
+
f"{u.reason}",
|
|
594
|
+
)
|
|
595
|
+
for u in health.unreadable
|
|
596
|
+
]
|
|
558
597
|
for market in health.markets:
|
|
559
598
|
if market.ltv_usage_percent is None:
|
|
560
599
|
continue
|