ipor-fusion 4.2.0.dev11__tar.gz → 4.2.0.dev14__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.2.0.dev11 → ipor_fusion-4.2.0.dev14}/PKG-INFO +2 -2
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/README.md +1 -1
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/pyproject.toml +1 -1
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/__init__.py +6 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/cli/vault_markets_view.py +3 -3
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/readers/__init__.py +9 -1
- ipor_fusion-4.2.0.dev14/src/ipor_fusion/readers/compound_v3.py +164 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/readers/lending_health.py +132 -55
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/validation.py +8 -2
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/vault_data.py +8 -4
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/vault_positions.py +28 -1
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_cli_lending_health.py +202 -1
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_cli_vault_predates_getters.py +1 -1
- ipor_fusion-4.2.0.dev14/tests/test_lending_health_compound_v3_onchain.py +87 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_public_api.py +6 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_reader_encoding.py +7 -1
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_validation.py +8 -0
- ipor_fusion-4.2.0.dev11/src/ipor_fusion/readers/compound_v3.py +0 -32
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/.claude-plugin/marketplace.json +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/.claude-plugin/plugin.json +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/.env.example +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/.gemini/settings.json +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/.git-blame-ignore-revs +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/.github/workflows/cd.yml +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/.github/workflows/ci.yml +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/.github/workflows/commit-lint.yml +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/.github/workflows/notify-slack.yml +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/.github/workflows/python-build.yml +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/.github/workflows/python-publish.yml +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/.github/workflows/release.yml +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/.github/workflows/report-slack.yml +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/.gitignore +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/.mcp.json +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/.pre-commit-config.yaml +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/.python-version +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/AGENTS.md +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/CHANGELOG.md +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/CLAUDE.md +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/CODEOWNERS +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/LICENSE +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/MIGRATION_4.md +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/build.sh +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/examples/CONTRIBUTING.md +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/examples/README.md +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/examples/advanced_euler_v2_credit_market_base.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/examples/advanced_euler_v2_looping_arbitrum.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/examples/crosschain_ccip_usdc_arbitrum_hyperevm.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/examples/external_state_margin_leg_base.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/examples/hypercore_vault_official_deployment_hyperevm.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/examples/simple_aave_v3_supply_base.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/pytest.ini +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/scripts/commit-check.sh +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/scripts/fusion_error_signatures.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/skills/ipor-deploy-vault/SKILL.md +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/about.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/cache.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/chains.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/cli/__init__.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/cli/address_cmd.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/cli/changelog_cmd.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/cli/config_cmd.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/cli/config_store.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/cli/fuse_cmd.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/cli/main.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/cli/market_cmd.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/cli/options.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/cli/oracle_mapping_view.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/cli/preflight_cmd.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/cli/vault_cmd.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/cli/vault_health.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/cli/vault_info_view.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/cli/vault_rendering.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/config/__init__.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/config/roles.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/core/__init__.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/core/access.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/core/address.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/core/context.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/core/context_manager.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/core/contract.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/core/erc20.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/core/external_state_executor.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/core/fee_manager.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/core/fuse.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/core/fuse_action.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/core/fuse_decoding.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/core/fusion_factory.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/core/logs.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/core/multicall.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/core/oracle.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/core/plasma_vault.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/core/rewards_manager.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/core/simulation.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/core/simulation_result.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/core/withdraw_manager.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/crosschain/__init__.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/crosschain/ccip/__init__.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/crosschain/ccip/chainlink.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/crosschain/ccip/codec.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/crosschain/ccip/contracts.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/crosschain/ccip/events.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/crosschain/ccip/indexer.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/crosschain/ccip/lane.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/crosschain/ccip/quote.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/crosschain/ccip/transport.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/crosschain/contracts.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/crosschain/discovery.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/crosschain/errors.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/crosschain/fuses/__init__.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/crosschain/fuses/base.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/crosschain/fuses/ccip.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/crosschain/fuses/stargate.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/crosschain/lane.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/crosschain/logs.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/crosschain/messages.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/crosschain/simulation.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/crosschain/stargate/__init__.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/crosschain/stargate/contracts.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/crosschain/stargate/lane.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/crosschain/stargate/layerzero.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/crosschain/stargate/transport.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/crosschain/transport.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/dependency_graph.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/entry_cli.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/entry_mcp.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/errors.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/explorer.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/field_docs.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/fuse_whitelist.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/fuses/__init__.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/fuses/aave_v3.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/fuses/async_action.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/fuses/base.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/fuses/compound_v3.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/fuses/erc4626.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/fuses/euler_v2.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/fuses/events.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/fuses/external_state.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/fuses/external_state_event_decoding.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/fuses/fluid_instadapp.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/fuses/gearbox_v3.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/fuses/hypercore.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/fuses/hypercore_event_decoding.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/fuses/hypercore_pre_hooks.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/fuses/merkl.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/fuses/morpho.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/fuses/ramses_v2.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/fuses/uniswap_v3.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/fuses/universal.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/fusion_errors.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/guide/__init__.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/guide/architecture.md +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/guide/glossary.md +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/guide/invariants.md +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/guide/quickstart.md +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/hypercore/__init__.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/market_ids.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/market_info.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/mcp/__init__.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/mcp/guide.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/mcp/models.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/mcp/server.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/morpho_api.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/onchain_text.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/preflight.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/py.typed +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/readers/aave_v3.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/readers/aave_v4.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/readers/euler_v2.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/readers/hypercore.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/readers/hypercore_outcome.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/readers/morpho.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/readers/oracle_mapping.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/readers/oracle_mapping_nodes.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/readers/oracle_mapping_reader.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/readers/position_manager.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/readers/ramses_v2.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/readers/uniswap_v3.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/readers/unpriceable.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/registry.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/substrates.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/types.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/vault_erc20.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/vault_info.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/vault_managers.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/vault_reads.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/UniswapV3UniversalRouter.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/_addresses.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/_crosschain.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/_crosschain_lifecycle.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/_crosschain_recovery.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/_euler_v2.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/_event_args.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/_fake_rpc.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/_foundry.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/_golden.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/_guide.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/_hypercore_model.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/_hypercore_shadow.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/_live.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/_multicall.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/_oracle_mapping_fake.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/_simulate.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/_validation.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/addresses.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/conftest.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/constants.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/fixtures/hypercore_hip3_flow.json +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/fixtures/hypercore_market55.json +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/fixtures/hypercore_official.json +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/fixtures/market_golden/meta_morpho_v1.json +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/fixtures/market_golden/meta_morpho_v1.mcp.json +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/fixtures/market_golden/meta_morpho_v2.json +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/fixtures/market_golden/meta_morpho_v2.mcp.json +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/fixtures/market_golden/morpho_api/market.json +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/fixtures/market_golden/morpho_api/vault_v1.json +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/fixtures/market_golden/morpho_api/vault_v2.json +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/fixtures/market_golden/morpho_api/vault_v2_not_found.json +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/fixtures/market_golden/morpho_blue.json +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/fixtures/market_golden/morpho_blue.mcp.json +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/fixtures/market_golden/morpho_blue_api_error.json +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/fixtures/market_golden/morpho_blue_api_error.mcp.json +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/fixtures/market_golden/morpho_blue_no_api.json +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/fixtures/market_golden/morpho_blue_no_api.mcp.json +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/fixtures/oracle_mapping_golden/mapping.json +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/fixtures/oracle_mapping_golden/mapping.mcp.json +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/fixtures/simulate_v1_request.json +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/fixtures/vault_info_golden/clean.json +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/fixtures/vault_info_golden/clean.mcp.json +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/fixtures/vault_info_golden/clean.txt +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/fixtures/vault_info_golden/deployment_error.json +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/fixtures/vault_info_golden/deployment_error.mcp.json +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/fixtures/vault_info_golden/deployment_error.txt +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/fixtures/vault_info_golden/fetched.cli.json +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/fixtures/vault_info_golden/fetched.json +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/fixtures/vault_info_golden/fetched.mcp.json +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/fixtures/vault_info_golden/full.json +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/fixtures/vault_info_golden/full.mcp.json +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/fixtures/vault_info_golden/full.txt +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/fixtures/vault_info_golden/rich.json +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/fixtures/vault_info_golden/rich.mcp.json +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/fixtures/vault_info_golden/rich.txt +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_about.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_access_manager.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_addresses_helper.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_cache.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_ccip_read_disabled.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_chains.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_cli_address.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_cli_changelog.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_cli_commands.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_cli_config_store.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_cli_helpers.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_cli_hypercore.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_cli_market_golden.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_cli_market_morpho_blue.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_cli_mcp_fuse_whitelist.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_cli_mcp_parity.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_cli_oracle_mapping.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_cli_oracle_mapping_golden.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_cli_pending_requests_scan.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_cli_preflight.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_cli_role_accounts.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_cli_vault_info_golden.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_cli_vault_validate.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_contract_call.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_contract_encoder.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_crosschain_ccip_events.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_crosschain_ccip_indexer.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_crosschain_ccip_quote.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_crosschain_chainlink.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_crosschain_contracts.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_crosschain_encoding.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_crosschain_errors.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_crosschain_lane.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_crosschain_layering.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_crosschain_transport.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_docs_typing.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_erc20.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_error_hierarchy.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_examples_crosschain.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_examples_euler_looping_arbitrum.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_examples_hypercore_hyperevm.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_examples_vaults.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_explorer.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_external_state_events.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_external_state_executor.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_fee_manager.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_fuse_decoding.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_fuse_encoding.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_fuse_whitelist.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_fusion_errors.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_fusion_instance.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_governance_wrappers.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_guide.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_hypercore_deployment.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_hypercore_events.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_hypercore_flow.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_hypercore_live.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_hypercore_outcome.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_hypercore_pre_hooks.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_hypercore_readers.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_hypercore_simulation.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_logs.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_mcp_address.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_mcp_hypercore.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_mcp_models.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_mcp_preflight.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_mcp_server.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_mcp_vault_validate.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_multicall.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_onchain_text.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_oracle.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_oracle_mapping.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_plasma_vault.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_preflight.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_public_type_reachability.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_registry.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_revert_decoding.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_rewards_manager.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_roles.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_sdk_layering.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_simulate_borrow_aave_v3_mainnet.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_simulate_claim_merkl_wrapper_base.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_simulate_claim_morpho_rewards.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_simulate_compound_v3_institutional_usdc.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_simulate_crosschain_hyperevm.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_simulate_euler_v2_batch_base.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_simulate_euler_v2_credit_market_base.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_simulate_euler_v2_swap_deploy_base.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_simulate_euler_v2_swap_reconfigure_base.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_simulate_euler_v2_swap_registry_base.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_simulate_examples_arbitrum.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_simulate_examples_base.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_simulate_examples_crosschain.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_simulate_examples_hyperevm.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_simulate_external_state_executor_base.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_simulate_fusion_factory_clone_base.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_simulate_looping_aave_v3_base.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_simulate_looping_morpho_blue_base.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_simulate_morpho_blue.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_simulate_pilot_v3.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_simulate_pilot_v4_uniswap.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_simulate_pilot_v5_ramses.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_simulate_pilot_v5_scheduled.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_simulate_plasma_vault_setters_base.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_simulate_preflight_base.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_simulate_readers_integration.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_simulate_release_funds.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_simulate_swap_on_base.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_simulate_timelock_base.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_simulate_vault_from_scratch_base.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_simulation_errors.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_simulation_observation.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_solidity_compiler.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_solidity_mirrors.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_substrates.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_types_and_events.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_unpriceable_tokens.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_vault_data_block.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_vault_dep_graph.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_vault_info_fetched_golden.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_vault_info_onchain_base.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_vault_info_onchain_base_no_withdraw_manager.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_vault_reconciliation_onchain_base.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_vault_simulator_blocks.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_web3_context.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/test_withdraw_manager.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/typed_client/consumer.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/tests/typed_client/rejected.py +0 -0
- {ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: ipor-fusion
|
|
3
|
-
Version: 4.2.0.
|
|
3
|
+
Version: 4.2.0.dev14
|
|
4
4
|
Summary: The IPOR Fusion SDK for Python
|
|
5
5
|
Project-URL: homepage, https://ipor.io
|
|
6
6
|
Project-URL: repository, https://github.com/IPOR-Labs/ipor-fusion.py
|
|
@@ -564,7 +564,7 @@ Read-only aggregators over positions and health, no fuses involved:
|
|
|
564
564
|
|--------|---------|
|
|
565
565
|
| `MorphoReader`, `AaveV3Reader`, `CompoundV3Reader` | Market params, rates and vault positions per lending protocol |
|
|
566
566
|
| `UniswapV3Reader`, `RamsesV2Reader` | LP position details |
|
|
567
|
-
| `fetch_vault_lending_health` | Health factor per lending market a vault is in (Morpho, Aave V3/V4, Euler V2; any other market that can borrow is listed as unreadable) |
|
|
567
|
+
| `fetch_vault_lending_health` | Health factor per lending market a vault is in (Morpho, Aave V3/V4, Euler V2, Compound V3; any other market that can borrow is listed as unreadable) |
|
|
568
568
|
| `build_oracle_mapping` | How the vault prices every configured asset |
|
|
569
569
|
|
|
570
570
|
### Supported networks
|
|
@@ -515,7 +515,7 @@ Read-only aggregators over positions and health, no fuses involved:
|
|
|
515
515
|
|--------|---------|
|
|
516
516
|
| `MorphoReader`, `AaveV3Reader`, `CompoundV3Reader` | Market params, rates and vault positions per lending protocol |
|
|
517
517
|
| `UniswapV3Reader`, `RamsesV2Reader` | LP position details |
|
|
518
|
-
| `fetch_vault_lending_health` | Health factor per lending market a vault is in (Morpho, Aave V3/V4, Euler V2; any other market that can borrow is listed as unreadable) |
|
|
518
|
+
| `fetch_vault_lending_health` | Health factor per lending market a vault is in (Morpho, Aave V3/V4, Euler V2, Compound V3; any other market that can borrow is listed as unreadable) |
|
|
519
519
|
| `build_oracle_mapping` | How the vault prices every configured asset |
|
|
520
520
|
|
|
521
521
|
### Supported networks
|
|
@@ -312,6 +312,9 @@ from ipor_fusion.readers import (
|
|
|
312
312
|
AaveV4OracleReader,
|
|
313
313
|
AaveV4SpokeReader,
|
|
314
314
|
AaveV4UserAccountData,
|
|
315
|
+
CometAccountValue,
|
|
316
|
+
CometAssetInfo,
|
|
317
|
+
CompoundV3FuseReader,
|
|
315
318
|
CompoundV3Reader,
|
|
316
319
|
LendingMarketHealth,
|
|
317
320
|
MiddlewarePricedToken,
|
|
@@ -788,6 +791,9 @@ __all__ = [
|
|
|
788
791
|
"AaveV4OracleReader",
|
|
789
792
|
"AaveV4SpokeReader",
|
|
790
793
|
"AaveV4UserAccountData",
|
|
794
|
+
"CometAccountValue",
|
|
795
|
+
"CometAssetInfo",
|
|
796
|
+
"CompoundV3FuseReader",
|
|
791
797
|
"CompoundV3Reader",
|
|
792
798
|
"HYPEREVM_CHAIN_ID",
|
|
793
799
|
"LendingMarketHealth",
|
{ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/cli/vault_markets_view.py
RENAMED
|
@@ -116,8 +116,8 @@ def _index_lending_health(
|
|
|
116
116
|
"""Split lending health rows by protocol for fast lookup during rendering.
|
|
117
117
|
|
|
118
118
|
Morpho rows are keyed by morpho substrate id (one row per substrate).
|
|
119
|
-
Aave V3 rows are keyed by IPOR market id (account-aggregated,
|
|
120
|
-
market); Aave V4 rows by Spoke address (account-aggregated per Spoke) and
|
|
119
|
+
Aave V3 and Compound V3 rows are keyed by IPOR market id (account-aggregated,
|
|
120
|
+
one row per market); Aave V4 rows by Spoke address (account-aggregated per Spoke) and
|
|
121
121
|
Euler V2 rows by sub-account address, in the same account-level map.
|
|
122
122
|
"""
|
|
123
123
|
morpho: dict[str, LendingMarketHealth] = {}
|
|
@@ -127,7 +127,7 @@ def _index_lending_health(
|
|
|
127
127
|
for m in lh.markets:
|
|
128
128
|
if m.protocol == "morpho" and m.substrate_id:
|
|
129
129
|
morpho[str(m.substrate_id).lower().removeprefix("0x")] = m
|
|
130
|
-
elif m.protocol
|
|
130
|
+
elif m.protocol in ("aave_v3", "compound_v3"):
|
|
131
131
|
aave[m.market_id] = m
|
|
132
132
|
elif m.protocol in ("aave_v4", "euler_v2") and m.substrate_id:
|
|
133
133
|
aave[m.substrate_id] = m
|
|
@@ -11,7 +11,12 @@ from ipor_fusion.readers.aave_v4 import (
|
|
|
11
11
|
AaveV4SpokeReader,
|
|
12
12
|
AaveV4UserAccountData,
|
|
13
13
|
)
|
|
14
|
-
from ipor_fusion.readers.compound_v3 import
|
|
14
|
+
from ipor_fusion.readers.compound_v3 import (
|
|
15
|
+
CometAccountValue,
|
|
16
|
+
CometAssetInfo,
|
|
17
|
+
CompoundV3FuseReader,
|
|
18
|
+
CompoundV3Reader,
|
|
19
|
+
)
|
|
15
20
|
from ipor_fusion.readers.hypercore import (
|
|
16
21
|
HYPEREVM_CHAIN_ID,
|
|
17
22
|
)
|
|
@@ -79,6 +84,9 @@ __all__ = [
|
|
|
79
84
|
"AaveV4OracleReader",
|
|
80
85
|
"AaveV4SpokeReader",
|
|
81
86
|
"AaveV4UserAccountData",
|
|
87
|
+
"CometAccountValue",
|
|
88
|
+
"CometAssetInfo",
|
|
89
|
+
"CompoundV3FuseReader",
|
|
82
90
|
"CompoundV3Reader",
|
|
83
91
|
"HYPEREVM_CHAIN_ID",
|
|
84
92
|
"LendingMarketHealth",
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
|
|
3
|
+
from eth_typing import ChecksumAddress
|
|
4
|
+
from web3 import Web3
|
|
5
|
+
|
|
6
|
+
from ipor_fusion.core.context import Web3Context
|
|
7
|
+
from ipor_fusion.core.contract import Call, ContractWrapper
|
|
8
|
+
from ipor_fusion.core.multicall import Multicall3
|
|
9
|
+
from ipor_fusion.types import Amount
|
|
10
|
+
|
|
11
|
+
COMET_FACTOR_SCALE = 10**18
|
|
12
|
+
COMET_PRICE_SCALE = 10**8
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass(frozen=True, slots=True)
|
|
16
|
+
class CometAssetInfo:
|
|
17
|
+
"""A collateral listing of a Comet; the factors are scaled by 1e18."""
|
|
18
|
+
|
|
19
|
+
asset: ChecksumAddress
|
|
20
|
+
price_feed: ChecksumAddress
|
|
21
|
+
scale: int
|
|
22
|
+
borrow_collateral_factor: int
|
|
23
|
+
liquidate_collateral_factor: int
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass(frozen=True, slots=True)
|
|
27
|
+
class CometAccountValue:
|
|
28
|
+
"""An account's position in the Comet's own prices, scaled by 1e8.
|
|
29
|
+
|
|
30
|
+
The unit is the Comet's price unit: USD on USD-priced Comets, ETH on a WETH
|
|
31
|
+
Comet. ``liquidation_limit`` is the collateral at its liquidation factors;
|
|
32
|
+
the account is liquidatable when ``debt`` exceeds it.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
collateral: int
|
|
36
|
+
liquidation_limit: int
|
|
37
|
+
debt: int
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _asset_info_decoder(
|
|
41
|
+
value: tuple[int, str, str, int, int, int, int, int],
|
|
42
|
+
) -> CometAssetInfo:
|
|
43
|
+
_, asset, price_feed, scale, borrow_factor, liquidate_factor, _, _ = value
|
|
44
|
+
return CometAssetInfo(
|
|
45
|
+
asset=Web3.to_checksum_address(asset),
|
|
46
|
+
price_feed=Web3.to_checksum_address(price_feed),
|
|
47
|
+
scale=scale,
|
|
48
|
+
borrow_collateral_factor=borrow_factor,
|
|
49
|
+
liquidate_collateral_factor=liquidate_factor,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class CompoundV3FuseReader(ContractWrapper):
|
|
54
|
+
"""Reader for the immutables of an IPOR Fusion Compound V3 fuse.
|
|
55
|
+
|
|
56
|
+
A Fusion market is bound to one Comet per chain through its fuses, so the
|
|
57
|
+
Comet behind a market is a property of its fuse.
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
def comet(self) -> Call[ChecksumAddress]:
|
|
61
|
+
return self._view(
|
|
62
|
+
"COMET()", output_types=["address"], decoder=Web3.to_checksum_address
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class CompoundV3Reader(ContractWrapper):
|
|
67
|
+
"""Reader for Compound V3 (Comet) on-chain state."""
|
|
68
|
+
|
|
69
|
+
def balance_of(self, account: ChecksumAddress) -> Call[Amount]:
|
|
70
|
+
return self._view(
|
|
71
|
+
"balanceOf(address)", account, output_types=["uint256"], decoder=Amount
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
def borrow_balance_of(self, account: ChecksumAddress) -> Call[Amount]:
|
|
75
|
+
return self._view(
|
|
76
|
+
"borrowBalanceOf(address)",
|
|
77
|
+
account,
|
|
78
|
+
output_types=["uint256"],
|
|
79
|
+
decoder=Amount,
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
def collateral_balance_of(
|
|
83
|
+
self, account: ChecksumAddress, asset: ChecksumAddress
|
|
84
|
+
) -> Call[Amount]:
|
|
85
|
+
return self._view(
|
|
86
|
+
"collateralBalanceOf(address,address)",
|
|
87
|
+
account,
|
|
88
|
+
asset,
|
|
89
|
+
output_types=["uint128"],
|
|
90
|
+
decoder=Amount,
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
def base_scale(self) -> Call[int]:
|
|
94
|
+
return self._view("baseScale()", output_types=["uint256"])
|
|
95
|
+
|
|
96
|
+
def base_token_price_feed(self) -> Call[ChecksumAddress]:
|
|
97
|
+
return self._view(
|
|
98
|
+
"baseTokenPriceFeed()",
|
|
99
|
+
output_types=["address"],
|
|
100
|
+
decoder=Web3.to_checksum_address,
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
def get_price(self, price_feed: ChecksumAddress) -> Call[int]:
|
|
104
|
+
return self._view("getPrice(address)", price_feed, output_types=["uint256"])
|
|
105
|
+
|
|
106
|
+
def num_assets(self) -> Call[int]:
|
|
107
|
+
return self._view("numAssets()", output_types=["uint8"])
|
|
108
|
+
|
|
109
|
+
def get_asset_info(self, index: int) -> Call[CometAssetInfo]:
|
|
110
|
+
return self._view(
|
|
111
|
+
"getAssetInfo(uint8)",
|
|
112
|
+
index,
|
|
113
|
+
output_types=[
|
|
114
|
+
"(uint8,address,address,uint64,uint64,uint64,uint64,uint128)"
|
|
115
|
+
],
|
|
116
|
+
decoder=_asset_info_decoder,
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
def read_account_value(
|
|
120
|
+
self, ctx: Web3Context, account: ChecksumAddress
|
|
121
|
+
) -> CometAccountValue:
|
|
122
|
+
"""Value ``account``'s collateral and debt as the Comet does, over every
|
|
123
|
+
collateral it lists rather than the vault's substrates: a revoked substrate
|
|
124
|
+
does not take collateral out of the account. Only the feeds of held assets
|
|
125
|
+
are read, as `isLiquidatable` does, so a broken feed of an asset the account
|
|
126
|
+
does not hold cannot hide its position (four batches)."""
|
|
127
|
+
multicall = Multicall3(ctx)
|
|
128
|
+
batch = multicall.batch()
|
|
129
|
+
count = batch.add(self.num_assets())
|
|
130
|
+
base_scale = batch.add(self.base_scale())
|
|
131
|
+
base_feed = batch.add(self.base_token_price_feed())
|
|
132
|
+
debt = batch.add(self.borrow_balance_of(account))
|
|
133
|
+
batch.run()
|
|
134
|
+
|
|
135
|
+
infos = multicall.aggregate(
|
|
136
|
+
[self.get_asset_info(i) for i in range(count.value)]
|
|
137
|
+
)
|
|
138
|
+
balances = multicall.aggregate(
|
|
139
|
+
[self.collateral_balance_of(account, info.asset) for info in infos]
|
|
140
|
+
)
|
|
141
|
+
held = [
|
|
142
|
+
(info, balance)
|
|
143
|
+
for info, balance in zip(infos, balances, strict=True)
|
|
144
|
+
if balance > 0
|
|
145
|
+
]
|
|
146
|
+
|
|
147
|
+
batch = multicall.batch()
|
|
148
|
+
base_price = batch.add(self.get_price(base_feed.value))
|
|
149
|
+
prices = [batch.add(self.get_price(info.price_feed)) for info, _ in held]
|
|
150
|
+
batch.run()
|
|
151
|
+
|
|
152
|
+
collateral = 0
|
|
153
|
+
liquidation_limit = 0
|
|
154
|
+
for (info, balance), price in zip(held, prices, strict=True):
|
|
155
|
+
value = balance * price.value // info.scale
|
|
156
|
+
collateral += value
|
|
157
|
+
liquidation_limit += (
|
|
158
|
+
value * info.liquidate_collateral_factor // COMET_FACTOR_SCALE
|
|
159
|
+
)
|
|
160
|
+
return CometAccountValue(
|
|
161
|
+
collateral=collateral,
|
|
162
|
+
liquidation_limit=liquidation_limit,
|
|
163
|
+
debt=debt.value * base_price.value // base_scale.value,
|
|
164
|
+
)
|
{ipor_fusion-4.2.0.dev11 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/readers/lending_health.py
RENAMED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
"""Lending health computation for Morpho Blue, Aave V3, Aave V4
|
|
1
|
+
"""Lending health computation for Morpho Blue, Aave V3, Aave V4, Euler V2 and
|
|
2
|
+
Compound V3 markets.
|
|
2
3
|
|
|
3
4
|
Provides a unified view of LTV, liquidation thresholds, and health factors
|
|
4
5
|
for lending positions held by Plasma Vaults.
|
|
@@ -7,8 +8,10 @@ for lending positions held by Plasma Vaults.
|
|
|
7
8
|
from __future__ import annotations
|
|
8
9
|
|
|
9
10
|
import logging
|
|
11
|
+
from collections.abc import Callable
|
|
10
12
|
from concurrent.futures import ThreadPoolExecutor
|
|
11
13
|
from dataclasses import dataclass, field
|
|
14
|
+
from functools import partial
|
|
12
15
|
|
|
13
16
|
import requests
|
|
14
17
|
from eth_abi import decode
|
|
@@ -30,6 +33,7 @@ from ipor_fusion.errors import EmptyCallResultError
|
|
|
30
33
|
from ipor_fusion.market_ids import IporFusionMarkets
|
|
31
34
|
from ipor_fusion.readers.aave_v3 import AaveV3Reader
|
|
32
35
|
from ipor_fusion.readers.aave_v4 import AaveV4OracleReader, AaveV4SpokeReader
|
|
36
|
+
from ipor_fusion.readers.compound_v3 import COMET_PRICE_SCALE, CompoundV3Reader
|
|
33
37
|
from ipor_fusion.readers.euler_v2 import (
|
|
34
38
|
EULER_USD_UNIT_OF_ACCOUNT,
|
|
35
39
|
EulerV2Substrate,
|
|
@@ -123,6 +127,18 @@ EULER_V2_MARKET_IDS = frozenset(
|
|
|
123
127
|
}
|
|
124
128
|
)
|
|
125
129
|
|
|
130
|
+
# Market IDs served by the Compound V3 fuses. One Comet per market, read from its
|
|
131
|
+
# balance fuse. Health is account-level; a Comet account owing nothing has no
|
|
132
|
+
# liquidation risk and no row. The WETH Comet prices in ETH, not USD.
|
|
133
|
+
COMPOUND_V3_MARKET_IDS = frozenset(
|
|
134
|
+
{
|
|
135
|
+
IporFusionMarkets.COMPOUND_V3_USDC,
|
|
136
|
+
IporFusionMarkets.COMPOUND_V3_USDT,
|
|
137
|
+
IporFusionMarkets.COMPOUND_V3_WETH,
|
|
138
|
+
IporFusionMarkets.COMPOUND_V3_INSTITUTIONAL_USDC,
|
|
139
|
+
}
|
|
140
|
+
)
|
|
141
|
+
|
|
126
142
|
# Markets whose fuses can borrow but whose health has no reader yet. Their
|
|
127
143
|
# positions are listed as unreadable rather than left out: an unknown
|
|
128
144
|
# liquidation risk is not a healthy one. Dolomite counts only when a
|
|
@@ -132,7 +148,6 @@ UNSUPPORTED_BORROW_MARKET_IDS = frozenset(
|
|
|
132
148
|
IporFusionMarkets.MOONWELL,
|
|
133
149
|
IporFusionMarkets.SILO_V2,
|
|
134
150
|
IporFusionMarkets.EBISU,
|
|
135
|
-
IporFusionMarkets.COMPOUND_V3_INSTITUTIONAL_USDC,
|
|
136
151
|
}
|
|
137
152
|
)
|
|
138
153
|
_UNSUPPORTED_REASON = "no lending health reader for this market"
|
|
@@ -147,6 +162,7 @@ class LendingMarketHealth:
|
|
|
147
162
|
- Aave V3: None — Aave health is account-level, aggregated across reserves.
|
|
148
163
|
- Aave V4: the Spoke address — account-level per Spoke, across its reserves.
|
|
149
164
|
- Euler V2: the sub-account address — account-level, across its collaterals.
|
|
165
|
+
- Compound V3: None — account-level on the market's Comet, across its collaterals.
|
|
150
166
|
"""
|
|
151
167
|
|
|
152
168
|
protocol: str
|
|
@@ -438,6 +454,57 @@ def _compute_aave_v4_market_health(
|
|
|
438
454
|
)
|
|
439
455
|
|
|
440
456
|
|
|
457
|
+
def _compute_compound_v3_market_health(
|
|
458
|
+
ctx: Web3Context,
|
|
459
|
+
comet: ChecksumAddress,
|
|
460
|
+
vault_address: PlasmaVaultAddress,
|
|
461
|
+
ipor_market_id: MarketId,
|
|
462
|
+
market_name: str,
|
|
463
|
+
) -> LendingMarketHealth | UnreadableLendingMarket | None:
|
|
464
|
+
"""Compute LTV health for the vault's account on one Comet; None when it owes
|
|
465
|
+
nothing. The health factor is the liquidation limit over the debt, so it falls
|
|
466
|
+
to 1 where `Comet.isLiquidatable` turns true."""
|
|
467
|
+
try:
|
|
468
|
+
reader = CompoundV3Reader(comet)
|
|
469
|
+
if reader.borrow_balance_of(vault_address).call(ctx) == 0:
|
|
470
|
+
return None
|
|
471
|
+
account = reader.read_account_value(ctx, vault_address)
|
|
472
|
+
except _READ_ERRORS as exc:
|
|
473
|
+
return UnreadableLendingMarket(
|
|
474
|
+
protocol="compound_v3",
|
|
475
|
+
market_id=ipor_market_id,
|
|
476
|
+
market_name=market_name,
|
|
477
|
+
reason=_reason("account value", exc),
|
|
478
|
+
)
|
|
479
|
+
# Dust that prices to nothing: the Comet rounds it the same way and cannot
|
|
480
|
+
# liquidate it.
|
|
481
|
+
if account.debt == 0:
|
|
482
|
+
return None
|
|
483
|
+
|
|
484
|
+
has_collateral = account.collateral > 0
|
|
485
|
+
has_limit = account.liquidation_limit > 0
|
|
486
|
+
current_ltv = account.debt / account.collateral if has_collateral else None
|
|
487
|
+
max_ltv = account.liquidation_limit / account.collateral if has_collateral else 0.0
|
|
488
|
+
# With no limit the health factor is 0, a known liquidation risk; only the usage
|
|
489
|
+
# has no value.
|
|
490
|
+
ltv_usage = account.debt / account.liquidation_limit * 100 if has_limit else None
|
|
491
|
+
in_usd = ipor_market_id != IporFusionMarkets.COMPOUND_V3_WETH
|
|
492
|
+
|
|
493
|
+
return LendingMarketHealth(
|
|
494
|
+
protocol="compound_v3",
|
|
495
|
+
market_id=ipor_market_id,
|
|
496
|
+
market_name=market_name,
|
|
497
|
+
current_ltv=round(current_ltv, 6) if current_ltv is not None else None,
|
|
498
|
+
max_ltv=round(max_ltv, 6),
|
|
499
|
+
health_factor=round(account.liquidation_limit / account.debt, 4),
|
|
500
|
+
total_collateral_usd=(
|
|
501
|
+
round(account.collateral / COMET_PRICE_SCALE, 2) if in_usd else None
|
|
502
|
+
),
|
|
503
|
+
total_debt_usd=round(account.debt / COMET_PRICE_SCALE, 2) if in_usd else None,
|
|
504
|
+
ltv_usage_percent=round(ltv_usage, 2) if ltv_usage is not None else None,
|
|
505
|
+
)
|
|
506
|
+
|
|
507
|
+
|
|
441
508
|
@dataclass(frozen=True, slots=True)
|
|
442
509
|
class _EulerV2Account:
|
|
443
510
|
"""An EVC sub-account of the vault and the Euler vaults it may borrow from."""
|
|
@@ -575,6 +642,7 @@ class _LendingMarkets:
|
|
|
575
642
|
aave_v3: list[tuple[MarketId, str, ChecksumAddress]]
|
|
576
643
|
aave_v4: list[tuple[MarketId, str, ChecksumAddress]]
|
|
577
644
|
euler_v2: list[tuple[MarketId, str, _EulerV2Account]]
|
|
645
|
+
compound_v3: list[tuple[MarketId, str, ChecksumAddress]]
|
|
578
646
|
unreadable: list[UnreadableLendingMarket]
|
|
579
647
|
|
|
580
648
|
|
|
@@ -583,12 +651,14 @@ def _collect_lending_markets(
|
|
|
583
651
|
balance_fuse_market_ids: list[MarketId],
|
|
584
652
|
market_substrates: dict[MarketId, list[bytes]],
|
|
585
653
|
aave_pools: dict[MarketId, ChecksumAddress],
|
|
654
|
+
comets: dict[MarketId, ChecksumAddress],
|
|
586
655
|
) -> _LendingMarkets:
|
|
587
656
|
"""Pick the positions to read: Morpho substrates, Aave V3 markets with a Pool,
|
|
588
|
-
the Spokes of Aave V4 markets
|
|
589
|
-
market that may borrow but has no reader is listed
|
|
657
|
+
the Spokes of Aave V4 markets, the borrowing Euler V2 sub-accounts and Compound
|
|
658
|
+
V3 markets with a Comet. A market that may borrow but has no reader is listed
|
|
659
|
+
as unreadable."""
|
|
590
660
|
markets = _LendingMarkets(
|
|
591
|
-
morpho=[], aave_v3=[], aave_v4=[], euler_v2=[], unreadable=[]
|
|
661
|
+
morpho=[], aave_v3=[], aave_v4=[], euler_v2=[], compound_v3=[], unreadable=[]
|
|
592
662
|
)
|
|
593
663
|
|
|
594
664
|
for mid in balance_fuse_market_ids:
|
|
@@ -621,6 +691,18 @@ def _collect_lending_markets(
|
|
|
621
691
|
(mid, name, account)
|
|
622
692
|
for account in _euler_v2_borrow_accounts(substrates, vault)
|
|
623
693
|
)
|
|
694
|
+
elif mid in COMPOUND_V3_MARKET_IDS:
|
|
695
|
+
if comet := comets.get(mid):
|
|
696
|
+
markets.compound_v3.append((mid, name, comet))
|
|
697
|
+
else:
|
|
698
|
+
markets.unreadable.append(
|
|
699
|
+
UnreadableLendingMarket(
|
|
700
|
+
protocol="compound_v3",
|
|
701
|
+
market_id=mid,
|
|
702
|
+
market_name=name,
|
|
703
|
+
reason="the balance fuse names no Comet",
|
|
704
|
+
)
|
|
705
|
+
)
|
|
624
706
|
elif _can_borrow(mid, substrates):
|
|
625
707
|
markets.unreadable.append(
|
|
626
708
|
UnreadableLendingMarket(
|
|
@@ -634,6 +716,45 @@ def _collect_lending_markets(
|
|
|
634
716
|
return markets
|
|
635
717
|
|
|
636
718
|
|
|
719
|
+
def _health_reads(
|
|
720
|
+
ctx: Web3Context, vault: PlasmaVaultAddress, markets: _LendingMarkets
|
|
721
|
+
) -> list[Callable[[], LendingMarketHealth | UnreadableLendingMarket | None]]:
|
|
722
|
+
"""One read per position; Aave V3 health is account-level per Pool, so each
|
|
723
|
+
market reads its own."""
|
|
724
|
+
reads: list[Callable[[], LendingMarketHealth | UnreadableLendingMarket | None]] = []
|
|
725
|
+
if markets.morpho:
|
|
726
|
+
morpho_reader = MorphoReader(morpho_blue_address(ctx.chain_id))
|
|
727
|
+
reads += [
|
|
728
|
+
partial(
|
|
729
|
+
_compute_morpho_market_health,
|
|
730
|
+
ctx,
|
|
731
|
+
morpho_reader,
|
|
732
|
+
morpho_mid,
|
|
733
|
+
vault,
|
|
734
|
+
mid,
|
|
735
|
+
name,
|
|
736
|
+
)
|
|
737
|
+
for mid, name, morpho_mid in markets.morpho
|
|
738
|
+
]
|
|
739
|
+
reads += [
|
|
740
|
+
partial(_compute_aave_market_health, ctx, AaveV3Reader(pool), vault, mid, name)
|
|
741
|
+
for mid, name, pool in markets.aave_v3
|
|
742
|
+
]
|
|
743
|
+
reads += [
|
|
744
|
+
partial(_compute_aave_v4_market_health, ctx, spoke, vault, mid, name)
|
|
745
|
+
for mid, name, spoke in markets.aave_v4
|
|
746
|
+
]
|
|
747
|
+
reads += [
|
|
748
|
+
partial(_compute_euler_v2_account_health, ctx, account, mid, name)
|
|
749
|
+
for mid, name, account in markets.euler_v2
|
|
750
|
+
]
|
|
751
|
+
reads += [
|
|
752
|
+
partial(_compute_compound_v3_market_health, ctx, comet, vault, mid, name)
|
|
753
|
+
for mid, name, comet in markets.compound_v3
|
|
754
|
+
]
|
|
755
|
+
return reads
|
|
756
|
+
|
|
757
|
+
|
|
637
758
|
def fetch_vault_lending_health(
|
|
638
759
|
ctx: Web3Context,
|
|
639
760
|
vault: PlasmaVaultAddress,
|
|
@@ -641,6 +762,7 @@ def fetch_vault_lending_health(
|
|
|
641
762
|
balance_fuse_market_ids: list[MarketId],
|
|
642
763
|
market_substrates: dict[MarketId, list[bytes]],
|
|
643
764
|
aave_pools: dict[MarketId, ChecksumAddress] | None = None,
|
|
765
|
+
comets: dict[MarketId, ChecksumAddress] | None = None,
|
|
644
766
|
) -> VaultLendingHealth:
|
|
645
767
|
"""Fetch lending health for all lending markets in a vault.
|
|
646
768
|
|
|
@@ -656,68 +778,23 @@ def fetch_vault_lending_health(
|
|
|
656
778
|
entry is listed in ``unreadable``. Defaults to the chain's Core pool for AAVE_V3 only:
|
|
657
779
|
the Pools of Aave V3 Prime and SparkLend are known only to their
|
|
658
780
|
fuses.
|
|
781
|
+
comets: Map of Compound V3 market_id -> the Comet its balance fuse reads
|
|
782
|
+
(`CompoundV3FuseReader.comet`). A Compound V3 market without an entry
|
|
783
|
+
is listed in ``unreadable``.
|
|
659
784
|
"""
|
|
660
785
|
if aave_pools is None:
|
|
661
786
|
core_pool = AAVE_V3_POOL.get(ctx.chain_id)
|
|
662
787
|
aave_pools = {IporFusionMarkets.AAVE_V3: core_pool} if core_pool else {}
|
|
663
788
|
|
|
664
789
|
markets = _collect_lending_markets(
|
|
665
|
-
vault, balance_fuse_market_ids, market_substrates, aave_pools
|
|
790
|
+
vault, balance_fuse_market_ids, market_substrates, aave_pools, comets or {}
|
|
666
791
|
)
|
|
667
792
|
|
|
668
793
|
results: list[LendingMarketHealth] = []
|
|
669
794
|
unreadable = list(markets.unreadable)
|
|
670
795
|
|
|
671
796
|
with ThreadPoolExecutor() as pool:
|
|
672
|
-
futures = []
|
|
673
|
-
|
|
674
|
-
if markets.morpho:
|
|
675
|
-
morpho_reader = MorphoReader(morpho_blue_address(ctx.chain_id))
|
|
676
|
-
for ipor_mid, name, morpho_mid in markets.morpho:
|
|
677
|
-
futures.append(
|
|
678
|
-
pool.submit(
|
|
679
|
-
_compute_morpho_market_health,
|
|
680
|
-
ctx,
|
|
681
|
-
morpho_reader,
|
|
682
|
-
morpho_mid,
|
|
683
|
-
vault,
|
|
684
|
-
ipor_mid,
|
|
685
|
-
name,
|
|
686
|
-
)
|
|
687
|
-
)
|
|
688
|
-
|
|
689
|
-
# Aave V3 health is account-level per Pool, so each market reads its own.
|
|
690
|
-
for ipor_mid, name, aave_pool in markets.aave_v3:
|
|
691
|
-
futures.append(
|
|
692
|
-
pool.submit(
|
|
693
|
-
_compute_aave_market_health,
|
|
694
|
-
ctx,
|
|
695
|
-
AaveV3Reader(aave_pool),
|
|
696
|
-
vault,
|
|
697
|
-
ipor_mid,
|
|
698
|
-
name,
|
|
699
|
-
)
|
|
700
|
-
)
|
|
701
|
-
|
|
702
|
-
for ipor_mid, name, spoke in markets.aave_v4:
|
|
703
|
-
futures.append(
|
|
704
|
-
pool.submit(
|
|
705
|
-
_compute_aave_v4_market_health,
|
|
706
|
-
ctx,
|
|
707
|
-
spoke,
|
|
708
|
-
vault,
|
|
709
|
-
ipor_mid,
|
|
710
|
-
name,
|
|
711
|
-
)
|
|
712
|
-
)
|
|
713
|
-
|
|
714
|
-
for ipor_mid, name, account in markets.euler_v2:
|
|
715
|
-
futures.append(
|
|
716
|
-
pool.submit(
|
|
717
|
-
_compute_euler_v2_account_health, ctx, account, ipor_mid, name
|
|
718
|
-
)
|
|
719
|
-
)
|
|
720
|
-
|
|
797
|
+
futures = [pool.submit(read) for read in _health_reads(ctx, vault, markets)]
|
|
721
798
|
for fut in futures:
|
|
722
799
|
result = fut.result()
|
|
723
800
|
if result is None:
|
|
@@ -691,9 +691,15 @@ def _lending_health_findings(state: VaultState) -> list[Finding]:
|
|
|
691
691
|
for u in health.unreadable
|
|
692
692
|
]
|
|
693
693
|
for market in health.markets:
|
|
694
|
-
|
|
694
|
+
# A critical row with no usage (debt against a zero limit) is still critical.
|
|
695
|
+
if market.ltv_usage_percent is None and not market.is_critical:
|
|
695
696
|
continue
|
|
696
697
|
ltv = f"{market.current_ltv:.4f}" if market.current_ltv is not None else "?"
|
|
698
|
+
usage = (
|
|
699
|
+
f"{market.ltv_usage_percent:.1f}%"
|
|
700
|
+
if market.ltv_usage_percent is not None
|
|
701
|
+
else "?"
|
|
702
|
+
)
|
|
697
703
|
health_factor = (
|
|
698
704
|
f", health_factor={market.health_factor:.4f}"
|
|
699
705
|
if market.health_factor is not None
|
|
@@ -702,7 +708,7 @@ def _lending_health_findings(state: VaultState) -> list[Finding]:
|
|
|
702
708
|
line = (
|
|
703
709
|
f"{market.protocol} {market.market_name}: "
|
|
704
710
|
f"LTV {ltv}/{market.max_ltv:.4f} "
|
|
705
|
-
f"({
|
|
711
|
+
f"({usage} usage){health_factor}"
|
|
706
712
|
)
|
|
707
713
|
if market.is_critical:
|
|
708
714
|
findings.append(
|
|
@@ -88,6 +88,7 @@ from ipor_fusion.vault_positions import (
|
|
|
88
88
|
_fetch_aave_pools,
|
|
89
89
|
_fetch_aave_positions,
|
|
90
90
|
_fetch_breakdown_token_prices,
|
|
91
|
+
_fetch_comets,
|
|
91
92
|
_fetch_morpho_positions,
|
|
92
93
|
)
|
|
93
94
|
from ipor_fusion.vault_reads import (
|
|
@@ -508,16 +509,17 @@ class _MarketReads:
|
|
|
508
509
|
# Only markets with at least one substrate.
|
|
509
510
|
market_substrates: dict[MarketId, list[bytes]]
|
|
510
511
|
aave_pools: dict[MarketId, ChecksumAddress]
|
|
512
|
+
comets: dict[MarketId, ChecksumAddress]
|
|
511
513
|
market_balances: dict[MarketId, Amount]
|
|
512
514
|
# False when the vault predates getMarketSubstrates (substrates unknown).
|
|
513
515
|
substrates_available: bool = True
|
|
514
516
|
|
|
515
517
|
|
|
516
518
|
def _fetch_market_reads(
|
|
517
|
-
ctx: Web3Context, plasma_vault: PlasmaVault, *,
|
|
519
|
+
ctx: Web3Context, plasma_vault: PlasmaVault, *, with_lending_venues: bool
|
|
518
520
|
) -> _MarketReads:
|
|
519
521
|
"""Balance fuses, then every market's dependency graph and substrates in
|
|
520
|
-
one batch, then (optionally) the Aave V3 Pools behind the markets."""
|
|
522
|
+
one batch, then (optionally) the Aave V3 Pools and Comets behind the markets."""
|
|
521
523
|
balance_fuses = plasma_vault.read_balance_fuses(ctx)
|
|
522
524
|
market_ids = [bf.market_id for bf in balance_fuses]
|
|
523
525
|
venues = list(dict.fromkeys(market_ids))
|
|
@@ -545,7 +547,8 @@ def _fetch_market_reads(
|
|
|
545
547
|
market_substrates={
|
|
546
548
|
mid: subs for mid, subs in zip(market_ids, substrates, strict=True) if subs
|
|
547
549
|
},
|
|
548
|
-
aave_pools=_fetch_aave_pools(ctx, balance_fuses) if
|
|
550
|
+
aave_pools=_fetch_aave_pools(ctx, balance_fuses) if with_lending_venues else {},
|
|
551
|
+
comets=_fetch_comets(ctx, balance_fuses) if with_lending_venues else {},
|
|
549
552
|
market_balances={mid: balance.value for mid, balance in balances},
|
|
550
553
|
substrates_available=None not in substrates,
|
|
551
554
|
)
|
|
@@ -583,6 +586,7 @@ def _fetch_lending_reads(
|
|
|
583
586
|
balance_fuse_market_ids=[bf.market_id for bf in markets.balance_fuses],
|
|
584
587
|
market_substrates=markets.market_substrates,
|
|
585
588
|
aave_pools=markets.aave_pools,
|
|
589
|
+
comets=markets.comets,
|
|
586
590
|
),
|
|
587
591
|
)
|
|
588
592
|
morpho_unread: list[str] = []
|
|
@@ -668,7 +672,7 @@ def fetch_vault_data(
|
|
|
668
672
|
_safe_call, partial(plasma_vault.read_stored_markets_total, ctx)
|
|
669
673
|
)
|
|
670
674
|
f_markets = pool.submit(
|
|
671
|
-
_fetch_market_reads, ctx, plasma_vault,
|
|
675
|
+
_fetch_market_reads, ctx, plasma_vault, with_lending_venues=lending
|
|
672
676
|
)
|
|
673
677
|
|
|
674
678
|
markets = f_markets.result()
|
|
@@ -20,7 +20,12 @@ from ipor_fusion.readers.aave_v3 import (
|
|
|
20
20
|
AaveV3PositionBreakdown,
|
|
21
21
|
AaveV3Reader,
|
|
22
22
|
)
|
|
23
|
-
from ipor_fusion.readers.
|
|
23
|
+
from ipor_fusion.readers.compound_v3 import CompoundV3FuseReader
|
|
24
|
+
from ipor_fusion.readers.lending_health import (
|
|
25
|
+
AAVE_V3_MARKET_IDS,
|
|
26
|
+
COMPOUND_V3_MARKET_IDS,
|
|
27
|
+
MORPHO_MARKET_IDS,
|
|
28
|
+
)
|
|
24
29
|
from ipor_fusion.readers.morpho import (
|
|
25
30
|
MorphoPositionBreakdown,
|
|
26
31
|
MorphoReader,
|
|
@@ -152,6 +157,28 @@ def _fetch_aave_pools(
|
|
|
152
157
|
}
|
|
153
158
|
|
|
154
159
|
|
|
160
|
+
def _fetch_comets(
|
|
161
|
+
ctx: Web3Context, balance_fuses: list[BalanceFuse]
|
|
162
|
+
) -> dict[MarketId, ChecksumAddress]:
|
|
163
|
+
"""Map every Compound V3 market to the Comet its balance fuse reads. A balance
|
|
164
|
+
fuse that names no Comet reverts or answers with empty data and is left out."""
|
|
165
|
+
fuses = {
|
|
166
|
+
bf.market_id: bf.fuse
|
|
167
|
+
for bf in balance_fuses
|
|
168
|
+
if bf.market_id in COMPOUND_V3_MARKET_IDS
|
|
169
|
+
}
|
|
170
|
+
if not fuses:
|
|
171
|
+
return {}
|
|
172
|
+
comets = Multicall3(ctx).try_aggregate(
|
|
173
|
+
[CompoundV3FuseReader(fuse).comet() for fuse in fuses.values()]
|
|
174
|
+
)
|
|
175
|
+
return {
|
|
176
|
+
mid: comet
|
|
177
|
+
for mid, comet in zip(fuses, comets, strict=True)
|
|
178
|
+
if comet is not None
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
|
|
155
182
|
def _fetch_aave_positions(
|
|
156
183
|
ctx: Web3Context,
|
|
157
184
|
pool: ThreadPoolExecutor,
|