ipor-fusion 4.0.0.dev164__tar.gz → 4.0.0.dev171__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.dev164 → ipor_fusion-4.0.0.dev171}/AGENTS.md +10 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/MIGRATION_4.md +26 -1
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/PKG-INFO +1 -1
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/examples/advanced_euler_v2_credit_market_base.py +1 -3
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/examples/advanced_euler_v2_looping_arbitrum.py +1 -3
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/examples/composed_hypercore_flow_arbitrum_hyperevm.py +1 -1
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/examples/crosschain_ccip_usdc_arbitrum_hyperevm.py +1 -1
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/examples/external_state_margin_leg_base.py +1 -3
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/examples/simple_aave_v3_supply_base.py +1 -3
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/pyproject.toml +1 -1
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/__init__.py +2 -2
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/core/simulation.py +52 -19
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/fuses/__init__.py +1 -1
- ipor_fusion-4.0.0.dev171/tests/_golden.py +69 -0
- ipor_fusion-4.0.0.dev171/tests/_oracle_mapping_fake.py +200 -0
- ipor_fusion-4.0.0.dev171/tests/fixtures/market_golden/meta_morpho_v1.json +99 -0
- ipor_fusion-4.0.0.dev171/tests/fixtures/market_golden/meta_morpho_v1.mcp.json +111 -0
- ipor_fusion-4.0.0.dev171/tests/fixtures/market_golden/meta_morpho_v2.json +135 -0
- ipor_fusion-4.0.0.dev171/tests/fixtures/market_golden/meta_morpho_v2.mcp.json +140 -0
- ipor_fusion-4.0.0.dev171/tests/fixtures/market_golden/morpho_api/market.json +208 -0
- ipor_fusion-4.0.0.dev171/tests/fixtures/market_golden/morpho_api/vault_v1.json +168 -0
- ipor_fusion-4.0.0.dev171/tests/fixtures/market_golden/morpho_api/vault_v2.json +115 -0
- ipor_fusion-4.0.0.dev171/tests/fixtures/market_golden/morpho_api/vault_v2_not_found.json +12 -0
- ipor_fusion-4.0.0.dev171/tests/fixtures/market_golden/morpho_blue.json +107 -0
- ipor_fusion-4.0.0.dev171/tests/fixtures/market_golden/morpho_blue.mcp.json +108 -0
- ipor_fusion-4.0.0.dev171/tests/fixtures/market_golden/morpho_blue_api_error.json +28 -0
- ipor_fusion-4.0.0.dev171/tests/fixtures/market_golden/morpho_blue_api_error.mcp.json +31 -0
- ipor_fusion-4.0.0.dev171/tests/fixtures/market_golden/morpho_blue_no_api.json +27 -0
- ipor_fusion-4.0.0.dev171/tests/fixtures/market_golden/morpho_blue_no_api.mcp.json +31 -0
- ipor_fusion-4.0.0.dev171/tests/fixtures/oracle_mapping_golden/mapping.json +573 -0
- ipor_fusion-4.0.0.dev171/tests/fixtures/oracle_mapping_golden/mapping.mcp.json +573 -0
- ipor_fusion-4.0.0.dev171/tests/fixtures/vault_info_golden/clean.mcp.json +136 -0
- ipor_fusion-4.0.0.dev171/tests/fixtures/vault_info_golden/deployment_error.json +131 -0
- ipor_fusion-4.0.0.dev171/tests/fixtures/vault_info_golden/deployment_error.mcp.json +138 -0
- ipor_fusion-4.0.0.dev171/tests/fixtures/vault_info_golden/deployment_error.txt +54 -0
- ipor_fusion-4.0.0.dev171/tests/fixtures/vault_info_golden/full.json +688 -0
- ipor_fusion-4.0.0.dev171/tests/fixtures/vault_info_golden/full.mcp.json +709 -0
- ipor_fusion-4.0.0.dev171/tests/fixtures/vault_info_golden/full.txt +154 -0
- ipor_fusion-4.0.0.dev171/tests/fixtures/vault_info_golden/rich.mcp.json +406 -0
- ipor_fusion-4.0.0.dev171/tests/test_cli_market_golden.py +199 -0
- ipor_fusion-4.0.0.dev171/tests/test_cli_oracle_mapping_golden.py +275 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_cli_vault_info_golden.py +264 -16
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_oracle_mapping.py +25 -193
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_public_api.py +10 -0
- ipor_fusion-4.0.0.dev171/tests/test_simulation_errors.py +229 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/.claude-plugin/marketplace.json +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/.claude-plugin/plugin.json +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/.env.example +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/.gemini/settings.json +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/.git-blame-ignore-revs +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/.github/workflows/cd.yml +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/.github/workflows/ci.yml +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/.github/workflows/commit-lint.yml +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/.github/workflows/notify-slack.yml +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/.github/workflows/python-build.yml +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/.github/workflows/python-publish.yml +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/.github/workflows/release.yml +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/.github/workflows/report-slack.yml +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/.gitignore +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/.mcp.json +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/.pre-commit-config.yaml +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/.python-version +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/CHANGELOG.md +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/CLAUDE.md +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/CODEOWNERS +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/LICENSE +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/README.md +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/build.sh +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/examples/CONTRIBUTING.md +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/examples/README.md +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/pytest.ini +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/scripts/commit-check.sh +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/scripts/crosschain_readiness.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/skills/ipor-deploy-vault/SKILL.md +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/about.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/cache.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/chains.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/cli/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/cli/changelog_cmd.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/cli/config_cmd.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/cli/config_store.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/cli/main.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/cli/market_cmd.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/cli/options.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/cli/oracle_mapping_view.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/cli/vault_cmd.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/cli/vault_health.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/cli/vault_info_view.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/cli/vault_markets_view.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/cli/vault_rendering.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/config/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/config/roles.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/core/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/core/access.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/core/address.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/core/context.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/core/contract.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/core/erc20.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/core/external_state_executor.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/core/fee_manager.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/core/fuse.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/core/fuse_action.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/core/fusion_factory.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/core/logs.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/core/multicall.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/core/oracle.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/core/plasma_vault.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/core/rewards_manager.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/core/withdraw_manager.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/crosschain/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/crosschain/ccip/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/crosschain/ccip/chainlink.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/crosschain/ccip/codec.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/crosschain/ccip/contracts.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/crosschain/ccip/events.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/crosschain/ccip/indexer.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/crosschain/ccip/lane.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/crosschain/ccip/quote.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/crosschain/ccip/transport.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/crosschain/contracts.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/crosschain/discovery.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/crosschain/errors.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/crosschain/fuses/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/crosschain/fuses/base.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/crosschain/fuses/ccip.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/crosschain/fuses/stargate.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/crosschain/lane.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/crosschain/logs.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/crosschain/messages.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/crosschain/simulation.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/crosschain/stargate/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/crosschain/stargate/contracts.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/crosschain/stargate/lane.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/crosschain/stargate/layerzero.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/crosschain/stargate/transport.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/crosschain/transport.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/dependency_graph.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/entry_cli.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/entry_mcp.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/errors.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/explorer.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/field_docs.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/fuses/aave_v3.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/fuses/async_action.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/fuses/base.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/fuses/compound_v3.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/fuses/erc4626.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/fuses/euler_v2.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/fuses/events.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/fuses/external_state.py +0 -0
- /ipor_fusion-4.0.0.dev164/src/ipor_fusion/fuses/external_state_events.py → /ipor_fusion-4.0.0.dev171/src/ipor_fusion/fuses/external_state_event_decoding.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/fuses/fluid_instadapp.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/fuses/gearbox_v3.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/fuses/hypercore.py +0 -0
- /ipor_fusion-4.0.0.dev164/src/ipor_fusion/fuses/hypercore_events.py → /ipor_fusion-4.0.0.dev171/src/ipor_fusion/fuses/hypercore_event_decoding.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/fuses/merkl.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/fuses/morpho.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/fuses/ramses_v2.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/fuses/uniswap_v3.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/fuses/universal.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/guide/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/guide/architecture.md +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/guide/glossary.md +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/guide/invariants.md +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/guide/quickstart.md +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/market_ids.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/market_info.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/mcp/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/mcp/guide.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/mcp/models.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/mcp/server.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/morpho_api.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/py.typed +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/readers/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/readers/aave_v3.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/readers/aave_v4.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/readers/compound_v3.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/readers/hypercore.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/readers/hypercore_outcome.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/readers/lending_health.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/readers/morpho.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/readers/oracle_mapping.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/readers/oracle_mapping_nodes.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/readers/oracle_mapping_reader.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/readers/position_manager.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/readers/ramses_v2.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/readers/uniswap_v3.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/readers/unpriceable.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/substrates.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/types.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/validation.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/vault_data.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/vault_erc20.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/vault_info.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/vault_managers.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/vault_positions.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/src/ipor_fusion/vault_reads.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/UniswapV3UniversalRouter.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/_crosschain.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/_crosschain_lifecycle.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/_crosschain_recovery.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/_euler_v2.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/_event_args.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/_foundry.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/_guide.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/_hypercore_model.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/_hypercore_shadow.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/_multicall.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/_simulate.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/_validation.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/addresses.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/conftest.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/constants.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/fixtures/hypercore_hip3_flow.json +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/fixtures/hypercore_market55.json +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/fixtures/simulate_v1_request.json +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/fixtures/vault_info_golden/clean.json +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/fixtures/vault_info_golden/clean.txt +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/fixtures/vault_info_golden/rich.json +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/fixtures/vault_info_golden/rich.txt +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_about.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_access_manager.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_cache.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_chains.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_cli_changelog.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_cli_commands.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_cli_config_store.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_cli_helpers.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_cli_hypercore.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_cli_lending_health.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_cli_market_morpho_blue.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_cli_oracle_mapping.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_cli_pending_requests_scan.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_cli_role_accounts.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_cli_vault_predates_getters.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_cli_vault_validate.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_contract_call.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_contract_encoder.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_crosschain_ccip_events.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_crosschain_ccip_indexer.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_crosschain_ccip_quote.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_crosschain_chainlink.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_crosschain_contracts.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_crosschain_encoding.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_crosschain_errors.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_crosschain_lane.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_crosschain_layering.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_crosschain_readiness.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_crosschain_readiness_report.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_crosschain_transport.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_docs_typing.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_erc20.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_error_hierarchy.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_examples_composed_hypercore.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_examples_crosschain.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_examples_euler_looping_arbitrum.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_examples_vaults.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_explorer.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_external_state_events.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_external_state_executor.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_fee_manager.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_fuse_encoding.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_fusion_instance.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_guide.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_hypercore_deployment.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_hypercore_events.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_hypercore_flow.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_hypercore_live.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_hypercore_outcome.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_hypercore_readers.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_hypercore_simulation.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_logs.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_mcp_hypercore.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_mcp_models.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_mcp_server.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_mcp_vault_validate.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_multicall.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_oracle.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_plasma_vault.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_public_type_reachability.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_reader_encoding.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_readiness_selection.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_revert_decoding.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_rewards_manager.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_roles.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_sdk_layering.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_simulate_borrow_aave_v3_mainnet.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_simulate_claim_merkl_wrapper_base.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_simulate_claim_morpho_rewards.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_simulate_crosschain_canary.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_simulate_crosschain_hypercore.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_simulate_crosschain_hyperevm.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_simulate_crosschain_lifecycle.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_simulate_euler_v2_batch_base.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_simulate_euler_v2_credit_market_base.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_simulate_euler_v2_swap_deploy_base.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_simulate_euler_v2_swap_reconfigure_base.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_simulate_euler_v2_swap_registry_base.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_simulate_examples_arbitrum.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_simulate_examples_base.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_simulate_examples_crosschain.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_simulate_external_state_executor_base.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_simulate_fusion_factory_clone_base.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_simulate_looping_aave_v3_base.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_simulate_looping_morpho_blue_base.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_simulate_morpho_blue.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_simulate_pilot_v3.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_simulate_pilot_v4_uniswap.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_simulate_pilot_v5_ramses.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_simulate_pilot_v5_scheduled.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_simulate_plasma_vault_setters_base.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_simulate_readers_integration.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_simulate_release_funds.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_simulate_swap_on_base.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_simulate_vault_from_scratch_base.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_simulation_observation.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_solidity_compiler.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_solidity_mirrors.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_substrates.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_types_and_events.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_unpriceable_tokens.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_validation.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_vault_data_block.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_vault_dep_graph.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_vault_info_onchain_base.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_vault_info_onchain_base_no_withdraw_manager.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_vault_simulator_blocks.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_web3_context.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/test_withdraw_manager.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/typed_client/consumer.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/tests/typed_client/rejected.py +0 -0
- {ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/uv.lock +0 -0
|
@@ -32,9 +32,19 @@ uv run pytest -m "cli or mcp" --no-cov # fast offline sub
|
|
|
32
32
|
uv run pytest --run-readiness -m readiness --no-cov -rA # opt-in live Ethereum-hub POC checks
|
|
33
33
|
uv run python scripts/crosschain_readiness.py --output /tmp/crosschain-readiness.json # live Arbitrum/HyperEVM report
|
|
34
34
|
uv run pytest tests/test_fuse_encoding.py -k aave --no-cov # single file / test
|
|
35
|
+
UPDATE_GOLDEN=1 uv run pytest -k golden --no-cov # rewrite pinned CLI/MCP output (tests/fixtures/*_golden/)
|
|
35
36
|
uv lock --check # uv.lock in sync with pyproject.toml
|
|
36
37
|
```
|
|
37
38
|
|
|
39
|
+
**Golden output.** The exact text of `fusion vault info` (text and `--json`),
|
|
40
|
+
`fusion market morpho-blue|meta-morpho --json`, `fusion vault oracle-mapping
|
|
41
|
+
--json` and the matching MCP tools (`vault_info`, `market_morpho_blue`,
|
|
42
|
+
`market_meta_morpho`, `vault_oracle_mapping`, called through the server) is pinned
|
|
43
|
+
in `tests/fixtures/*_golden/` (`tests/_golden.py`, the `*_golden.py` tests), from
|
|
44
|
+
recorded inputs and no network. A change to any of them fails those tests;
|
|
45
|
+
regenerate with `UPDATE_GOLDEN=1` only when the change is intended and review the
|
|
46
|
+
fixture diff. Without it the tests only compare.
|
|
47
|
+
|
|
38
48
|
Run format, check, pyright and pytest after every code change; that is exactly
|
|
39
49
|
the sequence in `.github/workflows/python-build.yml`. When ruff `C901` trips,
|
|
40
50
|
extract helpers, never suppress.
|
|
@@ -306,6 +306,14 @@ The internal `ipor_fusion.fuses.base` still re-exports them. The CLI's internal
|
|
|
306
306
|
`ipor_fusion.cli.vault_cmd` was split into `options`, `vault_info_view`,
|
|
307
307
|
`vault_markets_view` and `oracle_mapping_view` (private names, not API).
|
|
308
308
|
|
|
309
|
+
**Event decoder modules renamed.** `ipor_fusion.fuses.external_state_events` and
|
|
310
|
+
`ipor_fusion.fuses.hypercore_events` are now `external_state_event_decoding` and
|
|
311
|
+
`hypercore_event_decoding`. The receipt decoder `hypercore_events`, re-exported
|
|
312
|
+
from `ipor_fusion.fuses`, hid its submodule of the same name (`import
|
|
313
|
+
ipor_fusion.fuses.hypercore_events as m` bound the function); the external-state
|
|
314
|
+
module was renamed to match. Import the decoders with `from ipor_fusion import
|
|
315
|
+
external_state_events, hypercore_events`; the modules are internal.
|
|
316
|
+
|
|
309
317
|
**Removed.**
|
|
310
318
|
|
|
311
319
|
- `IporFusionMarkets.RWA`: market 50 is `IporFusionMarkets.EXTERNAL_STATE`.
|
|
@@ -379,7 +387,7 @@ is the wrong one.
|
|
|
379
387
|
from `IporFusionError`, so an `except IporFusionError` placed before their own
|
|
380
388
|
handlers catches them first. All three live in `ipor_fusion.errors`, like every
|
|
381
389
|
other SDK exception, and are exported from `ipor_fusion`; the modules that raise
|
|
382
|
-
them (`ipor_fusion.morpho_api`, `ipor_fusion.fuses.
|
|
390
|
+
them (`ipor_fusion.morpho_api`, `ipor_fusion.fuses.external_state_event_decoding`,
|
|
383
391
|
`ipor_fusion.crosschain.ccip.indexer`) still import the same classes.
|
|
384
392
|
- `AccessManager.read_owner(ctx)` raises the new `OwnerNotFoundError` (a
|
|
385
393
|
`LookupError`) when no account holds `OWNER_ROLE`, instead of an `IndexError`.
|
|
@@ -951,5 +959,22 @@ are listed with a link.
|
|
|
951
959
|
decoder cannot read raises the decoder's error. 3.x left it out of
|
|
952
960
|
`result.observations` (so `result.get(label)` raised `KeyError`) or reported an
|
|
953
961
|
undecodable read as `None`, indistinguishable from a read that returns `None`.
|
|
962
|
+
- **Check `success`; `error` is always set on failure.** A failed
|
|
963
|
+
`SimulatedCallResult` always carries a non-empty `error`: the node's message joined
|
|
964
|
+
with the decoded revert (`"execution reverted: NotAllowed"`), the decoded revert
|
|
965
|
+
alone when the node sent no message, else `"reverted without a reason"` (with the
|
|
966
|
+
return data's hex when it is too short to decode). 3.x could report `success=False`
|
|
967
|
+
with `error=None`, and `error` was the node's message only. A successful call's
|
|
968
|
+
`error` is `None`, and constructing one with an `error` raises `ValueError`.
|
|
969
|
+
`revert_reason` is `None` exactly when the call succeeded. The messages of
|
|
970
|
+
`SimulationResult.raise_for_failure` and `SimulationObservation.value`'s
|
|
971
|
+
`SimulationError` are `failed: {error}`; drop any `call.error or "reverted"`
|
|
972
|
+
fallback and match on `revert_reason`, not on the text of `error`.
|
|
973
|
+
- **`SimulatedCallResult` and `SimulationResult` are frozen**, so the rule above
|
|
974
|
+
holds after construction too: assigning a field raises
|
|
975
|
+
`dataclasses.FrozenInstanceError`. Build a changed copy with
|
|
976
|
+
`dataclasses.replace(result, ...)` (a failed call's `error` is completed again).
|
|
977
|
+
Both stay unhashable (`__hash__` is `None`, as before). The freeze is shallow:
|
|
978
|
+
the `logs` / `calls` lists themselves can still be mutated.
|
|
954
979
|
- Saved config entries are checksummed when loaded; the MCP `vault_add` tool rejects
|
|
955
980
|
a value that is not an address.
|
|
@@ -191,9 +191,7 @@ def _assert_all_success(result: SimulationResult) -> None:
|
|
|
191
191
|
if result.all_success:
|
|
192
192
|
return
|
|
193
193
|
failed = [(c.label, c.error) for c in result.failed_calls]
|
|
194
|
-
raise AssertionError(
|
|
195
|
-
f"simulation calls failed: {failed} (reason={result.revert_reason})"
|
|
196
|
-
)
|
|
194
|
+
raise AssertionError(f"simulation calls failed: {failed}")
|
|
197
195
|
|
|
198
196
|
|
|
199
197
|
def _connected_web3() -> Web3:
|
{ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/examples/advanced_euler_v2_looping_arbitrum.py
RENAMED
|
@@ -153,9 +153,7 @@ def _assert_all_success(result: SimulationResult) -> None:
|
|
|
153
153
|
if result.all_success:
|
|
154
154
|
return
|
|
155
155
|
failed = [(call.label, call.error) for call in result.failed_calls]
|
|
156
|
-
raise AssertionError(
|
|
157
|
-
f"simulation calls failed: {failed} (reason={result.revert_reason})"
|
|
158
|
-
)
|
|
156
|
+
raise AssertionError(f"simulation calls failed: {failed}")
|
|
159
157
|
|
|
160
158
|
|
|
161
159
|
def _connected_web3() -> Web3:
|
|
@@ -634,7 +634,7 @@ def core_leg_previews(web3_hype: Web3) -> list[Preview]:
|
|
|
634
634
|
# ── Part B: the hub legs, simulated at pinned blocks ─────────────────────────
|
|
635
635
|
def _assert_relay_success(results: dict[ChainId, SimulationResult]) -> None:
|
|
636
636
|
failed = [
|
|
637
|
-
f"{chain}:{call.label}:{call.
|
|
637
|
+
f"{chain}:{call.label}:{call.error}"
|
|
638
638
|
for chain, result in results.items()
|
|
639
639
|
for call in result.calls
|
|
640
640
|
if not call.success
|
|
@@ -179,7 +179,7 @@ def _assert_relay_success(results: dict[ChainId, SimulationResult]) -> None:
|
|
|
179
179
|
"""Raise with a readable summary if any simulated call on either chain
|
|
180
180
|
reverted; each entry names the call and decodes its own revert."""
|
|
181
181
|
failed = [
|
|
182
|
-
(int(chain_id), call.label, call.
|
|
182
|
+
(int(chain_id), call.label, call.error)
|
|
183
183
|
for chain_id, result in results.items()
|
|
184
184
|
for call in result.calls
|
|
185
185
|
if not call.success
|
{ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/examples/external_state_margin_leg_base.py
RENAMED
|
@@ -332,9 +332,7 @@ def _assert_all_success(result: SimulationResult) -> None:
|
|
|
332
332
|
if result.all_success:
|
|
333
333
|
return
|
|
334
334
|
failed = [(c.label, c.error) for c in result.failed_calls]
|
|
335
|
-
raise AssertionError(
|
|
336
|
-
f"simulation calls failed: {failed} (reason={result.revert_reason})"
|
|
337
|
-
)
|
|
335
|
+
raise AssertionError(f"simulation calls failed: {failed}")
|
|
338
336
|
|
|
339
337
|
|
|
340
338
|
def _assert_only_expected_failure(result: SimulationResult, label: str) -> bytes:
|
{ipor_fusion-4.0.0.dev164 → ipor_fusion-4.0.0.dev171}/examples/simple_aave_v3_supply_base.py
RENAMED
|
@@ -159,9 +159,7 @@ def _assert_all_success(result: SimulationResult) -> None:
|
|
|
159
159
|
if result.all_success:
|
|
160
160
|
return
|
|
161
161
|
failed = [(c.label, c.error) for c in result.failed_calls]
|
|
162
|
-
raise AssertionError(
|
|
163
|
-
f"simulation calls failed: {failed} (reason={result.revert_reason})"
|
|
164
|
-
)
|
|
162
|
+
raise AssertionError(f"simulation calls failed: {failed}")
|
|
165
163
|
|
|
166
164
|
|
|
167
165
|
def _connected_web3() -> Web3:
|
|
@@ -315,7 +315,7 @@ from ipor_fusion.fuses import (
|
|
|
315
315
|
precompile_index_of,
|
|
316
316
|
system_address,
|
|
317
317
|
)
|
|
318
|
-
from ipor_fusion.fuses.
|
|
318
|
+
from ipor_fusion.fuses.external_state_event_decoding import (
|
|
319
319
|
EXTERNAL_STATE_EVENT_CLASSES,
|
|
320
320
|
EXTERNAL_STATE_EVENT_SPECS,
|
|
321
321
|
EXTERNAL_STATE_EVENTS,
|
|
@@ -343,7 +343,7 @@ from ipor_fusion.fuses.external_state_events import (
|
|
|
343
343
|
external_state_event_name,
|
|
344
344
|
external_state_events,
|
|
345
345
|
)
|
|
346
|
-
from ipor_fusion.fuses.
|
|
346
|
+
from ipor_fusion.fuses.hypercore_event_decoding import (
|
|
347
347
|
HYPERCORE_EVENT_CLASSES,
|
|
348
348
|
HyperCoreActionEnqueuedArgs,
|
|
349
349
|
HyperCoreActionEnqueuedEvent,
|
|
@@ -142,8 +142,17 @@ class _Block:
|
|
|
142
142
|
override_provider: StateOverrideProvider | None = None
|
|
143
143
|
|
|
144
144
|
|
|
145
|
-
@dataclass(slots=True)
|
|
145
|
+
@dataclass(frozen=True, slots=True)
|
|
146
146
|
class SimulatedCallResult:
|
|
147
|
+
"""One simulated call. ``error`` is set exactly when ``success`` is False:
|
|
148
|
+
check ``success``, then read ``error`` for a non-empty, human-readable
|
|
149
|
+
failure (the node's message with the decoded revert reason, or
|
|
150
|
+
``"reverted without a reason"`` plus any undecodable return data).
|
|
151
|
+
Construction enforces it: a failed call's ``error`` is completed from its
|
|
152
|
+
return data, and a successful call given an ``error`` raises
|
|
153
|
+
``ValueError``. The record is frozen, so the invariant holds afterwards;
|
|
154
|
+
build a changed copy with `dataclasses.replace`."""
|
|
155
|
+
|
|
147
156
|
label: str | None
|
|
148
157
|
success: bool
|
|
149
158
|
return_data: HexBytes
|
|
@@ -151,19 +160,33 @@ class SimulatedCallResult:
|
|
|
151
160
|
error: str | None
|
|
152
161
|
logs: list[dict]
|
|
153
162
|
predicted_address: ChecksumAddress | None = None
|
|
163
|
+
# Frozen would add a __hash__ that fails on the logs list; stay unhashable.
|
|
164
|
+
__hash__ = None # type: ignore[assignment]
|
|
165
|
+
|
|
166
|
+
def __post_init__(self) -> None:
|
|
167
|
+
if self.success:
|
|
168
|
+
if self.error is not None:
|
|
169
|
+
raise ValueError(
|
|
170
|
+
f"successful simulated call {self.label!r} carries an error"
|
|
171
|
+
)
|
|
172
|
+
else:
|
|
173
|
+
# The record is frozen; completing the error is part of building it.
|
|
174
|
+
object.__setattr__(
|
|
175
|
+
self, "error", _failure_message(self.error, self.return_data)
|
|
176
|
+
)
|
|
154
177
|
|
|
155
178
|
@property
|
|
156
179
|
def revert_reason(self) -> str | None:
|
|
157
180
|
"""This call's decoded revert (a registered custom error by name, an
|
|
158
|
-
``Error(string)`` text or ``Panic``), ``None``
|
|
159
|
-
|
|
181
|
+
``Error(string)`` text or ``Panic``), else its ``error``; ``None``
|
|
182
|
+
exactly when it succeeded. ``SimulationResult.revert_reason`` is the
|
|
160
183
|
chain's first failed call, which may be a different one."""
|
|
161
184
|
if self.success:
|
|
162
185
|
return None
|
|
163
186
|
return _decode_revert(self.return_data, self.error)
|
|
164
187
|
|
|
165
188
|
|
|
166
|
-
@dataclass(slots=True)
|
|
189
|
+
@dataclass(frozen=True, slots=True)
|
|
167
190
|
class SimulationResult:
|
|
168
191
|
success: bool
|
|
169
192
|
all_success: bool
|
|
@@ -172,6 +195,7 @@ class SimulationResult:
|
|
|
172
195
|
execute_logs: list[dict]
|
|
173
196
|
calls: list[SimulatedCallResult] = field(default_factory=list)
|
|
174
197
|
failed_calls: list[SimulatedCallResult] = field(default_factory=list)
|
|
198
|
+
__hash__ = None # type: ignore[assignment]
|
|
175
199
|
|
|
176
200
|
def raise_for_failure(self) -> None:
|
|
177
201
|
"""Raise :class:`SimulationError` naming the first failed call (label,
|
|
@@ -179,15 +203,11 @@ class SimulationResult:
|
|
|
179
203
|
if not self.failed_calls:
|
|
180
204
|
return
|
|
181
205
|
first = self.failed_calls[0]
|
|
182
|
-
reason = _decode_revert(first.return_data, first.error)
|
|
183
206
|
label = first.label or "<unlabelled>"
|
|
184
|
-
detail = first.error or "reverted"
|
|
185
|
-
if reason and reason != first.error:
|
|
186
|
-
detail = f"{detail}: {reason}"
|
|
187
207
|
raise SimulationError(
|
|
188
|
-
f"simulated call {label!r} failed: {
|
|
208
|
+
f"simulated call {label!r} failed: {first.error}",
|
|
189
209
|
label=first.label,
|
|
190
|
-
revert_reason=
|
|
210
|
+
revert_reason=first.revert_reason,
|
|
191
211
|
)
|
|
192
212
|
|
|
193
213
|
|
|
@@ -223,12 +243,10 @@ class SimulationObservation(Generic[T_co]):
|
|
|
223
243
|
result = self.result
|
|
224
244
|
name = self._label or "<unlabelled>"
|
|
225
245
|
if not result.success:
|
|
226
|
-
reason = result.revert_reason
|
|
227
246
|
raise SimulationError(
|
|
228
|
-
f"observation {name!r} failed: {result.error
|
|
229
|
-
+ (f": {reason}" if reason and reason != result.error else ""),
|
|
247
|
+
f"observation {name!r} failed: {result.error}",
|
|
230
248
|
label=self._label,
|
|
231
|
-
revert_reason=
|
|
249
|
+
revert_reason=result.revert_reason,
|
|
232
250
|
)
|
|
233
251
|
if not result.return_data:
|
|
234
252
|
raise EmptyCallResultError(
|
|
@@ -732,7 +750,7 @@ class VaultSimulator:
|
|
|
732
750
|
status = int(raw.get("status", "0x1"), 16)
|
|
733
751
|
success = status == 1
|
|
734
752
|
gas_used = int(raw.get("gasUsed", "0x0"), 16)
|
|
735
|
-
|
|
753
|
+
message, return_data = _normalize_call_error(raw.get("error"), return_data)
|
|
736
754
|
logs = raw.get("logs", []) or []
|
|
737
755
|
|
|
738
756
|
call_result = SimulatedCallResult(
|
|
@@ -740,7 +758,7 @@ class VaultSimulator:
|
|
|
740
758
|
success=success,
|
|
741
759
|
return_data=return_data,
|
|
742
760
|
gas_used=gas_used,
|
|
743
|
-
error=
|
|
761
|
+
error=None if success else message,
|
|
744
762
|
logs=logs,
|
|
745
763
|
predicted_address=(source.predicted_address if success else None),
|
|
746
764
|
)
|
|
@@ -753,14 +771,13 @@ class VaultSimulator:
|
|
|
753
771
|
execute_gas += gas_used
|
|
754
772
|
execute_logs.extend(logs)
|
|
755
773
|
if not success and revert_reason is None:
|
|
756
|
-
revert_reason =
|
|
774
|
+
revert_reason = call_result.revert_reason
|
|
757
775
|
|
|
758
776
|
failed_calls = [c for c in parsed if not c.success]
|
|
759
777
|
all_success = not failed_calls
|
|
760
778
|
if revert_reason is None and failed_calls:
|
|
761
779
|
# No execute call reverted but a setup/observation did — surface it.
|
|
762
|
-
|
|
763
|
-
revert_reason = _decode_revert(first_failed.return_data, first_failed.error)
|
|
780
|
+
revert_reason = failed_calls[0].revert_reason
|
|
764
781
|
|
|
765
782
|
return SimulationResult(
|
|
766
783
|
success=execute_success,
|
|
@@ -910,6 +927,22 @@ def _normalize_call_error(
|
|
|
910
927
|
return (error_raw if isinstance(error_raw, str) else None), return_data
|
|
911
928
|
|
|
912
929
|
|
|
930
|
+
def _failure_message(message: str | None, return_data: HexBytes) -> str:
|
|
931
|
+
"""A failed call's ``error``: the node's message joined with the decoded
|
|
932
|
+
revert unless the message already carries it, else whichever exists, else
|
|
933
|
+
``"reverted without a reason"`` with any return data too short to decode."""
|
|
934
|
+
reason = _decode_revert(return_data, None)
|
|
935
|
+
if message and reason:
|
|
936
|
+
return message if reason in message else f"{message}: {reason}"
|
|
937
|
+
if message:
|
|
938
|
+
return message
|
|
939
|
+
if reason:
|
|
940
|
+
return reason
|
|
941
|
+
if return_data:
|
|
942
|
+
return f"reverted without a reason (return data 0x{bytes(return_data).hex()})"
|
|
943
|
+
return "reverted without a reason"
|
|
944
|
+
|
|
945
|
+
|
|
913
946
|
def _decode_revert(return_data: HexBytes, error: str | None) -> str | None:
|
|
914
947
|
if return_data and len(return_data) >= 4:
|
|
915
948
|
selector = bytes(return_data[:4])
|
|
@@ -49,7 +49,7 @@ from ipor_fusion.fuses.hypercore import (
|
|
|
49
49
|
precompile_index_of,
|
|
50
50
|
system_address,
|
|
51
51
|
)
|
|
52
|
-
from ipor_fusion.fuses.
|
|
52
|
+
from ipor_fusion.fuses.hypercore_event_decoding import (
|
|
53
53
|
HYPERCORE_EVENT_CLASSES,
|
|
54
54
|
HYPERCORE_EVENT_SPECS,
|
|
55
55
|
HYPERCORE_EVENTS,
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""Golden outputs: the exact text a CLI command or an MCP tool emits, pinned in
|
|
2
|
+
``tests/fixtures/*_golden/``.
|
|
3
|
+
|
|
4
|
+
Regenerate them only when an output change is intended, then review the diff:
|
|
5
|
+
|
|
6
|
+
UPDATE_GOLDEN=1 uv run pytest -k golden --no-cov
|
|
7
|
+
|
|
8
|
+
Without ``UPDATE_GOLDEN`` (the default) a golden is only compared, never written.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import asyncio
|
|
14
|
+
import json
|
|
15
|
+
import os
|
|
16
|
+
from datetime import UTC, datetime, tzinfo
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
|
|
19
|
+
from mcp.types import TextContent
|
|
20
|
+
from pydantic import BaseModel
|
|
21
|
+
|
|
22
|
+
from ipor_fusion.mcp.server import mcp
|
|
23
|
+
|
|
24
|
+
FIXTURES = Path(__file__).parent / "fixtures"
|
|
25
|
+
|
|
26
|
+
# The instant the goldens are rendered at: ages ("N days ago", `age_days`)
|
|
27
|
+
# are relative to it.
|
|
28
|
+
FROZEN_NOW = datetime(2026, 10, 1, tzinfo=UTC)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class FrozenDatetime(datetime):
|
|
32
|
+
"""`datetime` whose `now()` is `FROZEN_NOW`; patched over a module's
|
|
33
|
+
`datetime` import so its rendering does not depend on the wall clock."""
|
|
34
|
+
|
|
35
|
+
@classmethod
|
|
36
|
+
def now(cls, tz: tzinfo | None = None) -> FrozenDatetime:
|
|
37
|
+
return cls.fromtimestamp(FROZEN_NOW.timestamp(), tz)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def assert_golden(path: Path, text: str) -> None:
|
|
41
|
+
"""Compare ``text`` with the golden at ``path`` byte for byte; write it
|
|
42
|
+
instead when ``UPDATE_GOLDEN`` is set."""
|
|
43
|
+
if os.environ.get("UPDATE_GOLDEN"):
|
|
44
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
45
|
+
path.write_text(text, encoding="utf-8")
|
|
46
|
+
assert path.exists(), f"missing golden {path}; generate it with UPDATE_GOLDEN=1"
|
|
47
|
+
assert text == path.read_text(encoding="utf-8"), (
|
|
48
|
+
f"output differs from {path}; if the change is intended, regenerate "
|
|
49
|
+
"with UPDATE_GOLDEN=1 and review the diff"
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def mcp_tool_text(
|
|
54
|
+
tool: str, arguments: dict[str, object], model: type[BaseModel]
|
|
55
|
+
) -> str:
|
|
56
|
+
"""Call ``tool`` through the MCP server, as a client does, and return the
|
|
57
|
+
text it emits. Checks that the text is ``model_dump_json(indent=2)`` of the
|
|
58
|
+
tool's response model and carries the same document as its structured
|
|
59
|
+
content."""
|
|
60
|
+
result = asyncio.run(mcp.call_tool(tool, arguments))
|
|
61
|
+
# A tool with a typed return answers (content blocks, structured content).
|
|
62
|
+
assert isinstance(result, tuple) and len(result) == 2
|
|
63
|
+
content, structured = result
|
|
64
|
+
assert isinstance(content, list) and len(content) == 1
|
|
65
|
+
block = content[0]
|
|
66
|
+
assert isinstance(block, TextContent)
|
|
67
|
+
assert json.loads(block.text) == structured
|
|
68
|
+
assert block.text == model.model_validate(structured).model_dump_json(indent=2)
|
|
69
|
+
return block.text
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
"""In-memory stand-ins for the oracle mapping's chain reads: `FakeReader`
|
|
2
|
+
answers what `OracleMappingReader` would, `FakeVault` the three reads
|
|
3
|
+
`build_oracle_mapping` makes of the vault. No network, no RPC."""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from typing import Generic, TypedDict, TypeVar
|
|
8
|
+
|
|
9
|
+
from web3 import Web3
|
|
10
|
+
|
|
11
|
+
from ipor_fusion.types import Price
|
|
12
|
+
|
|
13
|
+
T = TypeVar("T")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def addr(n: int) -> str:
|
|
17
|
+
return Web3.to_checksum_address(f"0x{n:040x}")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class Feed(TypedDict, total=False):
|
|
21
|
+
"""What a price feed answers; an absent key is a getter that reverts."""
|
|
22
|
+
|
|
23
|
+
morpho_oracle: str
|
|
24
|
+
collateral: str
|
|
25
|
+
loan: str
|
|
26
|
+
vault: str
|
|
27
|
+
round: tuple[int, int, int, int, int]
|
|
28
|
+
feed_decimals: int
|
|
29
|
+
description: str | None
|
|
30
|
+
version: int
|
|
31
|
+
aggregator: str
|
|
32
|
+
phase_id: int
|
|
33
|
+
asset_x: str
|
|
34
|
+
xy_feed: str
|
|
35
|
+
y_usd_feed: str
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class Erc4626(TypedDict, total=False):
|
|
39
|
+
asset: str
|
|
40
|
+
decimals: int
|
|
41
|
+
rate: int
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class FakeReader:
|
|
45
|
+
"""In-memory stand-in for :class:`oracle_mapping.OracleMappingReader`."""
|
|
46
|
+
|
|
47
|
+
def __init__(self) -> None:
|
|
48
|
+
self.symbols: dict[str, str] = {}
|
|
49
|
+
self.decimals: dict[str, int] = {}
|
|
50
|
+
self.prices: dict[str, Price] = {}
|
|
51
|
+
self.sources: dict[str, str] = {}
|
|
52
|
+
self.feeds: dict[str, Feed] = {}
|
|
53
|
+
self.vaults: dict[str, Erc4626] = {}
|
|
54
|
+
self.morpho_prices: dict[str, int] = {}
|
|
55
|
+
# oracle variant (middleware-fallback resolution); "_mw" because the
|
|
56
|
+
# exact method name would be shadowed by the attribute
|
|
57
|
+
self.underlying_mw: str | None = None
|
|
58
|
+
self.registry: str | None = None
|
|
59
|
+
self.delegated: FakeReader | None = None
|
|
60
|
+
# enumeration (configured_assets / asset_source_events)
|
|
61
|
+
self.configured: list[str] | None = None
|
|
62
|
+
self.events: list[tuple[int, str, str]] = []
|
|
63
|
+
self.events_to_block: int | None = None
|
|
64
|
+
|
|
65
|
+
# token metadata
|
|
66
|
+
def symbol(self, a: str) -> str | None:
|
|
67
|
+
return self.symbols.get(a)
|
|
68
|
+
|
|
69
|
+
def token_decimals(self, a: str) -> int | None:
|
|
70
|
+
return self.decimals.get(a)
|
|
71
|
+
|
|
72
|
+
# oracle
|
|
73
|
+
def asset_price(self, a: str) -> Price | None:
|
|
74
|
+
return self.prices.get(a)
|
|
75
|
+
|
|
76
|
+
def source_of(self, a: str) -> str | None:
|
|
77
|
+
return self.sources.get(a)
|
|
78
|
+
|
|
79
|
+
# oracle variant probes
|
|
80
|
+
def underlying_middleware(self) -> str | None:
|
|
81
|
+
return self.underlying_mw
|
|
82
|
+
|
|
83
|
+
def chainlink_feed_registry(self) -> str | None:
|
|
84
|
+
return self.registry
|
|
85
|
+
|
|
86
|
+
def delegate(self, oracle: str) -> FakeReader:
|
|
87
|
+
assert oracle == self.underlying_mw # must delegate to the probed middleware
|
|
88
|
+
assert self.delegated is not None # fail fast on a misconfigured test
|
|
89
|
+
return self.delegated
|
|
90
|
+
|
|
91
|
+
# feed probes
|
|
92
|
+
def _f(self, s: str) -> Feed:
|
|
93
|
+
return self.feeds.get(s, {})
|
|
94
|
+
|
|
95
|
+
def feed_morpho_oracle(self, s: str) -> str | None:
|
|
96
|
+
return self._f(s).get("morpho_oracle")
|
|
97
|
+
|
|
98
|
+
def feed_collateral_token(self, s: str) -> str | None:
|
|
99
|
+
return self._f(s).get("collateral")
|
|
100
|
+
|
|
101
|
+
def feed_loan_token(self, s: str) -> str | None:
|
|
102
|
+
return self._f(s).get("loan")
|
|
103
|
+
|
|
104
|
+
def feed_vault(self, s: str) -> str | None:
|
|
105
|
+
return self._f(s).get("vault")
|
|
106
|
+
|
|
107
|
+
def feed_latest_round_data(self, s: str) -> tuple[int, int, int, int, int] | None:
|
|
108
|
+
return self._f(s).get("round")
|
|
109
|
+
|
|
110
|
+
def feed_decimals(self, s: str) -> int | None:
|
|
111
|
+
return self._f(s).get("feed_decimals")
|
|
112
|
+
|
|
113
|
+
def feed_description(self, s: str) -> str | None:
|
|
114
|
+
return self._f(s).get("description")
|
|
115
|
+
|
|
116
|
+
def feed_version(self, s: str) -> int | None:
|
|
117
|
+
return self._f(s).get("version")
|
|
118
|
+
|
|
119
|
+
def feed_aggregator(self, s: str) -> str | None:
|
|
120
|
+
return self._f(s).get("aggregator")
|
|
121
|
+
|
|
122
|
+
def feed_phase_id(self, s: str) -> int | None:
|
|
123
|
+
return self._f(s).get("phase_id")
|
|
124
|
+
|
|
125
|
+
def morpho_oracle_price(self, o: str) -> int | None:
|
|
126
|
+
return self.morpho_prices.get(o)
|
|
127
|
+
|
|
128
|
+
def feed_asset_x(self, s: str) -> str | None:
|
|
129
|
+
return self._f(s).get("asset_x")
|
|
130
|
+
|
|
131
|
+
def feed_asset_x_asset_y_feed(self, s: str) -> str | None:
|
|
132
|
+
return self._f(s).get("xy_feed")
|
|
133
|
+
|
|
134
|
+
def feed_asset_y_usd_feed(self, s: str) -> str | None:
|
|
135
|
+
return self._f(s).get("y_usd_feed")
|
|
136
|
+
|
|
137
|
+
# erc4626 vault
|
|
138
|
+
def vault_asset(self, v: str) -> str | None:
|
|
139
|
+
return self.vaults.get(v, {}).get("asset")
|
|
140
|
+
|
|
141
|
+
def vault_decimals(self, v: str) -> int | None:
|
|
142
|
+
return self.vaults.get(v, {}).get("decimals")
|
|
143
|
+
|
|
144
|
+
def vault_convert_to_assets(self, v: str, shares: int) -> int | None:
|
|
145
|
+
return self.vaults.get(v, {}).get("rate")
|
|
146
|
+
|
|
147
|
+
# enumeration
|
|
148
|
+
def configured_assets(self) -> list[str] | None:
|
|
149
|
+
return self.configured
|
|
150
|
+
|
|
151
|
+
def asset_source_events(self, to_block: int) -> list[tuple[int, str, str]]:
|
|
152
|
+
self.events_to_block = to_block
|
|
153
|
+
return self.events
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def chainlink_asset(r: FakeReader, asset: str, source: str, *, symbol: str) -> None:
|
|
157
|
+
r.symbols[asset] = symbol
|
|
158
|
+
r.decimals[asset] = 6
|
|
159
|
+
r.sources[asset] = source
|
|
160
|
+
r.prices[asset] = Price(asset=asset, amount=99_980_000, decimals=8)
|
|
161
|
+
# full confirmed-tier evidence: description + version on top of the round
|
|
162
|
+
r.feeds[source] = {
|
|
163
|
+
"round": (1, 99_980_000, 0, 1_700_000_000, 1),
|
|
164
|
+
"feed_decimals": 8,
|
|
165
|
+
"description": f"{symbol} / USD",
|
|
166
|
+
"version": 4,
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
class Returns(Generic[T]):
|
|
171
|
+
"""Wraps a value as a Call-like object exposing .call(ctx)."""
|
|
172
|
+
|
|
173
|
+
def __init__(self, value: T):
|
|
174
|
+
self._value = value
|
|
175
|
+
|
|
176
|
+
def call(self, ctx: object) -> T:
|
|
177
|
+
return self._value
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
class FakeVault:
|
|
181
|
+
"""Stand-in for PlasmaVault — only the three reads build_oracle_mapping makes."""
|
|
182
|
+
|
|
183
|
+
def __init__(
|
|
184
|
+
self, oracle: str, asset: str, name: str, *, name_raises: bool = False
|
|
185
|
+
):
|
|
186
|
+
self._oracle = oracle
|
|
187
|
+
self._asset = asset
|
|
188
|
+
self._name = name
|
|
189
|
+
self._name_raises = name_raises
|
|
190
|
+
|
|
191
|
+
def read_price_oracle_address(self, ctx: object) -> str:
|
|
192
|
+
return self._oracle
|
|
193
|
+
|
|
194
|
+
def underlying_asset_address(self) -> Returns[str]:
|
|
195
|
+
return Returns(self._asset)
|
|
196
|
+
|
|
197
|
+
def name(self) -> Returns[str]:
|
|
198
|
+
if self._name_raises:
|
|
199
|
+
raise RuntimeError("name() reverted")
|
|
200
|
+
return Returns(self._name)
|