ipor-fusion 4.0.0.dev83__tar.gz → 4.0.0.dev86__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.dev83 → ipor_fusion-4.0.0.dev86}/MIGRATION_4.md +51 -9
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/PKG-INFO +1 -1
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/pyproject.toml +1 -1
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/__init__.py +8 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/core/external_state_executor.py +66 -37
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/core/simulation.py +9 -6
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/crosschain/ccip/chainlink.py +5 -4
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/crosschain/ccip/indexer.py +51 -16
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/crosschain/ccip/quote.py +7 -4
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/errors.py +51 -11
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/mcp/server.py +1 -4
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/readers/hypercore.py +19 -8
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/readers/hypercore_outcome.py +4 -1
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_crosschain_ccip_indexer.py +52 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_crosschain_ccip_quote.py +6 -3
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_crosschain_chainlink.py +2 -1
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_error_hierarchy.py +8 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_external_state_executor.py +32 -28
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_hypercore_outcome.py +12 -1
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_hypercore_readers.py +17 -3
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_mcp_server.py +1 -5
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_public_api.py +4 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_vault_simulator_blocks.py +3 -3
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/.claude-plugin/marketplace.json +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/.claude-plugin/plugin.json +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/.env.example +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/.gemini/settings.json +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/.git-blame-ignore-revs +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/.github/workflows/cd.yml +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/.github/workflows/ci.yml +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/.github/workflows/commit-lint.yml +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/.github/workflows/notify-slack.yml +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/.github/workflows/python-build.yml +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/.github/workflows/python-publish.yml +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/.github/workflows/release.yml +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/.github/workflows/report-slack.yml +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/.gitignore +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/.mcp.json +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/.pre-commit-config.yaml +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/.python-version +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/AGENTS.md +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/CHANGELOG.md +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/CLAUDE.md +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/CODEOWNERS +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/LICENSE +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/README.md +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/build.sh +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/examples/CONTRIBUTING.md +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/examples/README.md +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/examples/advanced_euler_v2_credit_market_base.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/examples/advanced_euler_v2_looping_arbitrum.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/examples/composed_hypercore_flow_arbitrum_hyperevm.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/examples/crosschain_ccip_usdc_arbitrum_hyperevm.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/examples/external_state_margin_leg_base.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/examples/simple_aave_v3_supply_base.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/pytest.ini +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/scripts/commit-check.sh +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/scripts/crosschain_readiness.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/skills/ipor-deploy-vault/SKILL.md +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/about.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/cache.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/chains.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/cli/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/cli/changelog_cmd.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/cli/config_cmd.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/cli/config_store.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/cli/main.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/cli/market_cmd.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/cli/options.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/cli/oracle_mapping_view.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/cli/vault_cmd.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/cli/vault_health.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/cli/vault_info_view.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/cli/vault_markets_view.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/cli/vault_rendering.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/config/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/config/roles.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/core/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/core/access.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/core/address.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/core/context.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/core/contract.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/core/erc20.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/core/fee_manager.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/core/fuse_action.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/core/fusion_factory.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/core/logs.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/core/multicall.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/core/oracle.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/core/plasma_vault.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/core/rewards_manager.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/core/withdraw_manager.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/crosschain/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/crosschain/ccip/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/crosschain/ccip/codec.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/crosschain/ccip/contracts.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/crosschain/ccip/events.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/crosschain/ccip/lane.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/crosschain/ccip/transport.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/crosschain/contracts.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/crosschain/discovery.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/crosschain/errors.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/crosschain/lane.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/crosschain/logs.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/crosschain/messages.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/crosschain/simulation.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/crosschain/stargate/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/crosschain/stargate/contracts.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/crosschain/stargate/lane.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/crosschain/stargate/layerzero.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/crosschain/stargate/transport.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/crosschain/transport.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/dependency_graph.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/entry_cli.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/entry_mcp.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/explorer.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/field_docs.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/fuses/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/fuses/aave_v3.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/fuses/async_action.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/fuses/base.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/fuses/compound_v3.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/fuses/crosschain/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/fuses/crosschain/base.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/fuses/crosschain/ccip.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/fuses/crosschain/stargate.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/fuses/erc4626.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/fuses/euler_v2.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/fuses/events.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/fuses/external_state.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/fuses/external_state_events.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/fuses/fluid_instadapp.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/fuses/gearbox_v3.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/fuses/hypercore.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/fuses/hypercore_events.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/fuses/merkl.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/fuses/morpho.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/fuses/ramses_v2.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/fuses/uniswap_v3.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/fuses/universal.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/guide/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/guide/architecture.md +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/guide/glossary.md +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/guide/invariants.md +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/guide/quickstart.md +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/market_ids.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/market_info.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/mcp/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/mcp/guide.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/mcp/models.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/morpho_api.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/py.typed +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/readers/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/readers/aave_v3.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/readers/aave_v4.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/readers/compound_v3.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/readers/lending_health.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/readers/morpho.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/readers/oracle_mapping.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/readers/oracle_mapping_nodes.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/readers/oracle_mapping_reader.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/readers/position_manager.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/readers/ramses_v2.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/readers/uniswap_v3.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/readers/unpriceable.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/substrates.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/types.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/validation.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/vault_data.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/vault_info.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/vault_managers.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/vault_positions.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/vault_reads.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/UniswapV3UniversalRouter.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/_crosschain.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/_crosschain_lifecycle.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/_crosschain_recovery.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/_euler_v2.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/_foundry.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/_guide.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/_hypercore_model.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/_hypercore_shadow.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/_multicall.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/_simulate.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/_validation.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/addresses.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/conftest.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/constants.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/fixtures/hypercore_hip3_flow.json +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/fixtures/hypercore_market55.json +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/fixtures/vault_info_golden/clean.json +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/fixtures/vault_info_golden/clean.txt +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/fixtures/vault_info_golden/rich.json +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/fixtures/vault_info_golden/rich.txt +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_about.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_access_manager.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_cache.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_chains.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_cli_changelog.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_cli_commands.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_cli_config_store.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_cli_helpers.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_cli_hypercore.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_cli_lending_health.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_cli_market_morpho_blue.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_cli_oracle_mapping.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_cli_pending_requests_scan.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_cli_role_accounts.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_cli_vault_info_golden.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_cli_vault_predates_getters.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_cli_vault_validate.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_contract_call.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_contract_encoder.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_crosschain_ccip_events.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_crosschain_contracts.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_crosschain_encoding.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_crosschain_errors.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_crosschain_lane.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_crosschain_layering.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_crosschain_readiness.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_crosschain_readiness_report.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_crosschain_transport.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_erc20.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_examples_composed_hypercore.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_examples_crosschain.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_examples_euler_looping_arbitrum.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_examples_vaults.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_explorer.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_external_state_events.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_fee_manager.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_fuse_encoding.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_fusion_instance.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_guide.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_hypercore_deployment.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_hypercore_events.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_hypercore_flow.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_hypercore_live.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_hypercore_simulation.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_logs.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_mcp_hypercore.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_mcp_models.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_mcp_vault_validate.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_multicall.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_oracle.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_oracle_mapping.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_plasma_vault.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_reader_encoding.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_readiness_selection.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_revert_decoding.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_rewards_manager.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_roles.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_sdk_layering.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_simulate_borrow_aave_v3_mainnet.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_simulate_claim_merkl_wrapper_base.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_simulate_claim_morpho_rewards.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_simulate_crosschain_canary.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_simulate_crosschain_hypercore.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_simulate_crosschain_hyperevm.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_simulate_crosschain_lifecycle.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_simulate_euler_v2_batch_base.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_simulate_euler_v2_credit_market_base.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_simulate_euler_v2_swap_deploy_base.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_simulate_euler_v2_swap_reconfigure_base.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_simulate_euler_v2_swap_registry_base.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_simulate_examples_arbitrum.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_simulate_examples_base.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_simulate_examples_crosschain.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_simulate_external_state_executor_base.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_simulate_fusion_factory_clone_base.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_simulate_looping_aave_v3_base.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_simulate_looping_morpho_blue_base.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_simulate_morpho_blue.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_simulate_pilot_v3.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_simulate_pilot_v4_uniswap.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_simulate_pilot_v5_ramses.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_simulate_pilot_v5_scheduled.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_simulate_plasma_vault_setters_base.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_simulate_readers_integration.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_simulate_release_funds.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_simulate_swap_on_base.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_simulate_vault_from_scratch_base.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_solidity_compiler.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_solidity_mirrors.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_substrates.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_types_and_events.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_unpriceable_tokens.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_validation.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_vault_dep_graph.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_vault_info_onchain_base.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_vault_info_onchain_base_no_withdraw_manager.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_web3_context.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/test_withdraw_manager.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/tests/typed_client/consumer.py +0 -0
- {ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/uv.lock +0 -0
|
@@ -238,15 +238,20 @@ except below): `chain_id` fields are `ChainId`, `role_id` `RoleId`, `market_id`
|
|
|
238
238
|
## Errors
|
|
239
239
|
|
|
240
240
|
Every exception class the SDK defines now derives from `IporFusionError` and is
|
|
241
|
-
importable from `ipor_fusion`; the builtin bases they had are kept.
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
241
|
+
importable from `ipor_fusion`; the builtin bases they had are kept. One rule holds
|
|
242
|
+
across the whole SDK: a caller's mistake (a bad argument, or an operation the object
|
|
243
|
+
was not built for) raises a plain `ValueError` (`TypeError` for an argument of the
|
|
244
|
+
wrong type, `KeyError` for an unknown name in a mapping-style lookup such as
|
|
245
|
+
`ipor_fusion.guide.guide_text`); a chain, provider or remote API answering something the
|
|
246
|
+
SDK cannot use raises an `IporFusionError` subclass. Except for
|
|
247
|
+
`CrosschainDiscoveryError` (discovery) and `SimulationError` (`quote_ccip_native_fee`),
|
|
248
|
+
every subclass that replaces a `ValueError` raise also derives from `ValueError`, so
|
|
249
|
+
`except ValueError` keeps catching it; catch the subclass to tell a chain fact from a
|
|
250
|
+
bad argument. Receipts,
|
|
251
|
+
logs and transports passed in that do not contain what the call needs (a clone
|
|
252
|
+
receipt without its events, logs without the lane's `BalanceProposed`, a CCIP
|
|
253
|
+
message to a chain the transport does not configure) stay `ValueError`: the input
|
|
254
|
+
is the wrong one.
|
|
250
255
|
|
|
251
256
|
- `MorphoApiError` (still a `RuntimeError`), `ExternalStateEventDecodeError` (still a
|
|
252
257
|
`ValueError`) and `VerifierResultUnavailable` (still a `LookupError`) now derive
|
|
@@ -274,3 +279,40 @@ indexer answer, and `erc20_balance_slot` raises `ValueError` when no slot answer
|
|
|
274
279
|
- The Morpho API client raises `MorphoApiError` for every response it cannot read and
|
|
275
280
|
for any `OSError` or `http.client.HTTPException` while fetching (it caught only
|
|
276
281
|
`URLError` and `TimeoutError` before).
|
|
282
|
+
- The MCP `market_meta_morpho` tool lets `MorphoApiError` propagate instead of
|
|
283
|
+
re-raising it as `ValueError`. MCP clients see the same tool error message; only a
|
|
284
|
+
direct Python call of the tool function sees the new type.
|
|
285
|
+
- `read_hypercore_nav` (and `read_hypercore_vault_state`, which calls it) raises the new
|
|
286
|
+
`HyperCoreValuationError` (still a `ValueError`) for a failed `spotBalance`,
|
|
287
|
+
`tokenInfo` or `accountMarginSummary` precompile read, a granted spot token the
|
|
288
|
+
oracle does not price, a price overflow and an unsupported HIP-3 dex bitmap;
|
|
289
|
+
`HyperCoreNav.balance_wad` raises it for a negative NAV.
|
|
290
|
+
- `read_hypercore_transfer_snapshot` raises `UnsupportedChainError` (still a
|
|
291
|
+
`ValueError`) on a chain with no known `HyperCorePendingReader` when none is passed.
|
|
292
|
+
- `fetch_verifier_result` no longer leaks `KeyError`, `TypeError` or `ValueError` from
|
|
293
|
+
an indexer answer it cannot read (not JSON, a missing or mistyped field, invalid hex
|
|
294
|
+
or address, an empty or non-array CCV list, or a result for a different message id,
|
|
295
|
+
both now checked): that
|
|
296
|
+
indexer is skipped like an unreachable one, and when none serves a readable result
|
|
297
|
+
it raises `VerifierResultUnavailable` naming each indexer's reason. A `message_id`
|
|
298
|
+
that is not 32 bytes is still a `ValueError`.
|
|
299
|
+
- `erc20_balance_slot` raises the new `BalanceSlotNotFoundError` (still a
|
|
300
|
+
`ValueError`) when no slot up to `max_slot` answers as the balances mapping; a
|
|
301
|
+
negative `max_slot` is a `ValueError` with its own message.
|
|
302
|
+
- The external-state market raises the new `ExternalStateError` (still a
|
|
303
|
+
`ValueError`): `read_external_state` for a storage word longer than 32 bytes, an
|
|
304
|
+
executor word with bits above the address or a pause word other than 0 or 1;
|
|
305
|
+
`ExternalStateExecutor.for_vault` when no executor is deployed and, instead of
|
|
306
|
+
eth_abi's `NonEmptyPaddingBytes`, for a slot that is not an address;
|
|
307
|
+
`parse_balance_proposed` / `find_balance_proposed` for one of the executor's own
|
|
308
|
+
`BalanceProposed` logs whose data is missing or malformed (including data that is
|
|
309
|
+
not hex at all, which raised `TypeError`) or whose hash is not the hash of its own
|
|
310
|
+
fields; and `mark_nav` when the propose receipt it fetched has no such log or
|
|
311
|
+
records another value or proposer. A log that is not the event or not from the
|
|
312
|
+
executor, and `find_balance_proposed` over logs that hold no match, are still
|
|
313
|
+
`ValueError`.
|
|
314
|
+
- `ccip_token_lane` raises the new `UnsupportedContractVersionError` (still a
|
|
315
|
+
`ValueError`) for an OnRamp version whose layout is not verified.
|
|
316
|
+
- `quote_ccip_native_fee` raises `SimulationError` (label `"quote"`, with
|
|
317
|
+
`revert_reason`) instead of `ValueError` when the call reverts before the CCIP fee
|
|
318
|
+
check. A call that succeeds without sending a message is still a `ValueError`.
|
|
@@ -173,9 +173,12 @@ from ipor_fusion.dependency_graph import (
|
|
|
173
173
|
)
|
|
174
174
|
from ipor_fusion.errors import (
|
|
175
175
|
CUSTOM_ERRORS,
|
|
176
|
+
BalanceSlotNotFoundError,
|
|
176
177
|
ContractNotFoundError,
|
|
177
178
|
CrosschainDiscoveryError,
|
|
178
179
|
EmptyCallResultError,
|
|
180
|
+
ExternalStateError,
|
|
181
|
+
HyperCoreValuationError,
|
|
179
182
|
IporFusionError,
|
|
180
183
|
LogScanError,
|
|
181
184
|
LogSourceUnavailable,
|
|
@@ -185,6 +188,7 @@ from ipor_fusion.errors import (
|
|
|
185
188
|
SimulationRunError,
|
|
186
189
|
TransactionError,
|
|
187
190
|
UnsupportedChainError,
|
|
191
|
+
UnsupportedContractVersionError,
|
|
188
192
|
UnsupportedVaultVersionError,
|
|
189
193
|
decode_custom_error,
|
|
190
194
|
get_revert_reason,
|
|
@@ -717,9 +721,12 @@ __all__ = [
|
|
|
717
721
|
"format_market_label",
|
|
718
722
|
"market_name",
|
|
719
723
|
"DOCS",
|
|
724
|
+
"BalanceSlotNotFoundError",
|
|
720
725
|
"ContractNotFoundError",
|
|
721
726
|
"CrosschainDiscoveryError",
|
|
722
727
|
"EmptyCallResultError",
|
|
728
|
+
"ExternalStateError",
|
|
729
|
+
"HyperCoreValuationError",
|
|
723
730
|
"IporFusionError",
|
|
724
731
|
"LogScanError",
|
|
725
732
|
"LogSourceUnavailable",
|
|
@@ -729,6 +736,7 @@ __all__ = [
|
|
|
729
736
|
"SimulationError",
|
|
730
737
|
"SimulationRunError",
|
|
731
738
|
"UnsupportedChainError",
|
|
739
|
+
"UnsupportedContractVersionError",
|
|
732
740
|
"UnsupportedVaultVersionError",
|
|
733
741
|
"CUSTOM_ERRORS",
|
|
734
742
|
"decode_custom_error",
|
{ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/core/external_state_executor.py
RENAMED
|
@@ -31,6 +31,7 @@ from web3.types import BlockIdentifier, TxReceipt
|
|
|
31
31
|
|
|
32
32
|
from ipor_fusion.core.context import Web3Context
|
|
33
33
|
from ipor_fusion.core.contract import Call, ContractWrapper
|
|
34
|
+
from ipor_fusion.errors import ExternalStateError
|
|
34
35
|
from ipor_fusion.market_ids import IporFusionMarkets
|
|
35
36
|
from ipor_fusion.types import (
|
|
36
37
|
ZERO_ADDRESS,
|
|
@@ -107,6 +108,17 @@ def _hash_of(
|
|
|
107
108
|
)
|
|
108
109
|
|
|
109
110
|
|
|
111
|
+
def _no_balance_proposed(
|
|
112
|
+
executor: ChecksumAddress, balance_account: ChecksumAddress | None
|
|
113
|
+
) -> str:
|
|
114
|
+
for_account = (
|
|
115
|
+
f" for account {Web3.to_checksum_address(balance_account)}"
|
|
116
|
+
if balance_account is not None
|
|
117
|
+
else ""
|
|
118
|
+
)
|
|
119
|
+
return f"no BalanceProposed log from executor {executor}{for_account}"
|
|
120
|
+
|
|
121
|
+
|
|
110
122
|
def _require_matching_hash(
|
|
111
123
|
proposal: BalanceProposal, *, executor: ChecksumAddress, chain_id: ChainId
|
|
112
124
|
) -> None:
|
|
@@ -122,7 +134,7 @@ def _require_matching_hash(
|
|
|
122
134
|
if proposal.proposal_hash != _hash_of(
|
|
123
135
|
proposal, executor=executor, chain_id=chain_id
|
|
124
136
|
):
|
|
125
|
-
raise
|
|
137
|
+
raise ExternalStateError(
|
|
126
138
|
"proposal hash does not match its own fields: it pairs this value "
|
|
127
139
|
"with another proposal's hash, or it was computed for a different "
|
|
128
140
|
"executor or chain id"
|
|
@@ -191,7 +203,9 @@ def _storage_word(raw: bytes) -> int:
|
|
|
191
203
|
never a value an EVM slot can hold."""
|
|
192
204
|
data = bytes(raw)
|
|
193
205
|
if len(data) > 32:
|
|
194
|
-
raise
|
|
206
|
+
raise ExternalStateError(
|
|
207
|
+
f"storage word of {len(data)} bytes, more than a 32-byte slot"
|
|
208
|
+
)
|
|
195
209
|
return int.from_bytes(data.rjust(32, b"\x00"), "big")
|
|
196
210
|
|
|
197
211
|
|
|
@@ -230,9 +244,10 @@ def read_external_state(
|
|
|
230
244
|
hash by identity; ``pending`` is rejected), so the four values never mix
|
|
231
245
|
blocks; the block number is returned so executor reads can be pinned to
|
|
232
246
|
it. Decoding is canonical: a word longer than 32 bytes, an executor word
|
|
233
|
-
with bits above the address, or a pause word other than 0 or 1, raises
|
|
234
|
-
a plausible value; a zero
|
|
235
|
-
|
|
247
|
+
with bits above the address, or a pause word other than 0 or 1, raises
|
|
248
|
+
``ExternalStateError`` rather than passing as a plausible value; a zero
|
|
249
|
+
executor word is ``None`` (no executor deployed), which is a state, while
|
|
250
|
+
an RPC or decoding failure raises.
|
|
236
251
|
"""
|
|
237
252
|
vault = Web3.to_checksum_address(vault_address)
|
|
238
253
|
read_at, block = _snapshot_block(ctx)
|
|
@@ -241,11 +256,11 @@ def read_external_state(
|
|
|
241
256
|
_storage_word(ctx.get_storage_at(vault, base + i, read_at)) for i in range(4)
|
|
242
257
|
]
|
|
243
258
|
if words[0] >> 160:
|
|
244
|
-
raise
|
|
259
|
+
raise ExternalStateError(
|
|
245
260
|
f"malformed executor word in the external-state storage of {vault}"
|
|
246
261
|
)
|
|
247
262
|
if words[3] not in (0, 1):
|
|
248
|
-
raise
|
|
263
|
+
raise ExternalStateError(
|
|
249
264
|
f"malformed pause flag in the external-state storage of {vault}"
|
|
250
265
|
)
|
|
251
266
|
return ExternalStateVaultState(
|
|
@@ -309,24 +324,26 @@ class ExternalStateExecutor(ContractWrapper):
|
|
|
309
324
|
) -> ExternalStateExecutor:
|
|
310
325
|
"""Resolve the per-vault executor from vault storage and wrap it.
|
|
311
326
|
|
|
312
|
-
Raises `
|
|
313
|
-
zero) rather than returning a wrapper around the zero address, and
|
|
314
|
-
|
|
315
|
-
corrupt slot
|
|
327
|
+
Raises `ExternalStateError` when no executor is deployed yet (the slot
|
|
328
|
+
reads zero) rather than returning a wrapper around the zero address, and
|
|
329
|
+
when the slot is not an address (its high 12 bytes are non-zero: a wrong
|
|
330
|
+
or corrupt slot) or is longer than 32 bytes.
|
|
316
331
|
"""
|
|
317
|
-
|
|
318
|
-
# address is the slot's low 20 bytes, `decode` verifies the rest is zero.
|
|
319
|
-
raw = bytes(
|
|
332
|
+
word = _storage_word(
|
|
320
333
|
ctx.get_storage_at(
|
|
321
334
|
Web3.to_checksum_address(vault_address), cls._EXECUTOR_STORAGE_SLOT
|
|
322
335
|
)
|
|
323
|
-
)
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
336
|
+
)
|
|
337
|
+
if word >> 160:
|
|
338
|
+
raise ExternalStateError(
|
|
339
|
+
f"malformed executor word in the external-state storage of "
|
|
340
|
+
f"{vault_address}"
|
|
341
|
+
)
|
|
342
|
+
if word == 0:
|
|
343
|
+
raise ExternalStateError(
|
|
327
344
|
f"no ExternalStateExecutor deployed for vault {vault_address}"
|
|
328
345
|
)
|
|
329
|
-
return cls(ctx,
|
|
346
|
+
return cls(ctx, Web3.to_checksum_address(f"0x{word:040x}"))
|
|
330
347
|
|
|
331
348
|
def balances(self, balance_account: ChecksumAddress) -> Call[Amount]:
|
|
332
349
|
"""Underlying-unit balance the executor tracks for `balance_account`.
|
|
@@ -634,11 +651,10 @@ class ExternalStateExecutor(ContractWrapper):
|
|
|
634
651
|
Accepts receipt-shaped logs (HexBytes) and JSON-RPC / `eth_simulateV1`
|
|
635
652
|
-shaped logs (`0x`-hex strings) alike, so it also decodes rows from an
|
|
636
653
|
event index. Needs no chain access. Raises `ValueError` for a log that
|
|
637
|
-
is not this event
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
first.
|
|
654
|
+
is not this event or was not emitted by this executor, and for a call
|
|
655
|
+
with no chain id available at all; `ExternalStateError` (also a
|
|
656
|
+
`ValueError`) for one of ours that carries a hash that is not the hash
|
|
657
|
+
of its own fields, or whose data is missing or malformed.
|
|
642
658
|
|
|
643
659
|
Security: two checks, and both are needed. This executor must have
|
|
644
660
|
emitted the log, and `proposal_hash` must be the hash of the log's own
|
|
@@ -682,7 +698,7 @@ class ExternalStateExecutor(ContractWrapper):
|
|
|
682
698
|
"""Decode a log whose topic0 is already known to be this event."""
|
|
683
699
|
data = log.get("data")
|
|
684
700
|
if data is None:
|
|
685
|
-
raise
|
|
701
|
+
raise ExternalStateError("BalanceProposed log carries no data")
|
|
686
702
|
try:
|
|
687
703
|
# Unpacked in the order `BalanceProposed` declares them; all six
|
|
688
704
|
# are unindexed, so topics carries topic0 alone.
|
|
@@ -690,8 +706,8 @@ class ExternalStateExecutor(ContractWrapper):
|
|
|
690
706
|
cls._BALANCE_PROPOSED_TYPES,
|
|
691
707
|
HexBytes(data),
|
|
692
708
|
)
|
|
693
|
-
except (DecodingError, ValueError) as exc:
|
|
694
|
-
raise
|
|
709
|
+
except (DecodingError, ValueError, TypeError) as exc:
|
|
710
|
+
raise ExternalStateError(f"malformed BalanceProposed data: {exc}") from exc
|
|
695
711
|
return BalanceProposal(
|
|
696
712
|
balance_account=Web3.to_checksum_address(account),
|
|
697
713
|
proposer=Web3.to_checksum_address(proposer),
|
|
@@ -750,7 +766,19 @@ class ExternalStateExecutor(ContractWrapper):
|
|
|
750
766
|
silently skips, and over a range you filtered yourself a foreign log
|
|
751
767
|
means the filter is wrong, not that the row is uninteresting.
|
|
752
768
|
"""
|
|
753
|
-
|
|
769
|
+
found = self._last_balance_proposed(logs, balance_account, chain_id)
|
|
770
|
+
if found is not None:
|
|
771
|
+
return found
|
|
772
|
+
raise ValueError(_no_balance_proposed(self._address, balance_account))
|
|
773
|
+
|
|
774
|
+
def _last_balance_proposed(
|
|
775
|
+
self,
|
|
776
|
+
logs: Iterable[RawLog],
|
|
777
|
+
balance_account: ChecksumAddress | None,
|
|
778
|
+
chain_id: ChainId | None,
|
|
779
|
+
) -> BalanceProposal | None:
|
|
780
|
+
"""`find_balance_proposed` without the raise when nothing matches."""
|
|
781
|
+
self._require_address()
|
|
754
782
|
wanted = (
|
|
755
783
|
Web3.to_checksum_address(balance_account)
|
|
756
784
|
if balance_account is not None
|
|
@@ -773,11 +801,7 @@ class ExternalStateExecutor(ContractWrapper):
|
|
|
773
801
|
executor=self._address,
|
|
774
802
|
chain_id=self._require_chain_id(chain_id),
|
|
775
803
|
)
|
|
776
|
-
|
|
777
|
-
for_account = f" for account {wanted}" if wanted is not None else ""
|
|
778
|
-
raise ValueError(
|
|
779
|
-
f"no BalanceProposed log from executor {expected_emitter}{for_account}"
|
|
780
|
-
)
|
|
804
|
+
return found
|
|
781
805
|
|
|
782
806
|
def mark_nav(
|
|
783
807
|
self,
|
|
@@ -840,19 +864,24 @@ class ExternalStateExecutor(ContractWrapper):
|
|
|
840
864
|
)
|
|
841
865
|
# Verified against the CONFIRMER's chain, which is the one the confirm
|
|
842
866
|
# will be checked on; the wrapper's own ctx may be a third context.
|
|
843
|
-
proposal = self.
|
|
844
|
-
propose_receipt["logs"], balance_account,
|
|
867
|
+
proposal = self._last_balance_proposed(
|
|
868
|
+
propose_receipt["logs"], balance_account, confirmer_ctx.chain_id
|
|
845
869
|
)
|
|
870
|
+
if proposal is None:
|
|
871
|
+
raise ExternalStateError(
|
|
872
|
+
"the propose receipt records "
|
|
873
|
+
+ _no_balance_proposed(self._address, balance_account)
|
|
874
|
+
)
|
|
846
875
|
# The receipt is data from the node, so confirm only what we asked for:
|
|
847
876
|
# signing off on a proposal that differs from the one just sent would
|
|
848
877
|
# attest a value this call never chose.
|
|
849
878
|
if proposal.value != value:
|
|
850
|
-
raise
|
|
879
|
+
raise ExternalStateError(
|
|
851
880
|
f"proposed {value} but the receipt records {proposal.value}; "
|
|
852
881
|
"refusing to confirm"
|
|
853
882
|
)
|
|
854
883
|
if proposal.proposer != proposer_address:
|
|
855
|
-
raise
|
|
884
|
+
raise ExternalStateError(
|
|
856
885
|
f"proposal was made by {proposal.proposer}, not {proposer_address}; "
|
|
857
886
|
"refusing to confirm"
|
|
858
887
|
)
|
|
@@ -16,6 +16,7 @@ from web3.utils.address import get_create_address
|
|
|
16
16
|
from ipor_fusion.core.contract import Call, _encode_calldata
|
|
17
17
|
from ipor_fusion.core.fuse_action import FuseAction
|
|
18
18
|
from ipor_fusion.errors import (
|
|
19
|
+
BalanceSlotNotFoundError,
|
|
19
20
|
SimulationError,
|
|
20
21
|
SimulationRunError,
|
|
21
22
|
decode_custom_error,
|
|
@@ -751,13 +752,13 @@ def erc20_balance_slot(
|
|
|
751
752
|
"""Find the storage slot of ``token``'s ``balances`` mapping by overriding
|
|
752
753
|
every candidate in one ``eth_simulateV1`` request, using a distinct holder
|
|
753
754
|
per slot, until ``balanceOf`` reflects the override (slot 9 for Circle's
|
|
754
|
-
FiatToken, 0 for OpenZeppelin ERC20). Raises ``
|
|
755
|
-
to ``max_slot`` answers, which is the case for tokens whose
|
|
756
|
-
a plain address-keyed mapping
|
|
755
|
+
FiatToken, 0 for OpenZeppelin ERC20). Raises ``BalanceSlotNotFoundError``
|
|
756
|
+
when no slot up to ``max_slot`` answers, which is the case for tokens whose
|
|
757
|
+
balances are not a plain address-keyed mapping, and ``ValueError`` for a
|
|
758
|
+
negative ``max_slot``."""
|
|
757
759
|
probe = 0x1234_5678_9ABC
|
|
758
|
-
no_slot = f"no balances mapping slot found for {token} up to {max_slot}"
|
|
759
760
|
if max_slot < 0:
|
|
760
|
-
raise ValueError(
|
|
761
|
+
raise ValueError(f"max_slot must be non-negative, got {max_slot}")
|
|
761
762
|
|
|
762
763
|
sim = VaultSimulator(web3, block=block)
|
|
763
764
|
for slot in range(max_slot + 1):
|
|
@@ -778,7 +779,9 @@ def erc20_balance_slot(
|
|
|
778
779
|
for slot in range(max_slot + 1):
|
|
779
780
|
if result.get(f"balance_slot_{slot}") == probe:
|
|
780
781
|
return slot
|
|
781
|
-
raise
|
|
782
|
+
raise BalanceSlotNotFoundError(
|
|
783
|
+
f"no balances mapping slot found for {token} up to {max_slot}"
|
|
784
|
+
)
|
|
782
785
|
|
|
783
786
|
|
|
784
787
|
def _normalize_call_error(
|
{ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/crosschain/ccip/chainlink.py
RENAMED
|
@@ -16,6 +16,7 @@ from eth_typing import ChecksumAddress
|
|
|
16
16
|
from ipor_fusion.core.context import Web3Context
|
|
17
17
|
from ipor_fusion.core.contract import Call, ContractWrapper
|
|
18
18
|
from ipor_fusion.crosschain.contracts import _address
|
|
19
|
+
from ipor_fusion.errors import UnsupportedContractVersionError
|
|
19
20
|
from ipor_fusion.types import ZERO_ADDRESS, MessageId
|
|
20
21
|
|
|
21
22
|
|
|
@@ -118,9 +119,9 @@ def ccip_token_lane(
|
|
|
118
119
|
chain_selector: int,
|
|
119
120
|
) -> CcipTokenLane:
|
|
120
121
|
"""Read, on ``router``'s chain, whether a message lane and a ``token`` lane
|
|
121
|
-
exist towards ``chain_selector``. Seven reads at most. Raises
|
|
122
|
-
before reading static config when the
|
|
123
|
-
layout."""
|
|
122
|
+
exist towards ``chain_selector``. Seven reads at most. Raises
|
|
123
|
+
``UnsupportedContractVersionError`` before reading static config when the
|
|
124
|
+
OnRamp version has an unverified layout."""
|
|
124
125
|
router_wrapper = CcipRouter(ctx, router)
|
|
125
126
|
if not router_wrapper.is_chain_supported(chain_selector).call():
|
|
126
127
|
return CcipTokenLane(chain_selector, message_lane=False)
|
|
@@ -128,7 +129,7 @@ def ccip_token_lane(
|
|
|
128
129
|
on_ramp_wrapper = CcipOnRamp(ctx, on_ramp)
|
|
129
130
|
on_ramp_version = on_ramp_wrapper.type_and_version().call()
|
|
130
131
|
if on_ramp_version not in _SUPPORTED_ON_RAMP_VERSIONS:
|
|
131
|
-
raise
|
|
132
|
+
raise UnsupportedContractVersionError(
|
|
132
133
|
f"unsupported CCIP OnRamp version {on_ramp_version!r} at {on_ramp}; "
|
|
133
134
|
"ccip_token_lane supports OnRamp 2.0.0"
|
|
134
135
|
)
|
{ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/crosschain/ccip/indexer.py
RENAMED
|
@@ -35,8 +35,9 @@ _HEADERS = {"Accept": "application/json", "User-Agent": "ipor-fusion-sdk"}
|
|
|
35
35
|
|
|
36
36
|
class VerifierResultUnavailable(IporFusionError, LookupError):
|
|
37
37
|
"""No indexer could serve a verifier result for the message (not verified
|
|
38
|
-
yet,
|
|
39
|
-
LookupError: the result looked
|
|
38
|
+
yet, the indexers were unreachable, or they answered something unreadable);
|
|
39
|
+
the message says which, per indexer. Also a LookupError: the result looked
|
|
40
|
+
up is not there (yet)."""
|
|
40
41
|
|
|
41
42
|
|
|
42
43
|
@dataclass(frozen=True, slots=True)
|
|
@@ -63,7 +64,10 @@ def fetch_verifier_result(
|
|
|
63
64
|
timeout: float = 15.0,
|
|
64
65
|
) -> VerifierResult:
|
|
65
66
|
"""``GET /v1/verifierresults/<messageId>`` from the first indexer that has
|
|
66
|
-
a result; raises :class:`VerifierResultUnavailable` otherwise.
|
|
67
|
+
a readable result; raises :class:`VerifierResultUnavailable` otherwise. An
|
|
68
|
+
indexer whose answer cannot be read (not JSON, a missing or mistyped field,
|
|
69
|
+
invalid hex or address, a result for another message) is skipped like one
|
|
70
|
+
that is unreachable."""
|
|
67
71
|
if len(message_id) != 32:
|
|
68
72
|
raise ValueError("message_id must be 32 bytes")
|
|
69
73
|
hex_id = "0x" + message_id.hex()
|
|
@@ -79,26 +83,57 @@ def fetch_verifier_result(
|
|
|
79
83
|
if response.status_code != 200:
|
|
80
84
|
reasons.append(f"{base}: HTTP {response.status_code}")
|
|
81
85
|
continue
|
|
82
|
-
|
|
83
|
-
|
|
86
|
+
try:
|
|
87
|
+
result = _parse_answer(response.json(), message_id, base)
|
|
88
|
+
except (KeyError, IndexError, TypeError, ValueError) as exc:
|
|
89
|
+
reasons.append(f"{base}: malformed answer ({type(exc).__name__}: {exc})")
|
|
90
|
+
continue
|
|
91
|
+
if result is None:
|
|
84
92
|
reasons.append(f"{base}: no result yet")
|
|
85
93
|
continue
|
|
86
|
-
result
|
|
87
|
-
return VerifierResult(
|
|
88
|
-
message_id=MessageId(bytes.fromhex(result["message_id"][2:])),
|
|
89
|
-
ccv_addresses=tuple(
|
|
90
|
-
Web3.to_checksum_address(a) for a in result["message_ccv_addresses"]
|
|
91
|
-
),
|
|
92
|
-
ccv_data=bytes.fromhex(result["ccv_data"][2:]),
|
|
93
|
-
executor=Web3.to_checksum_address(result["message_executor_address"]),
|
|
94
|
-
message=result["message"],
|
|
95
|
-
indexer=base,
|
|
96
|
-
)
|
|
94
|
+
return result
|
|
97
95
|
raise VerifierResultUnavailable(
|
|
98
96
|
f"no verifier result for {hex_id}: " + "; ".join(reasons)
|
|
99
97
|
)
|
|
100
98
|
|
|
101
99
|
|
|
100
|
+
def _hex_bytes(value: Any) -> bytes:
|
|
101
|
+
if not isinstance(value, str) or not value.startswith("0x"):
|
|
102
|
+
raise ValueError(f"not 0x-prefixed hex: {value!r}")
|
|
103
|
+
return bytes.fromhex(value[2:])
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _parse_answer(
|
|
107
|
+
body: Any, message_id: MessageId, indexer: str
|
|
108
|
+
) -> VerifierResult | None:
|
|
109
|
+
"""The first result of an indexer's JSON answer; ``None`` when it has none.
|
|
110
|
+
Anything else it cannot read raises a builtin error, reported by the caller
|
|
111
|
+
as a malformed answer."""
|
|
112
|
+
if not isinstance(body, Mapping):
|
|
113
|
+
raise TypeError("answer is not a JSON object")
|
|
114
|
+
results = body.get("results")
|
|
115
|
+
if not results:
|
|
116
|
+
return None
|
|
117
|
+
result = results[0]["verifierResult"]
|
|
118
|
+
served_id = _hex_bytes(result["message_id"])
|
|
119
|
+
if served_id != message_id:
|
|
120
|
+
raise ValueError(f"result is for message 0x{served_id.hex()}")
|
|
121
|
+
message = result["message"]
|
|
122
|
+
if not isinstance(message, Mapping):
|
|
123
|
+
raise TypeError("message is not an object")
|
|
124
|
+
ccvs = result["message_ccv_addresses"]
|
|
125
|
+
if not isinstance(ccvs, list) or not ccvs:
|
|
126
|
+
raise ValueError("message_ccv_addresses is not a non-empty array")
|
|
127
|
+
return VerifierResult(
|
|
128
|
+
message_id=MessageId(served_id),
|
|
129
|
+
ccv_addresses=tuple(Web3.to_checksum_address(a) for a in ccvs),
|
|
130
|
+
ccv_data=_hex_bytes(result["ccv_data"]),
|
|
131
|
+
executor=Web3.to_checksum_address(result["message_executor_address"]),
|
|
132
|
+
message=message,
|
|
133
|
+
indexer=indexer,
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
|
|
102
137
|
def manual_execution(
|
|
103
138
|
off_ramp: CcipOffRamp,
|
|
104
139
|
encoded_message: bytes,
|
{ipor_fusion-4.0.0.dev83 → ipor_fusion-4.0.0.dev86}/src/ipor_fusion/crosschain/ccip/quote.py
RENAMED
|
@@ -18,6 +18,7 @@ from web3.types import BlockIdentifier
|
|
|
18
18
|
|
|
19
19
|
from ipor_fusion.core.contract import Call
|
|
20
20
|
from ipor_fusion.core.simulation import VaultSimulator
|
|
21
|
+
from ipor_fusion.errors import SimulationError
|
|
21
22
|
|
|
22
23
|
#: Selector of ``CcipInsufficientNativeFeeBalance(uint256 balance, uint256 required)``.
|
|
23
24
|
INSUFFICIENT_NATIVE_FEE_SELECTOR: bytes = keccak(
|
|
@@ -40,8 +41,8 @@ def quote_ccip_native_fee(
|
|
|
40
41
|
``payer`` is the contract that pays: the factory for ``requestDispatcher``,
|
|
41
42
|
the executor for a supply or command (``from_`` is then the alpha sending
|
|
42
43
|
the vault's ``execute``), the dispatcher for a return. The call must be
|
|
43
|
-
valid at ``block`` up to the fee check
|
|
44
|
-
sends no message
|
|
44
|
+
valid at ``block`` up to the fee check: a revert before it raises
|
|
45
|
+
``SimulationError`` and a call that sends no message ``ValueError``.
|
|
45
46
|
"""
|
|
46
47
|
simulator = VaultSimulator(web3, alpha=from_, block=block)
|
|
47
48
|
simulator.with_state_override(payer, balance=hex(0))
|
|
@@ -55,6 +56,8 @@ def quote_ccip_native_fee(
|
|
|
55
56
|
raise ValueError(
|
|
56
57
|
"the call succeeded with the payer at zero balance: it sends no CCIP message"
|
|
57
58
|
)
|
|
58
|
-
raise
|
|
59
|
-
f"the call reverted before the CCIP fee check: {result.revert_reason}"
|
|
59
|
+
raise SimulationError(
|
|
60
|
+
f"the call reverted before the CCIP fee check: {result.revert_reason}",
|
|
61
|
+
label="quote",
|
|
62
|
+
revert_reason=result.revert_reason,
|
|
60
63
|
)
|
|
@@ -148,16 +148,17 @@ def get_revert_reason(web3: Web3, tx_hash: TxHash, receipt: TxReceipt) -> str |
|
|
|
148
148
|
class IporFusionError(Exception):
|
|
149
149
|
"""Base of every exception the SDK defines.
|
|
150
150
|
|
|
151
|
-
The rule the SDK follows
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
151
|
+
The rule the SDK follows everywhere: a subclass when the chain, a provider
|
|
152
|
+
or a remote API answers something it cannot use. A caller's mistake (a bad
|
|
153
|
+
argument, or an operation the object was not built for, such as
|
|
154
|
+
``.call()`` on a write-only ``Call``) raises a builtin ``ValueError``
|
|
155
|
+
instead, as the standard library does for an operation on a closed file;
|
|
156
|
+
an argument of the wrong type raises ``TypeError`` and an unknown name in a
|
|
157
|
+
mapping-style lookup ``KeyError``. A subclass that also describes bad input
|
|
158
|
+
(an address that is not a vault, a chain the tooling does not cover)
|
|
159
|
+
additionally derives from ``ValueError``; others keep the builtin base
|
|
160
|
+
their raise sites had before 4.0 (``ValueError``, ``RuntimeError``,
|
|
161
|
+
``LookupError``) so existing handlers still catch them.
|
|
161
162
|
"""
|
|
162
163
|
|
|
163
164
|
|
|
@@ -191,6 +192,44 @@ class UnsupportedVaultVersionError(IporFusionError, ValueError):
|
|
|
191
192
|
"""
|
|
192
193
|
|
|
193
194
|
|
|
195
|
+
class UnsupportedContractVersionError(IporFusionError, ValueError):
|
|
196
|
+
"""A contract reports a version whose layout the SDK has not verified, so
|
|
197
|
+
reading it further could misread its storage or return data.
|
|
198
|
+
|
|
199
|
+
Also a ValueError, the type these failures had before 4.0.
|
|
200
|
+
"""
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
class HyperCoreValuationError(IporFusionError, ValueError):
|
|
204
|
+
"""The HyperCore NAV cannot be valued: a precompile read failed, the
|
|
205
|
+
oracle prices no granted spot token or its price overflows, the vault
|
|
206
|
+
grants a perp dex bitmap the valuation does not support, or the NAV is
|
|
207
|
+
negative (where the balance fuse reverts).
|
|
208
|
+
|
|
209
|
+
Also a ValueError, the type these failures had before 4.0.
|
|
210
|
+
"""
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
class ExternalStateError(IporFusionError, ValueError):
|
|
214
|
+
"""The external-state market's on-chain data is not usable: the vault's
|
|
215
|
+
storage holds no executor or a malformed word, an executor's
|
|
216
|
+
``BalanceProposed`` log is malformed or carries a hash that is not the hash
|
|
217
|
+
of its own fields, or a propose receipt does not record the proposal that
|
|
218
|
+
was sent.
|
|
219
|
+
|
|
220
|
+
Also a ValueError, the type these failures had before 4.0.
|
|
221
|
+
"""
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
class BalanceSlotNotFoundError(IporFusionError, ValueError):
|
|
225
|
+
"""No candidate storage slot of a token answers as its ``balances``
|
|
226
|
+
mapping: its balances are not a plain address-keyed mapping, or the
|
|
227
|
+
mapping lies beyond the slots searched.
|
|
228
|
+
|
|
229
|
+
Also a ValueError, the type this failure had before 4.0.
|
|
230
|
+
"""
|
|
231
|
+
|
|
232
|
+
|
|
194
233
|
class LogScanError(IporFusionError):
|
|
195
234
|
"""An `eth_getLogs` scan could not cover its whole block range.
|
|
196
235
|
|
|
@@ -252,7 +291,8 @@ class SimulationRunError(IporFusionError, RuntimeError):
|
|
|
252
291
|
|
|
253
292
|
|
|
254
293
|
class SimulationError(IporFusionError):
|
|
255
|
-
"""A simulated call reverted; raised by ``SimulationResult.raise_for_failure
|
|
294
|
+
"""A simulated call reverted; raised by ``SimulationResult.raise_for_failure``
|
|
295
|
+
and by ``quote_ccip_native_fee`` for a revert before the CCIP fee check."""
|
|
256
296
|
|
|
257
297
|
def __init__(
|
|
258
298
|
self,
|
|
@@ -633,10 +633,7 @@ def market_meta_morpho(
|
|
|
633
633
|
vault_address: MetaMorpho V1 or Vault V2 address.
|
|
634
634
|
chain_id: EVM chain ID.
|
|
635
635
|
"""
|
|
636
|
-
|
|
637
|
-
info = fetch_vault(Web3.to_checksum_address(vault_address), chain_id)
|
|
638
|
-
except MorphoApiError as exc:
|
|
639
|
-
raise ValueError(str(exc)) from exc
|
|
636
|
+
info = fetch_vault(Web3.to_checksum_address(vault_address), chain_id)
|
|
640
637
|
payload = build_meta_morpho_json(info, chain_id)
|
|
641
638
|
return MetaMorphoVaultResponse.model_validate(payload)
|
|
642
639
|
|