ipor-fusion 4.0.0.dev60__tar.gz → 4.0.0.dev63__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.dev60 → ipor_fusion-4.0.0.dev63}/PKG-INFO +1 -1
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/pyproject.toml +1 -1
- ipor_fusion-4.0.0.dev63/src/ipor_fusion/readers/oracle_mapping.py +484 -0
- ipor_fusion-4.0.0.dev63/src/ipor_fusion/readers/oracle_mapping_nodes.py +367 -0
- ipor_fusion-4.0.0.dev63/src/ipor_fusion/readers/oracle_mapping_reader.py +242 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/test_mcp_server.py +7 -5
- ipor_fusion-4.0.0.dev60/src/ipor_fusion/readers/oracle_mapping.py +0 -1042
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/.claude-plugin/marketplace.json +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/.claude-plugin/plugin.json +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/.env.example +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/.gemini/settings.json +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/.git-blame-ignore-revs +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/.github/workflows/cd.yml +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/.github/workflows/ci.yml +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/.github/workflows/commit-lint.yml +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/.github/workflows/notify-slack.yml +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/.github/workflows/python-build.yml +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/.github/workflows/python-publish.yml +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/.github/workflows/release.yml +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/.github/workflows/report-slack.yml +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/.gitignore +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/.mcp.json +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/.pre-commit-config.yaml +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/.python-version +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/AGENTS.md +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/CHANGELOG.md +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/CLAUDE.md +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/CODEOWNERS +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/LICENSE +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/MIGRATION_4.md +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/README.md +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/build.sh +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/examples/CONTRIBUTING.md +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/examples/README.md +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/examples/advanced_euler_v2_credit_market_base.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/examples/advanced_euler_v2_looping_arbitrum.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/examples/composed_hypercore_flow_arbitrum_hyperevm.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/examples/crosschain_ccip_usdc_arbitrum_hyperevm.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/examples/external_state_margin_leg_base.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/examples/simple_aave_v3_supply_base.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/pytest.ini +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/scripts/commit-check.sh +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/scripts/crosschain_readiness.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/skills/ipor-deploy-vault/SKILL.md +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/about.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/cache.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/chains.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/cli/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/cli/changelog_cmd.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/cli/config_cmd.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/cli/config_store.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/cli/main.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/cli/market_cmd.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/cli/options.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/cli/oracle_mapping_view.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/cli/vault_cmd.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/cli/vault_health.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/cli/vault_info_view.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/cli/vault_markets_view.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/cli/vault_rendering.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/config/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/config/roles.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/core/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/core/access.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/core/address.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/core/context.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/core/contract.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/core/erc20.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/core/external_state_executor.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/core/fee_manager.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/core/fuse_action.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/core/fusion_factory.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/core/logs.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/core/multicall.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/core/oracle.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/core/plasma_vault.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/core/rewards_manager.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/core/simulation.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/core/withdraw_manager.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/crosschain/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/crosschain/ccip/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/crosschain/ccip/chainlink.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/crosschain/ccip/codec.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/crosschain/ccip/contracts.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/crosschain/ccip/events.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/crosschain/ccip/indexer.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/crosschain/ccip/lane.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/crosschain/ccip/quote.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/crosschain/ccip/transport.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/crosschain/contracts.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/crosschain/discovery.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/crosschain/errors.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/crosschain/lane.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/crosschain/logs.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/crosschain/messages.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/crosschain/simulation.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/crosschain/stargate/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/crosschain/stargate/contracts.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/crosschain/stargate/lane.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/crosschain/stargate/layerzero.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/crosschain/stargate/transport.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/crosschain/transport.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/dependency_graph.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/entry_cli.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/entry_mcp.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/errors.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/explorer.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/field_docs.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/fuses/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/fuses/aave_v3.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/fuses/async_action.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/fuses/base.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/fuses/compound_v3.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/fuses/crosschain/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/fuses/crosschain/base.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/fuses/crosschain/ccip.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/fuses/crosschain/stargate.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/fuses/erc4626.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/fuses/euler_v2.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/fuses/events.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/fuses/external_state.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/fuses/external_state_events.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/fuses/fluid_instadapp.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/fuses/gearbox_v3.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/fuses/hypercore.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/fuses/hypercore_events.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/fuses/merkl.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/fuses/morpho.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/fuses/ramses_v2.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/fuses/uniswap_v3.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/fuses/universal.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/guide/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/guide/architecture.md +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/guide/glossary.md +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/guide/invariants.md +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/guide/quickstart.md +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/market_ids.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/market_info.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/mcp/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/mcp/guide.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/mcp/models.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/mcp/server.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/morpho_api.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/py.typed +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/readers/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/readers/aave_v3.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/readers/aave_v4.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/readers/compound_v3.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/readers/hypercore.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/readers/hypercore_outcome.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/readers/lending_health.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/readers/morpho.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/readers/position_manager.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/readers/ramses_v2.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/readers/uniswap_v3.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/readers/unpriceable.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/substrates.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/types.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/validation.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/vault_data.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/vault_info.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/vault_managers.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/vault_positions.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/vault_reads.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/UniswapV3UniversalRouter.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/_crosschain.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/_crosschain_lifecycle.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/_crosschain_recovery.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/_euler_v2.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/_foundry.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/_guide.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/_hypercore_model.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/_hypercore_shadow.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/_multicall.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/_simulate.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/_validation.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/addresses.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/conftest.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/constants.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/fixtures/hypercore_hip3_flow.json +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/fixtures/hypercore_market55.json +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/fixtures/vault_info_golden/clean.json +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/fixtures/vault_info_golden/clean.txt +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/fixtures/vault_info_golden/rich.json +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/fixtures/vault_info_golden/rich.txt +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/test_about.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/test_access_manager.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/test_cache.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/test_chains.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/test_cli_changelog.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/test_cli_commands.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/test_cli_config_store.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/test_cli_helpers.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/test_cli_hypercore.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/test_cli_lending_health.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/test_cli_market_morpho_blue.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/test_cli_oracle_mapping.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/test_cli_pending_requests_scan.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/test_cli_role_accounts.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/test_cli_vault_info_golden.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/test_cli_vault_predates_getters.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/test_cli_vault_validate.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/test_contract_call.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/test_contract_encoder.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/test_crosschain_ccip_events.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/test_crosschain_ccip_indexer.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/test_crosschain_ccip_quote.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/test_crosschain_chainlink.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/test_crosschain_contracts.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/test_crosschain_encoding.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/test_crosschain_errors.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/test_crosschain_lane.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/test_crosschain_layering.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/test_crosschain_readiness.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/test_crosschain_readiness_report.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/test_crosschain_transport.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/test_erc20.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/test_examples_composed_hypercore.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/test_examples_crosschain.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/test_examples_euler_looping_arbitrum.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/test_examples_vaults.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/test_explorer.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/test_external_state_events.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/test_external_state_executor.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/test_fee_manager.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/test_fuse_encoding.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/test_guide.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/test_hypercore_deployment.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/test_hypercore_events.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/test_hypercore_flow.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/test_hypercore_live.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/test_hypercore_outcome.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/test_hypercore_readers.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/test_hypercore_simulation.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/test_logs.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/test_mcp_hypercore.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/test_mcp_models.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/test_mcp_vault_validate.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/test_multicall.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/test_oracle.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/test_oracle_mapping.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/test_plasma_vault.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/test_reader_encoding.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/test_readiness_selection.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/test_revert_decoding.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/test_rewards_manager.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/test_roles.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/test_sdk_layering.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/test_simulate_borrow_aave_v3_mainnet.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/test_simulate_claim_merkl_wrapper_base.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/test_simulate_claim_morpho_rewards.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/test_simulate_crosschain_canary.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/test_simulate_crosschain_hypercore.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/test_simulate_crosschain_hyperevm.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/test_simulate_crosschain_lifecycle.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/test_simulate_euler_v2_batch_base.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/test_simulate_euler_v2_credit_market_base.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/test_simulate_euler_v2_swap_deploy_base.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/test_simulate_euler_v2_swap_reconfigure_base.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/test_simulate_euler_v2_swap_registry_base.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/test_simulate_examples_arbitrum.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/test_simulate_examples_base.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/test_simulate_examples_crosschain.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/test_simulate_external_state_executor_base.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/test_simulate_fusion_factory_clone_base.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/test_simulate_looping_aave_v3_base.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/test_simulate_looping_morpho_blue_base.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/test_simulate_morpho_blue.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/test_simulate_pilot_v3.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/test_simulate_pilot_v4_uniswap.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/test_simulate_pilot_v5_ramses.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/test_simulate_pilot_v5_scheduled.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/test_simulate_plasma_vault_setters_base.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/test_simulate_readers_integration.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/test_simulate_release_funds.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/test_simulate_swap_on_base.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/test_simulate_vault_from_scratch_base.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/test_solidity_compiler.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/test_solidity_mirrors.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/test_substrates.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/test_types_and_events.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/test_unpriceable_tokens.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/test_validation.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/test_vault_dep_graph.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/test_vault_info_onchain_base.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/test_vault_info_onchain_base_no_withdraw_manager.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/test_vault_simulator_blocks.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/test_web3_context.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/test_withdraw_manager.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/tests/typed_client/consumer.py +0 -0
- {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev63}/uv.lock +0 -0
|
@@ -0,0 +1,484 @@
|
|
|
1
|
+
"""Resolve how a Plasma Vault prices every configured asset at a given block.
|
|
2
|
+
|
|
3
|
+
Powers the ``fusion vault oracle-mapping`` CLI command and the
|
|
4
|
+
``vault_oracle_mapping`` MCP tool. For each asset the vault's price oracle
|
|
5
|
+
knows about, this walks the *source* price feed, classifies its type by
|
|
6
|
+
interface probing, reads the effective price, and recursively resolves any
|
|
7
|
+
feed that derives its price from another asset (ERC4626 wrappers, Morpho
|
|
8
|
+
collateral feeds). Unknown feeds are reported as partial, never dropped.
|
|
9
|
+
A zero source is not a dead end: the oracle may be a manager variant that
|
|
10
|
+
delegates such assets to an underlying global middleware — that delegation is
|
|
11
|
+
followed and reported as ``middleware_fallback``. Every aggregator-compatible
|
|
12
|
+
read (Chainlink-tier leaves, dual-cross-reference component feeds) carries
|
|
13
|
+
``description()`` and the full ``latestRoundData()`` round in
|
|
14
|
+
``source_detail`` — raw values plus ISO-8601 UTC twins of the timestamps,
|
|
15
|
+
no staleness judgment; ``description`` is null when the feed does not
|
|
16
|
+
implement it.
|
|
17
|
+
|
|
18
|
+
Chainlink leaves are graded by on-chain evidence: ``ChainlinkAggregator``
|
|
19
|
+
only when the full AggregatorV3Interface answers (``latestRoundData`` +
|
|
20
|
+
``decimals`` + ``description`` + ``version``) and nothing contradicts it;
|
|
21
|
+
``chainlink_style`` when the feed merely quacks like an aggregator — verify
|
|
22
|
+
the address yourself if identity matters. Grading only ever withholds the
|
|
23
|
+
confirmed label; it cannot prove a contract's identity or operator.
|
|
24
|
+
|
|
25
|
+
Statuses: a node is ``resolved`` (own feed explained and every dependency
|
|
26
|
+
resolved), ``partially_resolved`` (own feed explained, but some descendant is
|
|
27
|
+
not resolved), or ``partial`` (the node's own resolution is incomplete —
|
|
28
|
+
``reason`` says why). The mapping-level ``status`` rolls up the roots:
|
|
29
|
+
``resolved`` when every configured asset resolved (vacuously so for zero
|
|
30
|
+
assets), ``unresolved`` when every root is ``partial`` (total failure),
|
|
31
|
+
``partially_resolved`` otherwise. The ``unresolved`` array mirrors
|
|
32
|
+
``partial`` nodes only; demoted parents self-describe.
|
|
33
|
+
|
|
34
|
+
Design:
|
|
35
|
+
- All network access goes through :class:`OracleMappingReader` (the single
|
|
36
|
+
seam). Every read is "safe": a revert/decode failure yields ``None`` so one
|
|
37
|
+
bad asset never aborts the whole map. The pure logic (classification,
|
|
38
|
+
recursion, event collapse) takes a reader and is unit-testable with a fake.
|
|
39
|
+
- SDK primitives are reused for the reads that already exist
|
|
40
|
+
(``PriceOracleMiddleware``, ``PriceOracleMiddlewareManager``, ``ERC20``,
|
|
41
|
+
``PlasmaVault``); only the feed-probe reads are added here.
|
|
42
|
+
|
|
43
|
+
Adding a feed type:
|
|
44
|
+
|
|
45
|
+
1. Wrapper class (``ContractWrapper``) exposing its getters.
|
|
46
|
+
2. Revert-safe reader probes on :class:`OracleMappingReader` (``None`` on
|
|
47
|
+
failure); both live in ``oracle_mapping_reader``.
|
|
48
|
+
3. ``TYPE_*`` constant (``oracle_mapping_nodes``) + ``_resolve_*`` function
|
|
49
|
+
(``oracle_mapping_nodes`` for a leaf, here when it recurses); recurse only into
|
|
50
|
+
middleware assets — component *feeds* belong in ``source_detail``.
|
|
51
|
+
4. Dispatch entry in ``_classify_and_resolve``, ordered by specificity,
|
|
52
|
+
above the generic Chainlink-tier fallback. Gate on the type-defining
|
|
53
|
+
reads, not a single common-named getter (false-matcher magnet); an
|
|
54
|
+
incomplete gate falls through with no type claim.
|
|
55
|
+
5. External doc touchpoints: ``cli/vault_cmd.py`` (command docstring +
|
|
56
|
+
rendering), ``mcp/server.py`` tool docstring and ``mcp/models.py``
|
|
57
|
+
``source_type`` description (the last two are tripwire-tested).
|
|
58
|
+
6. Tests against the fake reader: precedence over the generic aggregator
|
|
59
|
+
fallback, happy path, per-probe degradation to ``partial``, incomplete
|
|
60
|
+
gate falling through, and ABI-decode cases for the new reader probes.
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
from __future__ import annotations
|
|
64
|
+
|
|
65
|
+
from eth_typing import ChecksumAddress
|
|
66
|
+
|
|
67
|
+
from ipor_fusion.core.context import Web3Context
|
|
68
|
+
from ipor_fusion.core.plasma_vault import PlasmaVault
|
|
69
|
+
from ipor_fusion.readers.oracle_mapping_nodes import (
|
|
70
|
+
TYPE_CHAINLINK,
|
|
71
|
+
TYPE_CHAINLINK_STYLE,
|
|
72
|
+
TYPE_DUAL_XREF,
|
|
73
|
+
TYPE_ERC4626,
|
|
74
|
+
TYPE_MIDDLEWARE_FALLBACK,
|
|
75
|
+
TYPE_MORPHO,
|
|
76
|
+
TYPE_UNKNOWN,
|
|
77
|
+
WAD_DECIMALS,
|
|
78
|
+
ZERO_ADDRESS,
|
|
79
|
+
OracleAsset,
|
|
80
|
+
OracleMapping,
|
|
81
|
+
OracleNode,
|
|
82
|
+
OraclePrice,
|
|
83
|
+
_apply_dependency_status,
|
|
84
|
+
_collect_unresolved,
|
|
85
|
+
_mapping_status,
|
|
86
|
+
_partial,
|
|
87
|
+
_price_block,
|
|
88
|
+
_resolve_chainlink,
|
|
89
|
+
_resolve_dual_xref,
|
|
90
|
+
collapse_sources,
|
|
91
|
+
normalize_wad,
|
|
92
|
+
)
|
|
93
|
+
from ipor_fusion.readers.oracle_mapping_reader import (
|
|
94
|
+
ASSET_PRICE_SOURCE_UPDATED_TOPIC,
|
|
95
|
+
OracleMappingReader,
|
|
96
|
+
)
|
|
97
|
+
from ipor_fusion.types import (
|
|
98
|
+
AssetSource,
|
|
99
|
+
PlasmaVaultAddress,
|
|
100
|
+
Price,
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
__all__ = [
|
|
104
|
+
"ASSET_PRICE_SOURCE_UPDATED_TOPIC",
|
|
105
|
+
"TYPE_CHAINLINK",
|
|
106
|
+
"TYPE_CHAINLINK_STYLE",
|
|
107
|
+
"TYPE_DUAL_XREF",
|
|
108
|
+
"TYPE_ERC4626",
|
|
109
|
+
"TYPE_MIDDLEWARE_FALLBACK",
|
|
110
|
+
"TYPE_MORPHO",
|
|
111
|
+
"TYPE_UNKNOWN",
|
|
112
|
+
"WAD_DECIMALS",
|
|
113
|
+
"ZERO_ADDRESS",
|
|
114
|
+
"OracleAsset",
|
|
115
|
+
"OracleMapping",
|
|
116
|
+
"OracleMappingReader",
|
|
117
|
+
"OracleNode",
|
|
118
|
+
"OraclePrice",
|
|
119
|
+
"build_oracle_mapping",
|
|
120
|
+
"collapse_sources",
|
|
121
|
+
"normalize_wad",
|
|
122
|
+
"resolve_asset",
|
|
123
|
+
]
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def _resolve_erc4626(
|
|
127
|
+
reader: OracleMappingReader,
|
|
128
|
+
node: OracleNode,
|
|
129
|
+
label: str,
|
|
130
|
+
vault: ChecksumAddress,
|
|
131
|
+
underlying: ChecksumAddress,
|
|
132
|
+
depth: int,
|
|
133
|
+
visited: set[str],
|
|
134
|
+
max_depth: int,
|
|
135
|
+
) -> OracleNode:
|
|
136
|
+
"""Resolve an ERC4626 feed: share→underlying rate, then recurse on the underlying."""
|
|
137
|
+
share_decimals = reader.vault_decimals(vault)
|
|
138
|
+
rate = (
|
|
139
|
+
reader.vault_convert_to_assets(vault, 10**share_decimals)
|
|
140
|
+
if share_decimals is not None
|
|
141
|
+
else None
|
|
142
|
+
)
|
|
143
|
+
node.source_type = TYPE_ERC4626
|
|
144
|
+
node.source_detail = {
|
|
145
|
+
"vault": vault,
|
|
146
|
+
"underlying": underlying,
|
|
147
|
+
"share_decimals": share_decimals,
|
|
148
|
+
"rate": str(rate) if rate is not None else None,
|
|
149
|
+
}
|
|
150
|
+
dep = _resolve(reader, underlying, depth + 1, visited, max_depth)
|
|
151
|
+
node.dependencies = [dep]
|
|
152
|
+
node.path = [label, "convertToAssets(1 share)", *dep.path]
|
|
153
|
+
if rate is None:
|
|
154
|
+
# No derived price without the share→asset rate — same "complete data
|
|
155
|
+
# or partial" standard as dual-xref; the dep is still attached.
|
|
156
|
+
node.status = "partial"
|
|
157
|
+
node.reason = "erc4626_rate_unreadable"
|
|
158
|
+
return node
|
|
159
|
+
node.status = "resolved"
|
|
160
|
+
return node
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def _resolve_morpho(
|
|
164
|
+
reader: OracleMappingReader,
|
|
165
|
+
node: OracleNode,
|
|
166
|
+
label: str,
|
|
167
|
+
source: ChecksumAddress,
|
|
168
|
+
morpho_oracle: ChecksumAddress,
|
|
169
|
+
loan: ChecksumAddress,
|
|
170
|
+
depth: int,
|
|
171
|
+
visited: set[str],
|
|
172
|
+
max_depth: int,
|
|
173
|
+
) -> OracleNode:
|
|
174
|
+
"""Resolve a Morpho collateral feed: market price, then recurse on the loan token."""
|
|
175
|
+
collateral = reader.feed_collateral_token(source)
|
|
176
|
+
price = reader.morpho_oracle_price(morpho_oracle)
|
|
177
|
+
node.source_type = TYPE_MORPHO
|
|
178
|
+
node.source_detail = {
|
|
179
|
+
"morpho_oracle": morpho_oracle,
|
|
180
|
+
"collateral_token": collateral,
|
|
181
|
+
"loan_token": loan,
|
|
182
|
+
"morpho_price": str(price) if price is not None else None,
|
|
183
|
+
}
|
|
184
|
+
dep = _resolve(reader, loan, depth + 1, visited, max_depth)
|
|
185
|
+
node.dependencies = [dep]
|
|
186
|
+
node.path = [label, "Morpho collateral/loan oracle", *dep.path]
|
|
187
|
+
node.status = "resolved"
|
|
188
|
+
return node
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def _resolve_middleware_fallback(
|
|
192
|
+
reader: OracleMappingReader,
|
|
193
|
+
asset: ChecksumAddress,
|
|
194
|
+
symbol: str | None,
|
|
195
|
+
decimals: int | None,
|
|
196
|
+
source: ChecksumAddress | None,
|
|
197
|
+
label: str,
|
|
198
|
+
price: Price | None,
|
|
199
|
+
depth: int,
|
|
200
|
+
visited: set[str],
|
|
201
|
+
max_depth: int,
|
|
202
|
+
) -> OracleNode:
|
|
203
|
+
"""Explain a zero/unreadable source instead of declaring the asset dead.
|
|
204
|
+
|
|
205
|
+
On a ``PriceOracleMiddlewareManager`` a zero source means "no per-vault
|
|
206
|
+
override" — the price comes from the underlying global middleware, so the
|
|
207
|
+
asset is re-resolved there to surface the real feed. Without a manager, a
|
|
208
|
+
readable price means the middleware priced the asset through its own
|
|
209
|
+
internal fallback (the Chainlink FeedRegistry on mainnet).
|
|
210
|
+
"""
|
|
211
|
+
underlying_middleware = reader.underlying_middleware()
|
|
212
|
+
if (
|
|
213
|
+
underlying_middleware is not None
|
|
214
|
+
and underlying_middleware.lower() == ZERO_ADDRESS
|
|
215
|
+
):
|
|
216
|
+
underlying_middleware = None
|
|
217
|
+
if underlying_middleware is None and price is None:
|
|
218
|
+
return _partial(
|
|
219
|
+
asset,
|
|
220
|
+
symbol,
|
|
221
|
+
decimals,
|
|
222
|
+
source,
|
|
223
|
+
TYPE_UNKNOWN,
|
|
224
|
+
"no_source_configured",
|
|
225
|
+
[label],
|
|
226
|
+
price,
|
|
227
|
+
)
|
|
228
|
+
node = OracleNode(
|
|
229
|
+
asset=asset,
|
|
230
|
+
symbol=symbol,
|
|
231
|
+
decimals=decimals,
|
|
232
|
+
source=source,
|
|
233
|
+
price=_price_block(price),
|
|
234
|
+
)
|
|
235
|
+
node.source_type = TYPE_MIDDLEWARE_FALLBACK
|
|
236
|
+
node.status = "resolved"
|
|
237
|
+
if underlying_middleware is not None:
|
|
238
|
+
# Same asset, different oracle — must not be in `visited` yet; runaway
|
|
239
|
+
# manager→manager chains are bounded by max_depth instead.
|
|
240
|
+
dep = _resolve(
|
|
241
|
+
reader.delegate(underlying_middleware),
|
|
242
|
+
asset,
|
|
243
|
+
depth + 1,
|
|
244
|
+
visited,
|
|
245
|
+
max_depth,
|
|
246
|
+
)
|
|
247
|
+
node.dependencies = [dep]
|
|
248
|
+
# dep re-resolves the same asset, so drop its leading label from the path.
|
|
249
|
+
node.path = [label, "middleware fallback", *dep.path[1:]]
|
|
250
|
+
node.source_detail = {
|
|
251
|
+
"delegated_to": underlying_middleware,
|
|
252
|
+
"chainlink_feed_registry": None,
|
|
253
|
+
}
|
|
254
|
+
if price is None:
|
|
255
|
+
# On-chain the manager's zero-source getAssetPrice IS this
|
|
256
|
+
# delegation, so its own read failing while the hop answers means
|
|
257
|
+
# an anomaly (transient read, or a feed answering 0/negative) —
|
|
258
|
+
# surface it instead of reporting resolved with a null price.
|
|
259
|
+
node.status = "partial"
|
|
260
|
+
node.reason = "manager_price_unreadable"
|
|
261
|
+
return node
|
|
262
|
+
# Not a manager, yet the price read succeeded (else the no_source_configured
|
|
263
|
+
# early return above would have fired): the middleware's internal fallback
|
|
264
|
+
# (FeedRegistry on mainnet) priced it — report a leaf.
|
|
265
|
+
registry = reader.chainlink_feed_registry()
|
|
266
|
+
if registry is not None and registry.lower() == ZERO_ADDRESS:
|
|
267
|
+
registry = None
|
|
268
|
+
node.path = [label, "middleware fallback"]
|
|
269
|
+
node.source_detail = {"delegated_to": None, "chainlink_feed_registry": registry}
|
|
270
|
+
return node
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def _classify_and_resolve(
|
|
274
|
+
reader: OracleMappingReader,
|
|
275
|
+
node: OracleNode,
|
|
276
|
+
label: str,
|
|
277
|
+
source: ChecksumAddress,
|
|
278
|
+
depth: int,
|
|
279
|
+
visited: set[str],
|
|
280
|
+
max_depth: int,
|
|
281
|
+
) -> OracleNode:
|
|
282
|
+
"""Classify the source by interface probing and dispatch to its type resolver."""
|
|
283
|
+
# Probe most specific first; Chainlink's interface is shared by the others.
|
|
284
|
+
# Partial getter match = wrong type hypothesis, not a broken feed — fall
|
|
285
|
+
# through (a foreign getter answering caps the leaf at chainlink_style).
|
|
286
|
+
# New types: see the "Adding a feed type" checklist in the module docstring.
|
|
287
|
+
asset_x = reader.feed_asset_x(source)
|
|
288
|
+
if asset_x is not None:
|
|
289
|
+
xy_feed = reader.feed_asset_x_asset_y_feed(source)
|
|
290
|
+
y_usd_feed = reader.feed_asset_y_usd_feed(source)
|
|
291
|
+
if xy_feed is not None and y_usd_feed is not None:
|
|
292
|
+
return _resolve_dual_xref(reader, node, label, asset_x, xy_feed, y_usd_feed)
|
|
293
|
+
morpho_oracle = reader.feed_morpho_oracle(source)
|
|
294
|
+
if morpho_oracle is not None:
|
|
295
|
+
# gate: the type-defining loanToken() must answer too — a lone
|
|
296
|
+
# morphoOracle() hit is a false matcher, not a broken Morpho feed
|
|
297
|
+
loan = reader.feed_loan_token(source)
|
|
298
|
+
if loan is not None:
|
|
299
|
+
return _resolve_morpho(
|
|
300
|
+
reader,
|
|
301
|
+
node,
|
|
302
|
+
label,
|
|
303
|
+
source,
|
|
304
|
+
morpho_oracle,
|
|
305
|
+
loan,
|
|
306
|
+
depth,
|
|
307
|
+
visited,
|
|
308
|
+
max_depth,
|
|
309
|
+
)
|
|
310
|
+
vault = reader.feed_vault(source)
|
|
311
|
+
if vault is not None:
|
|
312
|
+
# gate: vault() is a common method name — only a vault answering
|
|
313
|
+
# asset() defines the ERC4626 hypothesis
|
|
314
|
+
underlying = reader.vault_asset(vault)
|
|
315
|
+
if underlying is not None:
|
|
316
|
+
return _resolve_erc4626(
|
|
317
|
+
reader, node, label, vault, underlying, depth, visited, max_depth
|
|
318
|
+
)
|
|
319
|
+
rnd = reader.feed_latest_round_data(source)
|
|
320
|
+
if rnd is not None:
|
|
321
|
+
has_foreign_getter = (
|
|
322
|
+
asset_x is not None or morpho_oracle is not None or vault is not None
|
|
323
|
+
)
|
|
324
|
+
return _resolve_chainlink(
|
|
325
|
+
reader, node, label, source, rnd, has_foreign_getter=has_foreign_getter
|
|
326
|
+
)
|
|
327
|
+
node.source_type = TYPE_UNKNOWN
|
|
328
|
+
node.status = "partial"
|
|
329
|
+
node.reason = "unsupported_custom_feed"
|
|
330
|
+
node.path = [label, "custom feed (unresolved)"]
|
|
331
|
+
return node
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
def _resolve(
|
|
335
|
+
reader: OracleMappingReader,
|
|
336
|
+
asset: ChecksumAddress,
|
|
337
|
+
depth: int,
|
|
338
|
+
visited: set[str],
|
|
339
|
+
max_depth: int,
|
|
340
|
+
) -> OracleNode:
|
|
341
|
+
"""Resolve one asset into a node (recursively for derived feeds)."""
|
|
342
|
+
symbol = reader.symbol(asset)
|
|
343
|
+
decimals = reader.token_decimals(asset)
|
|
344
|
+
label = symbol or asset
|
|
345
|
+
key = asset.lower()
|
|
346
|
+
|
|
347
|
+
# The oracle's getAssetPrice is authoritative and independent of how far we
|
|
348
|
+
# can explain the feed, so read it up front — even cycle/depth-capped nodes
|
|
349
|
+
# carry the real price.
|
|
350
|
+
price = reader.asset_price(asset)
|
|
351
|
+
|
|
352
|
+
if key in visited:
|
|
353
|
+
return _partial(
|
|
354
|
+
asset,
|
|
355
|
+
symbol,
|
|
356
|
+
decimals,
|
|
357
|
+
None,
|
|
358
|
+
TYPE_UNKNOWN,
|
|
359
|
+
"cycle_detected",
|
|
360
|
+
[label],
|
|
361
|
+
price,
|
|
362
|
+
)
|
|
363
|
+
if depth > max_depth:
|
|
364
|
+
return _partial(
|
|
365
|
+
asset,
|
|
366
|
+
symbol,
|
|
367
|
+
decimals,
|
|
368
|
+
None,
|
|
369
|
+
TYPE_UNKNOWN,
|
|
370
|
+
"max_depth_exceeded",
|
|
371
|
+
[label],
|
|
372
|
+
price,
|
|
373
|
+
)
|
|
374
|
+
|
|
375
|
+
source = reader.source_of(asset)
|
|
376
|
+
if source is None or source.lower() == ZERO_ADDRESS:
|
|
377
|
+
return _apply_dependency_status(
|
|
378
|
+
_resolve_middleware_fallback(
|
|
379
|
+
reader,
|
|
380
|
+
asset,
|
|
381
|
+
symbol,
|
|
382
|
+
decimals,
|
|
383
|
+
source,
|
|
384
|
+
label,
|
|
385
|
+
price,
|
|
386
|
+
depth,
|
|
387
|
+
visited,
|
|
388
|
+
max_depth,
|
|
389
|
+
)
|
|
390
|
+
)
|
|
391
|
+
|
|
392
|
+
node = OracleNode(
|
|
393
|
+
asset=asset,
|
|
394
|
+
symbol=symbol,
|
|
395
|
+
decimals=decimals,
|
|
396
|
+
source=source,
|
|
397
|
+
price=_price_block(price),
|
|
398
|
+
)
|
|
399
|
+
visited.add(key)
|
|
400
|
+
try:
|
|
401
|
+
return _apply_dependency_status(
|
|
402
|
+
_classify_and_resolve(
|
|
403
|
+
reader, node, label, source, depth, visited, max_depth
|
|
404
|
+
)
|
|
405
|
+
)
|
|
406
|
+
finally:
|
|
407
|
+
visited.discard(key)
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
def resolve_asset(
|
|
411
|
+
reader: OracleMappingReader, asset: ChecksumAddress, max_depth: int
|
|
412
|
+
) -> OracleNode:
|
|
413
|
+
"""Public entry: resolve a single asset into a node tree."""
|
|
414
|
+
return _resolve(reader, asset, 0, set(), max_depth)
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
def _enumerate_assets(
|
|
418
|
+
reader: OracleMappingReader, effective_block: int
|
|
419
|
+
) -> tuple[list[ChecksumAddress], AssetSource]:
|
|
420
|
+
"""Configured assets via ``getConfiguredAssets`` or event-replay fallback."""
|
|
421
|
+
assets = reader.configured_assets()
|
|
422
|
+
if assets is not None:
|
|
423
|
+
return assets, "getConfiguredAssets"
|
|
424
|
+
events = reader.asset_source_events(effective_block)
|
|
425
|
+
return list(collapse_sources(events)), "events"
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
def build_oracle_mapping(
|
|
429
|
+
ctx: Web3Context,
|
|
430
|
+
vault_address: PlasmaVaultAddress,
|
|
431
|
+
effective_block: int,
|
|
432
|
+
max_depth: int = 6,
|
|
433
|
+
) -> OracleMapping:
|
|
434
|
+
"""Build the full oracle mapping for ``vault_address``.
|
|
435
|
+
|
|
436
|
+
``ctx`` must already be pinned to ``effective_block`` (the caller sets
|
|
437
|
+
``ctx.default_block``); the block is echoed in the output. Raises
|
|
438
|
+
`UnsupportedVaultVersionError` for a vault that exposes no price-oracle
|
|
439
|
+
getter.
|
|
440
|
+
"""
|
|
441
|
+
vault = PlasmaVault(ctx, vault_address)
|
|
442
|
+
oracle_addr = vault.price_oracle_address()
|
|
443
|
+
asset_addr = vault.underlying_asset_address().call()
|
|
444
|
+
|
|
445
|
+
try:
|
|
446
|
+
vault_name = vault.name().call()
|
|
447
|
+
except Exception:
|
|
448
|
+
vault_name = None
|
|
449
|
+
|
|
450
|
+
reader = OracleMappingReader(ctx, oracle_addr)
|
|
451
|
+
assets, asset_source = _enumerate_assets(reader, effective_block)
|
|
452
|
+
|
|
453
|
+
configured: list[OracleNode] = []
|
|
454
|
+
for asset in assets:
|
|
455
|
+
try:
|
|
456
|
+
configured.append(resolve_asset(reader, asset, max_depth))
|
|
457
|
+
except Exception:
|
|
458
|
+
configured.append(
|
|
459
|
+
_partial(
|
|
460
|
+
asset,
|
|
461
|
+
reader.symbol(asset),
|
|
462
|
+
reader.token_decimals(asset),
|
|
463
|
+
None,
|
|
464
|
+
TYPE_UNKNOWN,
|
|
465
|
+
"resolution_error",
|
|
466
|
+
[asset],
|
|
467
|
+
)
|
|
468
|
+
)
|
|
469
|
+
|
|
470
|
+
return OracleMapping(
|
|
471
|
+
vault=vault_address,
|
|
472
|
+
vault_name=vault_name,
|
|
473
|
+
asset=OracleAsset(
|
|
474
|
+
address=asset_addr,
|
|
475
|
+
symbol=reader.symbol(asset_addr),
|
|
476
|
+
decimals=reader.token_decimals(asset_addr),
|
|
477
|
+
),
|
|
478
|
+
price_oracle=oracle_addr,
|
|
479
|
+
block_number=effective_block,
|
|
480
|
+
asset_source=asset_source,
|
|
481
|
+
status=_mapping_status(configured),
|
|
482
|
+
configured_assets=configured,
|
|
483
|
+
unresolved=_collect_unresolved(configured),
|
|
484
|
+
)
|