ipor-fusion 4.0.0.dev35__tar.gz → 4.0.0.dev39__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.dev35 → ipor_fusion-4.0.0.dev39}/PKG-INFO +1 -1
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/pyproject.toml +1 -1
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/cli/config_store.py +13 -2
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/cli/market_cmd.py +7 -7
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/cli/vault_cmd.py +1 -1
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/cli/vault_markets_view.py +1 -1
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/market_info.py +1 -1
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/mcp/models.py +1 -1
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/mcp/server.py +5 -1
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/morpho_api.py +89 -63
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/validation.py +3 -3
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/vault_data.py +1 -1
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/vault_info.py +1 -1
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/test_cli_helpers.py +31 -2
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/test_cli_market_morpho_blue.py +55 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/test_mcp_models.py +19 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/test_mcp_server.py +14 -2
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/.claude-plugin/marketplace.json +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/.claude-plugin/plugin.json +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/.env.example +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/.gemini/settings.json +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/.git-blame-ignore-revs +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/.github/workflows/cd.yml +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/.github/workflows/ci.yml +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/.github/workflows/commit-lint.yml +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/.github/workflows/notify-slack.yml +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/.github/workflows/python-build.yml +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/.github/workflows/python-publish.yml +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/.github/workflows/release.yml +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/.github/workflows/report-slack.yml +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/.gitignore +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/.mcp.json +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/.pre-commit-config.yaml +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/.python-version +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/AGENTS.md +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/CHANGELOG.md +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/CLAUDE.md +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/CODEOWNERS +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/LICENSE +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/README.md +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/build.sh +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/examples/CONTRIBUTING.md +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/examples/README.md +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/examples/advanced_euler_v2_credit_market_base.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/examples/advanced_euler_v2_looping_arbitrum.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/examples/composed_hypercore_flow_arbitrum_hyperevm.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/examples/crosschain_ccip_usdc_arbitrum_hyperevm.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/examples/external_state_margin_leg_base.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/examples/simple_aave_v3_supply_base.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/pytest.ini +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/scripts/commit-check.sh +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/scripts/crosschain_readiness.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/skills/ipor-deploy-vault/SKILL.md +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/about.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/cache.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/chains.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/cli/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/cli/changelog_cmd.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/cli/config_cmd.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/cli/main.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/cli/options.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/cli/oracle_mapping_view.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/cli/vault_health.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/cli/vault_info_view.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/cli/vault_rendering.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/config/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/config/roles.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/core/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/core/access.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/core/address.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/core/context.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/core/contract.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/core/erc20.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/core/external_state_executor.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/core/fee_manager.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/core/fuse_action.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/core/fusion_factory.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/core/logs.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/core/multicall.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/core/oracle.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/core/plasma_vault.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/core/rewards_manager.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/core/simulation.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/core/withdraw_manager.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/crosschain/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/crosschain/ccip/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/crosschain/ccip/chainlink.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/crosschain/ccip/codec.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/crosschain/ccip/contracts.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/crosschain/ccip/events.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/crosschain/ccip/indexer.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/crosschain/ccip/lane.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/crosschain/ccip/quote.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/crosschain/ccip/transport.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/crosschain/contracts.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/crosschain/discovery.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/crosschain/errors.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/crosschain/lane.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/crosschain/logs.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/crosschain/messages.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/crosschain/simulation.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/crosschain/stargate/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/crosschain/stargate/contracts.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/crosschain/stargate/lane.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/crosschain/stargate/layerzero.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/crosschain/stargate/transport.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/crosschain/transport.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/dependency_graph.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/entry_cli.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/entry_mcp.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/errors.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/explorer.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/field_docs.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/fuses/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/fuses/aave_v3.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/fuses/async_action.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/fuses/base.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/fuses/compound_v3.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/fuses/crosschain/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/fuses/crosschain/base.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/fuses/crosschain/ccip.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/fuses/crosschain/stargate.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/fuses/erc4626.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/fuses/euler_v2.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/fuses/events.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/fuses/external_state.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/fuses/external_state_events.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/fuses/fluid_instadapp.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/fuses/gearbox_v3.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/fuses/hypercore.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/fuses/hypercore_events.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/fuses/merkl.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/fuses/morpho.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/fuses/ramses_v2.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/fuses/uniswap_v3.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/fuses/universal.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/guide/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/guide/architecture.md +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/guide/glossary.md +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/guide/invariants.md +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/guide/quickstart.md +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/market_ids.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/mcp/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/mcp/guide.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/py.typed +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/readers/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/readers/aave_v3.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/readers/aave_v4.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/readers/compound_v3.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/readers/hypercore.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/readers/hypercore_outcome.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/readers/lending_health.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/readers/morpho.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/readers/oracle_mapping.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/readers/position_manager.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/readers/ramses_v2.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/readers/uniswap_v3.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/readers/unpriceable.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/substrates.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/types.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/UniswapV3UniversalRouter.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/_crosschain.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/_crosschain_lifecycle.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/_crosschain_recovery.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/_euler_v2.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/_foundry.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/_guide.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/_hypercore_model.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/_hypercore_shadow.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/_multicall.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/_simulate.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/_validation.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/addresses.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/conftest.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/constants.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/fixtures/hypercore_hip3_flow.json +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/fixtures/hypercore_market55.json +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/fixtures/vault_info_golden/clean.json +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/fixtures/vault_info_golden/clean.txt +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/fixtures/vault_info_golden/rich.json +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/fixtures/vault_info_golden/rich.txt +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/test_about.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/test_access_manager.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/test_cache.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/test_chains.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/test_cli_changelog.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/test_cli_commands.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/test_cli_config_store.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/test_cli_hypercore.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/test_cli_lending_health.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/test_cli_oracle_mapping.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/test_cli_pending_requests_scan.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/test_cli_role_accounts.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/test_cli_vault_info_golden.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/test_cli_vault_predates_getters.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/test_cli_vault_validate.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/test_contract_call.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/test_contract_encoder.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/test_crosschain_ccip_events.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/test_crosschain_ccip_indexer.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/test_crosschain_ccip_quote.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/test_crosschain_chainlink.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/test_crosschain_contracts.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/test_crosschain_encoding.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/test_crosschain_errors.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/test_crosschain_lane.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/test_crosschain_layering.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/test_crosschain_readiness.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/test_crosschain_readiness_report.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/test_crosschain_transport.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/test_erc20.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/test_examples_composed_hypercore.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/test_examples_crosschain.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/test_examples_euler_looping_arbitrum.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/test_examples_vaults.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/test_explorer.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/test_external_state_events.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/test_external_state_executor.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/test_fee_manager.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/test_fuse_encoding.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/test_guide.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/test_hypercore_deployment.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/test_hypercore_events.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/test_hypercore_flow.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/test_hypercore_live.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/test_hypercore_outcome.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/test_hypercore_readers.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/test_hypercore_simulation.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/test_logs.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/test_mcp_hypercore.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/test_mcp_vault_validate.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/test_multicall.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/test_oracle.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/test_oracle_mapping.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/test_plasma_vault.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/test_reader_encoding.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/test_readiness_selection.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/test_revert_decoding.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/test_rewards_manager.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/test_roles.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/test_sdk_layering.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/test_simulate_borrow_aave_v3_mainnet.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/test_simulate_claim_merkl_wrapper_base.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/test_simulate_claim_morpho_rewards.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/test_simulate_crosschain_canary.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/test_simulate_crosschain_hypercore.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/test_simulate_crosschain_hyperevm.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/test_simulate_crosschain_lifecycle.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/test_simulate_euler_v2_batch_base.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/test_simulate_euler_v2_credit_market_base.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/test_simulate_euler_v2_swap_deploy_base.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/test_simulate_euler_v2_swap_reconfigure_base.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/test_simulate_euler_v2_swap_registry_base.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/test_simulate_examples_arbitrum.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/test_simulate_examples_base.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/test_simulate_examples_crosschain.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/test_simulate_external_state_executor_base.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/test_simulate_fusion_factory_clone_base.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/test_simulate_looping_aave_v3_base.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/test_simulate_looping_morpho_blue_base.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/test_simulate_morpho_blue.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/test_simulate_pilot_v3.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/test_simulate_pilot_v4_uniswap.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/test_simulate_pilot_v5_ramses.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/test_simulate_pilot_v5_scheduled.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/test_simulate_plasma_vault_setters_base.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/test_simulate_readers_integration.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/test_simulate_release_funds.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/test_simulate_swap_on_base.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/test_simulate_vault_from_scratch_base.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/test_solidity_compiler.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/test_solidity_mirrors.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/test_substrates.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/test_types_and_events.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/test_unpriceable_tokens.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/test_validation.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/test_vault_dep_graph.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/test_vault_info_onchain_base.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/test_vault_info_onchain_base_no_withdraw_manager.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/test_vault_simulator_blocks.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/test_web3_context.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/test_withdraw_manager.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/tests/typed_client/consumer.py +0 -0
- {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/uv.lock +0 -0
|
@@ -6,7 +6,9 @@ from pathlib import Path
|
|
|
6
6
|
from urllib.parse import urlsplit
|
|
7
7
|
|
|
8
8
|
import click
|
|
9
|
-
from
|
|
9
|
+
from eth_typing import ChecksumAddress
|
|
10
|
+
from pydantic import BaseModel, Field, ValidationError, field_validator, model_validator
|
|
11
|
+
from web3 import Web3
|
|
10
12
|
|
|
11
13
|
from ipor_fusion.types import ChainId
|
|
12
14
|
|
|
@@ -22,10 +24,19 @@ CONFIG_VERSION = 1
|
|
|
22
24
|
|
|
23
25
|
|
|
24
26
|
class VaultEntry(BaseModel):
|
|
25
|
-
address:
|
|
27
|
+
address: ChecksumAddress
|
|
26
28
|
label: str
|
|
27
29
|
chain_id: ChainId
|
|
28
30
|
|
|
31
|
+
@field_validator("address", mode="before")
|
|
32
|
+
@classmethod
|
|
33
|
+
def _checksum(cls, value: object) -> object:
|
|
34
|
+
# Configs written before 4.0 may hold any spelling; a value that is not
|
|
35
|
+
# an address is left for the commands to report, not for every load to fail.
|
|
36
|
+
if isinstance(value, str) and Web3.is_address(value):
|
|
37
|
+
return Web3.to_checksum_address(value)
|
|
38
|
+
return value
|
|
39
|
+
|
|
29
40
|
|
|
30
41
|
class FusionConfig(BaseModel):
|
|
31
42
|
providers: dict[str, str] = Field(default_factory=dict)
|
|
@@ -389,12 +389,12 @@ def _print_vault_v2(
|
|
|
389
389
|
click.echo("")
|
|
390
390
|
click.echo("Roles:")
|
|
391
391
|
click.echo(f" Owner: {vault.owner}")
|
|
392
|
-
click.echo(f" Curator: {vault.curator}")
|
|
392
|
+
click.echo(f" Curator: {vault.curator or ''}")
|
|
393
393
|
click.echo(
|
|
394
|
-
f" Performance fee: {vault.performance_fee * 100:.2f}% → {vault.performance_fee_recipient}"
|
|
394
|
+
f" Performance fee: {vault.performance_fee * 100:.2f}% → {vault.performance_fee_recipient or ''}"
|
|
395
395
|
)
|
|
396
396
|
click.echo(
|
|
397
|
-
f" Management fee: {vault.management_fee * 100:.2f}% → {vault.management_fee_recipient}"
|
|
397
|
+
f" Management fee: {vault.management_fee * 100:.2f}% → {vault.management_fee_recipient or ''}"
|
|
398
398
|
)
|
|
399
399
|
click.echo(f" Allocators ({len(vault.allocators)}):")
|
|
400
400
|
for addr in vault.allocators or ["(none)"]:
|
|
@@ -515,13 +515,13 @@ def _print_vault_v1(
|
|
|
515
515
|
f"({_format_amount(vault.total_assets, vault.asset_decimals)} total)"
|
|
516
516
|
)
|
|
517
517
|
click.echo(
|
|
518
|
-
f" Performance fee: {vault.fee_wad / WAD * 100:.2f}% → {vault.fee_recipient}"
|
|
518
|
+
f" Performance fee: {vault.fee_wad / WAD * 100:.2f}% → {vault.fee_recipient or ''}"
|
|
519
519
|
)
|
|
520
520
|
click.echo("")
|
|
521
521
|
click.echo("Roles:")
|
|
522
522
|
click.echo(f" Owner: {vault.owner}")
|
|
523
|
-
click.echo(f" Curator: {vault.curator}")
|
|
524
|
-
click.echo(f" Guardian: {vault.guardian}")
|
|
523
|
+
click.echo(f" Curator: {vault.curator or ''}")
|
|
524
|
+
click.echo(f" Guardian: {vault.guardian or ''}")
|
|
525
525
|
click.echo(f" Allocators ({len(vault.allocators)}):")
|
|
526
526
|
for addr in vault.allocators or ["(none)"]:
|
|
527
527
|
tag = (
|
|
@@ -551,7 +551,7 @@ def _print_vault_v1(
|
|
|
551
551
|
return
|
|
552
552
|
rows: list[tuple[str, ...]] = []
|
|
553
553
|
for alloc in allocations:
|
|
554
|
-
flow = vault.public_allocator_flow_caps.get(alloc.market_id
|
|
554
|
+
flow = vault.public_allocator_flow_caps.get(alloc.market_id, (0, 0))
|
|
555
555
|
flow_str = (
|
|
556
556
|
f"in {_format_amount(flow[0], alloc.loan_decimals)}" if flow[0] else "—"
|
|
557
557
|
)
|
|
@@ -95,7 +95,7 @@ def vault() -> None:
|
|
|
95
95
|
default=None,
|
|
96
96
|
help="Chain ID or name (auto-detected when only one provider is configured).",
|
|
97
97
|
)
|
|
98
|
-
def add(address:
|
|
98
|
+
def add(address: ChecksumAddress, label: str | None, chain_id: ChainId | None) -> None:
|
|
99
99
|
"""Save a vault to the config."""
|
|
100
100
|
cfg = load_config()
|
|
101
101
|
|
{ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev39}/src/ipor_fusion/cli/vault_markets_view.py
RENAMED
|
@@ -53,7 +53,7 @@ def _print_lending_health( # noqa: C901
|
|
|
53
53
|
for pb in positions:
|
|
54
54
|
coll_sym = resolve_token_symbol(ctx, pb.collateral_token) or "?"
|
|
55
55
|
loan_sym = resolve_token_symbol(ctx, pb.loan_token) or "?"
|
|
56
|
-
click.echo(f" morpho market
|
|
56
|
+
click.echo(f" morpho market {pb.market_id} ({coll_sym}/{loan_sym}):")
|
|
57
57
|
click.echo(
|
|
58
58
|
f" Collateral: {_format_token_amount(ctx, int(pb.collateral), pb.collateral_token, prices)}"
|
|
59
59
|
)
|
|
@@ -34,7 +34,7 @@ def build_morpho_blue_market_json(
|
|
|
34
34
|
market's parameters, state and rates (from `MorphoReader`), plus the
|
|
35
35
|
MetaMorpho vaults supplying it when ``api_market`` (`fetch_market`) is
|
|
36
36
|
given, or ``api_error`` when that lookup failed. ``market_id`` is the
|
|
37
|
-
|
|
37
|
+
canonical id (`parse_morpho_market_id`) and is written as is."""
|
|
38
38
|
out: dict = {
|
|
39
39
|
"market_id": market_id,
|
|
40
40
|
"chain_id": chain_id,
|
|
@@ -464,7 +464,11 @@ def vault_add(
|
|
|
464
464
|
save_config(cfg)
|
|
465
465
|
return ActionResult(message=f"Vault {label} ({address}) updated.")
|
|
466
466
|
|
|
467
|
-
cfg.vaults.append(
|
|
467
|
+
cfg.vaults.append(
|
|
468
|
+
VaultEntry(
|
|
469
|
+
address=Web3.to_checksum_address(address), label=label, chain_id=chain_id
|
|
470
|
+
)
|
|
471
|
+
)
|
|
468
472
|
save_config(cfg)
|
|
469
473
|
return ActionResult(message=f"Vault {label} ({address}) added.")
|
|
470
474
|
|
|
@@ -14,6 +14,7 @@ from urllib.error import URLError
|
|
|
14
14
|
from urllib.request import Request, urlopen
|
|
15
15
|
|
|
16
16
|
from eth_abi import decode, encode
|
|
17
|
+
from eth_typing import ChecksumAddress
|
|
17
18
|
from eth_utils import keccak
|
|
18
19
|
from web3 import Web3
|
|
19
20
|
|
|
@@ -97,7 +98,7 @@ class VaultFlowCap:
|
|
|
97
98
|
class VaultAllocation:
|
|
98
99
|
"""A MetaMorpho vault's exposure to a single Morpho Blue market."""
|
|
99
100
|
|
|
100
|
-
vault_address:
|
|
101
|
+
vault_address: ChecksumAddress
|
|
101
102
|
vault_name: str
|
|
102
103
|
vault_symbol: str
|
|
103
104
|
asset_symbol: str
|
|
@@ -115,10 +116,10 @@ class MorphoApiMarket:
|
|
|
115
116
|
|
|
116
117
|
market_id: MorphoBlueMarketId
|
|
117
118
|
lltv: int
|
|
118
|
-
loan_token:
|
|
119
|
+
loan_token: ChecksumAddress
|
|
119
120
|
loan_symbol: str
|
|
120
121
|
loan_decimals: int
|
|
121
|
-
collateral_token:
|
|
122
|
+
collateral_token: ChecksumAddress
|
|
122
123
|
collateral_symbol: str
|
|
123
124
|
collateral_decimals: int
|
|
124
125
|
oracle: str
|
|
@@ -179,13 +180,14 @@ def fetch_market(
|
|
|
179
180
|
|
|
180
181
|
def _parse_market(raw: dict) -> MorphoApiMarket:
|
|
181
182
|
state = raw.get("state") or {}
|
|
183
|
+
market_id = _api_market_id(raw.get("marketId"))
|
|
182
184
|
return MorphoApiMarket(
|
|
183
|
-
market_id=
|
|
185
|
+
market_id=market_id,
|
|
184
186
|
lltv=int(raw["lltv"]),
|
|
185
|
-
loan_token=raw["loanAsset"]["address"],
|
|
187
|
+
loan_token=_api_address(raw["loanAsset"]["address"]),
|
|
186
188
|
loan_symbol=raw["loanAsset"]["symbol"],
|
|
187
189
|
loan_decimals=int(raw["loanAsset"]["decimals"]),
|
|
188
|
-
collateral_token=raw["collateralAsset"]["address"],
|
|
190
|
+
collateral_token=_api_address(raw["collateralAsset"]["address"]),
|
|
189
191
|
collateral_symbol=raw["collateralAsset"]["symbol"],
|
|
190
192
|
collateral_decimals=int(raw["collateralAsset"]["decimals"]),
|
|
191
193
|
oracle=raw["oracle"]["address"],
|
|
@@ -198,9 +200,7 @@ def _parse_market(raw: dict) -> MorphoApiMarket:
|
|
|
198
200
|
borrow_apy=float(state.get("borrowApy") or 0.0),
|
|
199
201
|
fee_wad=int(state.get("fee") or 0),
|
|
200
202
|
timestamp=int(state.get("timestamp") or 0),
|
|
201
|
-
vaults=[
|
|
202
|
-
_parse_vault(v, raw["marketId"]) for v in raw.get("supplyingVaults") or []
|
|
203
|
-
],
|
|
203
|
+
vaults=[_parse_vault(v, market_id) for v in raw.get("supplyingVaults") or []],
|
|
204
204
|
)
|
|
205
205
|
|
|
206
206
|
|
|
@@ -281,8 +281,8 @@ class VaultV2Cap:
|
|
|
281
281
|
relative_cap_wad: int
|
|
282
282
|
allocation: int
|
|
283
283
|
market_id: MorphoBlueMarketId | None
|
|
284
|
-
loan_token:
|
|
285
|
-
collateral_token:
|
|
284
|
+
loan_token: ChecksumAddress | None
|
|
285
|
+
collateral_token: ChecksumAddress | None
|
|
286
286
|
oracle: str | None
|
|
287
287
|
irm: str | None
|
|
288
288
|
lltv: int | None
|
|
@@ -297,20 +297,22 @@ class VaultV2Cap:
|
|
|
297
297
|
class VaultV2Adapter:
|
|
298
298
|
"""An adapter contract attached to a Vault V2 (e.g. Morpho adapter)."""
|
|
299
299
|
|
|
300
|
-
address:
|
|
300
|
+
address: ChecksumAddress
|
|
301
301
|
adapter_type: str
|
|
302
302
|
assets: int
|
|
303
|
-
inner_vault:
|
|
303
|
+
inner_vault: (
|
|
304
|
+
ChecksumAddress | None
|
|
305
|
+
) # for MorphoVaultV2Adapter: the underlying market vault
|
|
304
306
|
|
|
305
307
|
|
|
306
308
|
@dataclass(slots=True)
|
|
307
309
|
class VaultV2Info:
|
|
308
310
|
"""Snapshot of a Morpho Vault V2."""
|
|
309
311
|
|
|
310
|
-
address:
|
|
312
|
+
address: ChecksumAddress
|
|
311
313
|
name: str
|
|
312
314
|
symbol: str
|
|
313
|
-
asset_address:
|
|
315
|
+
asset_address: ChecksumAddress
|
|
314
316
|
asset_symbol: str
|
|
315
317
|
asset_decimals: int
|
|
316
318
|
total_assets: int
|
|
@@ -319,13 +321,13 @@ class VaultV2Info:
|
|
|
319
321
|
share_price: float
|
|
320
322
|
max_apy: float
|
|
321
323
|
performance_fee: float
|
|
322
|
-
performance_fee_recipient:
|
|
324
|
+
performance_fee_recipient: ChecksumAddress | None
|
|
323
325
|
management_fee: float
|
|
324
|
-
management_fee_recipient:
|
|
325
|
-
owner:
|
|
326
|
-
curator:
|
|
327
|
-
allocators: list[
|
|
328
|
-
sentinels: list[
|
|
326
|
+
management_fee_recipient: ChecksumAddress | None
|
|
327
|
+
owner: ChecksumAddress
|
|
328
|
+
curator: ChecksumAddress | None
|
|
329
|
+
allocators: list[ChecksumAddress]
|
|
330
|
+
sentinels: list[ChecksumAddress]
|
|
329
331
|
liquidity_adapter: VaultV2Adapter | None
|
|
330
332
|
adapters: list[VaultV2Adapter]
|
|
331
333
|
caps: list[VaultV2Cap] = field(default_factory=list)
|
|
@@ -355,22 +357,24 @@ class VaultV1MarketAllocation:
|
|
|
355
357
|
class VaultV1Info:
|
|
356
358
|
"""Snapshot of a MetaMorpho V1 vault."""
|
|
357
359
|
|
|
358
|
-
address:
|
|
360
|
+
address: ChecksumAddress
|
|
359
361
|
name: str
|
|
360
362
|
symbol: str
|
|
361
|
-
asset_address:
|
|
363
|
+
asset_address: ChecksumAddress
|
|
362
364
|
asset_symbol: str
|
|
363
365
|
asset_decimals: int
|
|
364
366
|
total_assets: int
|
|
365
367
|
fee_wad: int
|
|
366
|
-
owner:
|
|
367
|
-
curator:
|
|
368
|
-
guardian:
|
|
369
|
-
fee_recipient:
|
|
370
|
-
allocators: list[
|
|
368
|
+
owner: ChecksumAddress
|
|
369
|
+
curator: ChecksumAddress | None
|
|
370
|
+
guardian: ChecksumAddress | None
|
|
371
|
+
fee_recipient: ChecksumAddress | None
|
|
372
|
+
allocators: list[ChecksumAddress]
|
|
371
373
|
allocations: list[VaultV1MarketAllocation]
|
|
372
374
|
public_allocator: VaultFlowCap | None # vault-level (no flowCap filtering)
|
|
373
|
-
public_allocator_flow_caps: dict[
|
|
375
|
+
public_allocator_flow_caps: dict[
|
|
376
|
+
MorphoBlueMarketId, tuple[int, int]
|
|
377
|
+
] # (maxIn, maxOut)
|
|
374
378
|
|
|
375
379
|
|
|
376
380
|
def fetch_vault(
|
|
@@ -431,10 +435,10 @@ def _parse_vault_v2(raw: dict) -> VaultV2Info:
|
|
|
431
435
|
]
|
|
432
436
|
caps = [_parse_v2_cap(c) for c in (raw.get("caps") or {}).get("items", []) if c]
|
|
433
437
|
return VaultV2Info(
|
|
434
|
-
address=raw
|
|
438
|
+
address=_api_address(raw.get("address")),
|
|
435
439
|
name=raw.get("name") or "",
|
|
436
440
|
symbol=raw.get("symbol") or "",
|
|
437
|
-
asset_address=asset.get("address")
|
|
441
|
+
asset_address=_api_address(asset.get("address")),
|
|
438
442
|
asset_symbol=asset.get("symbol") or "",
|
|
439
443
|
asset_decimals=int(asset.get("decimals") or 0),
|
|
440
444
|
total_assets=int(raw.get("totalAssets") or 0),
|
|
@@ -443,18 +447,22 @@ def _parse_vault_v2(raw: dict) -> VaultV2Info:
|
|
|
443
447
|
share_price=float(raw.get("sharePrice") or 0.0),
|
|
444
448
|
max_apy=float(raw.get("maxApy") or 0.0),
|
|
445
449
|
performance_fee=float(raw.get("performanceFee") or 0.0),
|
|
446
|
-
performance_fee_recipient=
|
|
450
|
+
performance_fee_recipient=_api_optional_address(
|
|
451
|
+
raw.get("performanceFeeRecipient")
|
|
452
|
+
),
|
|
447
453
|
management_fee=float(raw.get("managementFee") or 0.0),
|
|
448
|
-
management_fee_recipient=
|
|
449
|
-
|
|
450
|
-
|
|
454
|
+
management_fee_recipient=_api_optional_address(
|
|
455
|
+
raw.get("managementFeeRecipient")
|
|
456
|
+
),
|
|
457
|
+
owner=_api_address((raw.get("owner") or {}).get("address")),
|
|
458
|
+
curator=_api_optional_address((raw.get("curator") or {}).get("address")),
|
|
451
459
|
allocators=[
|
|
452
|
-
a["allocator"]["address"]
|
|
460
|
+
_api_address(a["allocator"]["address"])
|
|
453
461
|
for a in raw.get("allocators") or []
|
|
454
462
|
if (a.get("allocator") or {}).get("address")
|
|
455
463
|
],
|
|
456
464
|
sentinels=[
|
|
457
|
-
s["sentinel"]["address"]
|
|
465
|
+
_api_address(s["sentinel"]["address"])
|
|
458
466
|
for s in raw.get("sentinels") or []
|
|
459
467
|
if (s.get("sentinel") or {}).get("address")
|
|
460
468
|
],
|
|
@@ -466,10 +474,10 @@ def _parse_vault_v2(raw: dict) -> VaultV2Info:
|
|
|
466
474
|
|
|
467
475
|
def _parse_v2_adapter(raw: dict) -> VaultV2Adapter:
|
|
468
476
|
return VaultV2Adapter(
|
|
469
|
-
address=raw.get("address")
|
|
477
|
+
address=_api_address(raw.get("address")),
|
|
470
478
|
adapter_type=raw.get("type") or raw.get("__typename") or "",
|
|
471
479
|
assets=int(raw.get("assets") or 0),
|
|
472
|
-
inner_vault=(raw.get("metaMorpho") or {}).get("address"),
|
|
480
|
+
inner_vault=_api_optional_address((raw.get("metaMorpho") or {}).get("address")),
|
|
473
481
|
)
|
|
474
482
|
|
|
475
483
|
|
|
@@ -500,7 +508,10 @@ def _parse_v2_cap(raw: dict) -> VaultV2Cap:
|
|
|
500
508
|
|
|
501
509
|
def _decode_market_v1_id_data(
|
|
502
510
|
id_data: str,
|
|
503
|
-
) ->
|
|
511
|
+
) -> (
|
|
512
|
+
tuple[ChecksumAddress, ChecksumAddress, ChecksumAddress, ChecksumAddress, int]
|
|
513
|
+
| None
|
|
514
|
+
):
|
|
504
515
|
"""Decode a Vault V2 `MarketV1`-type cap idData into MarketParams.
|
|
505
516
|
|
|
506
517
|
Vault V2 encodes the cap idData as `abi.encode(string idType, address adapter,
|
|
@@ -548,6 +559,16 @@ def _decode_market_v1_id_data(
|
|
|
548
559
|
)
|
|
549
560
|
|
|
550
561
|
|
|
562
|
+
def _api_address(value: object) -> ChecksumAddress:
|
|
563
|
+
if not isinstance(value, str) or not Web3.is_address(value):
|
|
564
|
+
raise MorphoApiError(f"Morpho API returned an invalid address: {value!r}")
|
|
565
|
+
return Web3.to_checksum_address(value)
|
|
566
|
+
|
|
567
|
+
|
|
568
|
+
def _api_optional_address(value: object) -> ChecksumAddress | None:
|
|
569
|
+
return None if value is None or value == "" else _api_address(value)
|
|
570
|
+
|
|
571
|
+
|
|
551
572
|
def _api_market_id(value: object) -> MorphoBlueMarketId:
|
|
552
573
|
try:
|
|
553
574
|
return parse_morpho_market_id(str(value or ""))
|
|
@@ -598,7 +619,7 @@ def _parse_vault_v1(raw: dict) -> VaultV1Info:
|
|
|
598
619
|
|
|
599
620
|
pac = raw.get("publicAllocatorConfig")
|
|
600
621
|
flow_cap_summary: VaultFlowCap | None = None
|
|
601
|
-
flow_caps: dict[
|
|
622
|
+
flow_caps: dict[MorphoBlueMarketId, tuple[int, int]] = {}
|
|
602
623
|
if pac:
|
|
603
624
|
flow_cap_summary = VaultFlowCap(
|
|
604
625
|
fee_wei=int(pac.get("fee") or 0),
|
|
@@ -607,25 +628,29 @@ def _parse_vault_v1(raw: dict) -> VaultV1Info:
|
|
|
607
628
|
admin=pac.get("admin"),
|
|
608
629
|
)
|
|
609
630
|
for fc in pac.get("flowCaps") or []:
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
631
|
+
if raw_mid := (fc.get("market") or {}).get("marketId"):
|
|
632
|
+
flow_caps[_api_market_id(raw_mid)] = (
|
|
633
|
+
int(fc.get("maxIn") or 0),
|
|
634
|
+
int(fc.get("maxOut") or 0),
|
|
635
|
+
)
|
|
613
636
|
|
|
614
637
|
return VaultV1Info(
|
|
615
|
-
address=raw
|
|
638
|
+
address=_api_address(raw.get("address")),
|
|
616
639
|
name=raw.get("name") or "",
|
|
617
640
|
symbol=raw.get("symbol") or "",
|
|
618
|
-
asset_address=asset.get("address")
|
|
641
|
+
asset_address=_api_address(asset.get("address")),
|
|
619
642
|
asset_symbol=asset.get("symbol") or "",
|
|
620
643
|
asset_decimals=int(asset.get("decimals") or 0),
|
|
621
644
|
total_assets=int(state.get("totalAssets") or 0),
|
|
622
645
|
fee_wad=int(state.get("fee") or 0),
|
|
623
|
-
owner=state.get("owner")
|
|
624
|
-
curator=state.get("curator")
|
|
625
|
-
guardian=state.get("guardian")
|
|
626
|
-
fee_recipient=state.get("feeRecipient")
|
|
646
|
+
owner=_api_address(state.get("owner")),
|
|
647
|
+
curator=_api_optional_address(state.get("curator")),
|
|
648
|
+
guardian=_api_optional_address(state.get("guardian")),
|
|
649
|
+
fee_recipient=_api_optional_address(state.get("feeRecipient")),
|
|
627
650
|
allocators=[
|
|
628
|
-
a["address"]
|
|
651
|
+
_api_address(a["address"])
|
|
652
|
+
for a in raw.get("allocators") or []
|
|
653
|
+
if a.get("address")
|
|
629
654
|
],
|
|
630
655
|
allocations=allocations,
|
|
631
656
|
public_allocator=flow_cap_summary,
|
|
@@ -633,27 +658,28 @@ def _parse_vault_v1(raw: dict) -> VaultV1Info:
|
|
|
633
658
|
)
|
|
634
659
|
|
|
635
660
|
|
|
661
|
+
def _is_market(entry: dict, market_id: MorphoBlueMarketId) -> bool:
|
|
662
|
+
"""Whether an allocation or flow-cap ``entry`` names ``market_id``, in any
|
|
663
|
+
spelling the API returns."""
|
|
664
|
+
raw = (entry.get("market") or {}).get("marketId")
|
|
665
|
+
try:
|
|
666
|
+
return parse_morpho_market_id(str(raw or "")) == market_id
|
|
667
|
+
except ValueError:
|
|
668
|
+
return False
|
|
669
|
+
|
|
670
|
+
|
|
636
671
|
def _parse_vault(raw: dict, market_id: MorphoBlueMarketId) -> VaultAllocation:
|
|
637
672
|
asset = raw.get("asset") or {}
|
|
638
673
|
state = raw.get("state") or {}
|
|
639
674
|
allocation_for_market: dict = next(
|
|
640
|
-
(
|
|
641
|
-
a
|
|
642
|
-
for a in (state.get("allocation") or [])
|
|
643
|
-
if (a.get("market") or {}).get("marketId", "").lower() == market_id
|
|
644
|
-
),
|
|
675
|
+
(a for a in (state.get("allocation") or []) if _is_market(a, market_id)),
|
|
645
676
|
{},
|
|
646
677
|
)
|
|
647
678
|
pac = raw.get("publicAllocatorConfig")
|
|
648
679
|
flow_cap: VaultFlowCap | None = None
|
|
649
680
|
if pac:
|
|
650
681
|
flow_for_market = next(
|
|
651
|
-
(
|
|
652
|
-
fc
|
|
653
|
-
for fc in (pac.get("flowCaps") or [])
|
|
654
|
-
if (fc.get("market") or {}).get("marketId", "").lower()
|
|
655
|
-
== market_id.lower()
|
|
656
|
-
),
|
|
682
|
+
(fc for fc in (pac.get("flowCaps") or []) if _is_market(fc, market_id)),
|
|
657
683
|
None,
|
|
658
684
|
)
|
|
659
685
|
if flow_for_market:
|
|
@@ -667,7 +693,7 @@ def _parse_vault(raw: dict, market_id: MorphoBlueMarketId) -> VaultAllocation:
|
|
|
667
693
|
a["address"] for a in (raw.get("allocators") or []) if a.get("address")
|
|
668
694
|
]
|
|
669
695
|
return VaultAllocation(
|
|
670
|
-
vault_address=raw["address"],
|
|
696
|
+
vault_address=_api_address(raw["address"]),
|
|
671
697
|
vault_name=raw.get("name") or "",
|
|
672
698
|
vault_symbol=raw.get("symbol") or "",
|
|
673
699
|
asset_symbol=asset.get("symbol") or "",
|
|
@@ -98,7 +98,7 @@ class VaultState(Protocol):
|
|
|
98
98
|
@property
|
|
99
99
|
def block_timestamp(self) -> int: ...
|
|
100
100
|
@property
|
|
101
|
-
def asset(self) ->
|
|
101
|
+
def asset(self) -> ChecksumAddress: ...
|
|
102
102
|
@property
|
|
103
103
|
def asset_symbol(self) -> str: ...
|
|
104
104
|
@property
|
|
@@ -219,11 +219,11 @@ class VaultValidation:
|
|
|
219
219
|
found. Raw amounts are in the underlying ``asset``'s decimals.
|
|
220
220
|
"""
|
|
221
221
|
|
|
222
|
-
vault:
|
|
222
|
+
vault: ChecksumAddress
|
|
223
223
|
chain_id: ChainId
|
|
224
224
|
block_number: int
|
|
225
225
|
block_timestamp: int
|
|
226
|
-
asset:
|
|
226
|
+
asset: ChecksumAddress
|
|
227
227
|
asset_symbol: str
|
|
228
228
|
asset_decimals: int
|
|
229
229
|
asset_price_usd: float | None
|
|
@@ -505,7 +505,7 @@ def _morpho_breakdown_json(
|
|
|
505
505
|
prices = data.token_prices_usd
|
|
506
506
|
return [
|
|
507
507
|
{
|
|
508
|
-
"morpho_market_id":
|
|
508
|
+
"morpho_market_id": pb.market_id,
|
|
509
509
|
"collateral_symbol": resolve_token_symbol(ctx, pb.collateral_token) or None,
|
|
510
510
|
"loan_symbol": resolve_token_symbol(ctx, pb.loan_token) or None,
|
|
511
511
|
"collateral": _build_breakdown_amount_json(
|
|
@@ -63,7 +63,11 @@ from ipor_fusion.readers.lending_health import (
|
|
|
63
63
|
)
|
|
64
64
|
from ipor_fusion.readers.morpho import MORPHO_BLUE_ADDRESSES, MorphoPositionBreakdown
|
|
65
65
|
from ipor_fusion.substrates import decode_substrate, market_name
|
|
66
|
-
from ipor_fusion.types import
|
|
66
|
+
from ipor_fusion.types import (
|
|
67
|
+
Amount,
|
|
68
|
+
MarketId,
|
|
69
|
+
parse_morpho_market_id,
|
|
70
|
+
)
|
|
67
71
|
from ipor_fusion.validation import Erc20Holdings
|
|
68
72
|
from ipor_fusion.vault_data import (
|
|
69
73
|
FeeData,
|
|
@@ -86,6 +90,7 @@ from ipor_fusion.vault_info import (
|
|
|
86
90
|
_build_dependency_graph_json,
|
|
87
91
|
_build_fees_json,
|
|
88
92
|
_build_share_price_json,
|
|
93
|
+
_morpho_breakdown_json,
|
|
89
94
|
_partition_balance_fuses,
|
|
90
95
|
)
|
|
91
96
|
|
|
@@ -1509,7 +1514,7 @@ _TOKEN_WETH = Web3.to_checksum_address("0x" + "33" * 20)
|
|
|
1509
1514
|
|
|
1510
1515
|
def _morpho_breakdown(market_id: str = "ab" * 32):
|
|
1511
1516
|
return MorphoPositionBreakdown(
|
|
1512
|
-
market_id=
|
|
1517
|
+
market_id=parse_morpho_market_id(market_id),
|
|
1513
1518
|
loan_token=_TOKEN_USDC,
|
|
1514
1519
|
collateral_token=_TOKEN_WETH,
|
|
1515
1520
|
collateral=Amount(10 * 10**18),
|
|
@@ -1705,6 +1710,30 @@ class TestFetcherCollectors:
|
|
|
1705
1710
|
assert 14 in out and 1 not in out
|
|
1706
1711
|
assert len(out[14]) == 1
|
|
1707
1712
|
|
|
1713
|
+
@patch("ipor_fusion.vault_info.resolve_token_decimals", return_value=18)
|
|
1714
|
+
@patch("ipor_fusion.vault_info.resolve_token_symbol", return_value="WETH")
|
|
1715
|
+
@patch("ipor_fusion.vault_data.resolve_token_decimals", return_value=18)
|
|
1716
|
+
@patch("ipor_fusion.vault_data.resolve_token_symbol", return_value="WETH")
|
|
1717
|
+
def test_collected_morpho_id_renders_with_one_prefix(
|
|
1718
|
+
self, _sym, _dec, _view_sym, _view_dec, capsys
|
|
1719
|
+
):
|
|
1720
|
+
(market_id,) = _collect_morpho_substrates({14: [bytes.fromhex("ab" * 32)]})[14]
|
|
1721
|
+
breakdown = MorphoPositionBreakdown(
|
|
1722
|
+
market_id=market_id,
|
|
1723
|
+
loan_token=_TOKEN_USDC,
|
|
1724
|
+
collateral_token=_TOKEN_WETH,
|
|
1725
|
+
collateral=Amount(0),
|
|
1726
|
+
borrow_assets=Amount(0),
|
|
1727
|
+
supply_assets=Amount(0),
|
|
1728
|
+
)
|
|
1729
|
+
data = _make_data(morpho_positions={14: [breakdown]})
|
|
1730
|
+
(entry,) = _morpho_breakdown_json(MagicMock(), data, [breakdown])
|
|
1731
|
+
assert entry["morpho_market_id"] == "0x" + "ab" * 32
|
|
1732
|
+
_print_lending_health(MagicMock(), data)
|
|
1733
|
+
out = capsys.readouterr().out
|
|
1734
|
+
assert f"morpho market 0x{'ab' * 32} " in out
|
|
1735
|
+
assert "0x0x" not in out
|
|
1736
|
+
|
|
1708
1737
|
def test_collect_morpho_substrates_skips_wrong_length(self):
|
|
1709
1738
|
bad = bytes.fromhex("aa" * 16) # wrong length
|
|
1710
1739
|
out = _collect_morpho_substrates({14: [bad]})
|
|
@@ -21,6 +21,7 @@ from ipor_fusion.morpho_api import (
|
|
|
21
21
|
fetch_market,
|
|
22
22
|
fetch_vault,
|
|
23
23
|
)
|
|
24
|
+
from ipor_fusion.types import parse_morpho_market_id
|
|
24
25
|
|
|
25
26
|
MARKET_ID = "ad656d430bb3d8c1469bf45c8ad4ebae1b04be04757c69fa424eec78d7b3f4dc"
|
|
26
27
|
LOAN_TOKEN = Web3.to_checksum_address("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48")
|
|
@@ -381,6 +382,60 @@ class TestMorphoApiClient:
|
|
|
381
382
|
assert vault.flow_cap.admin == ADMIN_ADDR
|
|
382
383
|
assert vault.allocators == [PUBLIC_ALLOCATOR_ETH, ADMIN_ADDR]
|
|
383
384
|
|
|
385
|
+
def test_fetch_market_matches_allocations_in_any_spelling(self):
|
|
386
|
+
upper = "0x" + MARKET_ID.upper()
|
|
387
|
+
market = {"market": {"marketId": upper}}
|
|
388
|
+
body = {
|
|
389
|
+
"data": {
|
|
390
|
+
"marketById": {
|
|
391
|
+
"marketId": upper,
|
|
392
|
+
"lltv": "915000000000000000",
|
|
393
|
+
"loanAsset": {
|
|
394
|
+
"address": LOAN_TOKEN,
|
|
395
|
+
"symbol": "USDC",
|
|
396
|
+
"decimals": 6,
|
|
397
|
+
},
|
|
398
|
+
"collateralAsset": {
|
|
399
|
+
"address": COLLATERAL_TOKEN,
|
|
400
|
+
"symbol": "WOUSD",
|
|
401
|
+
"decimals": 18,
|
|
402
|
+
},
|
|
403
|
+
"oracle": {"address": ORACLE},
|
|
404
|
+
"irmAddress": IRM,
|
|
405
|
+
"state": {},
|
|
406
|
+
"supplyingVaults": [
|
|
407
|
+
{
|
|
408
|
+
"address": VAULT_ADDR,
|
|
409
|
+
"symbol": "v",
|
|
410
|
+
"name": "v",
|
|
411
|
+
"asset": {"symbol": "USDC", "decimals": 6},
|
|
412
|
+
"state": {
|
|
413
|
+
"allocation": [
|
|
414
|
+
{**market, "supplyAssets": 123, "supplyCap": 456}
|
|
415
|
+
]
|
|
416
|
+
},
|
|
417
|
+
"publicAllocatorConfig": {
|
|
418
|
+
"fee": 0,
|
|
419
|
+
"flowCaps": [{**market, "maxIn": 7, "maxOut": 8}],
|
|
420
|
+
},
|
|
421
|
+
}
|
|
422
|
+
],
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
with patch("ipor_fusion.morpho_api.urlopen") as mock_open:
|
|
427
|
+
mock_resp = MagicMock()
|
|
428
|
+
mock_resp.read.return_value = json.dumps(body).encode()
|
|
429
|
+
mock_open.return_value.__enter__.return_value = mock_resp
|
|
430
|
+
|
|
431
|
+
result = fetch_market(parse_morpho_market_id(MARKET_ID), 1)
|
|
432
|
+
|
|
433
|
+
assert result.market_id == "0x" + MARKET_ID.lower()
|
|
434
|
+
vault = result.vaults[0]
|
|
435
|
+
assert (vault.supply_assets, vault.supply_cap) == (123, 456)
|
|
436
|
+
assert vault.flow_cap is not None
|
|
437
|
+
assert (vault.flow_cap.max_in, vault.flow_cap.max_out) == (7, 8)
|
|
438
|
+
|
|
384
439
|
def test_fetch_market_handles_missing_market(self):
|
|
385
440
|
with patch("ipor_fusion.morpho_api.urlopen") as mock_open:
|
|
386
441
|
mock_resp = MagicMock()
|