ipor-fusion 4.0.0.dev203__tar.gz → 4.0.0.dev210__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.dev203 → ipor_fusion-4.0.0.dev210}/.github/workflows/cd.yml +2 -1
- ipor_fusion-4.0.0.dev210/.github/workflows/ci.yml +55 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/.github/workflows/python-build.yml +15 -4
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/AGENTS.md +17 -4
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/MIGRATION_4.md +7 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/PKG-INFO +1 -1
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/pyproject.toml +2 -4
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/crosschain/ccip/lane.py +32 -3
- ipor_fusion-4.0.0.dev210/tests/_live.py +67 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/conftest.py +11 -13
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_crosschain_lane.py +58 -3
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_readiness_selection.py +2 -1
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_solidity_mirrors.py +3 -2
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_vault_info_onchain_base.py +11 -23
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_vault_info_onchain_base_no_withdraw_manager.py +4 -19
- ipor_fusion-4.0.0.dev203/.github/workflows/ci.yml +0 -32
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/.claude-plugin/marketplace.json +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/.claude-plugin/plugin.json +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/.env.example +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/.gemini/settings.json +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/.git-blame-ignore-revs +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/.github/workflows/commit-lint.yml +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/.github/workflows/notify-slack.yml +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/.github/workflows/python-publish.yml +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/.github/workflows/release.yml +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/.github/workflows/report-slack.yml +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/.gitignore +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/.mcp.json +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/.pre-commit-config.yaml +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/.python-version +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/CHANGELOG.md +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/CLAUDE.md +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/CODEOWNERS +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/LICENSE +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/README.md +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/build.sh +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/examples/CONTRIBUTING.md +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/examples/README.md +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/examples/advanced_euler_v2_credit_market_base.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/examples/advanced_euler_v2_looping_arbitrum.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/examples/composed_hypercore_flow_arbitrum_hyperevm.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/examples/crosschain_ccip_usdc_arbitrum_hyperevm.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/examples/external_state_margin_leg_base.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/examples/simple_aave_v3_supply_base.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/pytest.ini +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/scripts/commit-check.sh +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/scripts/crosschain_readiness.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/skills/ipor-deploy-vault/SKILL.md +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/about.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/cache.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/chains.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/cli/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/cli/changelog_cmd.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/cli/config_cmd.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/cli/config_store.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/cli/main.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/cli/market_cmd.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/cli/options.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/cli/oracle_mapping_view.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/cli/vault_cmd.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/cli/vault_health.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/cli/vault_info_view.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/cli/vault_markets_view.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/cli/vault_rendering.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/config/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/config/roles.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/core/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/core/access.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/core/address.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/core/context.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/core/contract.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/core/erc20.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/core/external_state_executor.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/core/fee_manager.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/core/fuse.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/core/fuse_action.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/core/fusion_factory.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/core/logs.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/core/multicall.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/core/oracle.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/core/plasma_vault.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/core/rewards_manager.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/core/simulation.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/core/simulation_result.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/core/withdraw_manager.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/crosschain/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/crosschain/ccip/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/crosschain/ccip/chainlink.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/crosschain/ccip/codec.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/crosschain/ccip/contracts.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/crosschain/ccip/events.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/crosschain/ccip/indexer.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/crosschain/ccip/quote.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/crosschain/ccip/transport.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/crosschain/contracts.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/crosschain/discovery.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/crosschain/errors.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/crosschain/fuses/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/crosschain/fuses/base.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/crosschain/fuses/ccip.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/crosschain/fuses/stargate.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/crosschain/lane.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/crosschain/logs.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/crosschain/messages.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/crosschain/simulation.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/crosschain/stargate/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/crosschain/stargate/contracts.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/crosschain/stargate/lane.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/crosschain/stargate/layerzero.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/crosschain/stargate/transport.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/crosschain/transport.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/dependency_graph.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/entry_cli.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/entry_mcp.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/errors.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/explorer.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/field_docs.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/fuses/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/fuses/aave_v3.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/fuses/async_action.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/fuses/base.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/fuses/compound_v3.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/fuses/erc4626.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/fuses/euler_v2.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/fuses/events.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/fuses/external_state.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/fuses/external_state_event_decoding.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/fuses/fluid_instadapp.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/fuses/gearbox_v3.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/fuses/hypercore.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/fuses/hypercore_event_decoding.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/fuses/merkl.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/fuses/morpho.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/fuses/ramses_v2.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/fuses/uniswap_v3.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/fuses/universal.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/guide/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/guide/architecture.md +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/guide/glossary.md +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/guide/invariants.md +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/guide/quickstart.md +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/market_ids.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/market_info.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/mcp/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/mcp/guide.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/mcp/models.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/mcp/server.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/morpho_api.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/py.typed +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/readers/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/readers/aave_v3.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/readers/aave_v4.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/readers/compound_v3.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/readers/hypercore.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/readers/hypercore_outcome.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/readers/lending_health.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/readers/morpho.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/readers/oracle_mapping.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/readers/oracle_mapping_nodes.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/readers/oracle_mapping_reader.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/readers/position_manager.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/readers/ramses_v2.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/readers/uniswap_v3.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/readers/unpriceable.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/substrates.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/types.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/validation.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/vault_data.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/vault_erc20.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/vault_info.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/vault_managers.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/vault_positions.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/vault_reads.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/UniswapV3UniversalRouter.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/_crosschain.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/_crosschain_lifecycle.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/_crosschain_recovery.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/_euler_v2.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/_event_args.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/_fake_rpc.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/_foundry.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/_golden.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/_guide.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/_hypercore_model.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/_hypercore_shadow.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/_multicall.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/_oracle_mapping_fake.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/_simulate.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/_validation.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/addresses.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/constants.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/fixtures/hypercore_hip3_flow.json +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/fixtures/hypercore_market55.json +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/fixtures/market_golden/meta_morpho_v1.json +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/fixtures/market_golden/meta_morpho_v1.mcp.json +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/fixtures/market_golden/meta_morpho_v2.json +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/fixtures/market_golden/meta_morpho_v2.mcp.json +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/fixtures/market_golden/morpho_api/market.json +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/fixtures/market_golden/morpho_api/vault_v1.json +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/fixtures/market_golden/morpho_api/vault_v2.json +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/fixtures/market_golden/morpho_api/vault_v2_not_found.json +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/fixtures/market_golden/morpho_blue.json +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/fixtures/market_golden/morpho_blue.mcp.json +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/fixtures/market_golden/morpho_blue_api_error.json +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/fixtures/market_golden/morpho_blue_api_error.mcp.json +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/fixtures/market_golden/morpho_blue_no_api.json +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/fixtures/market_golden/morpho_blue_no_api.mcp.json +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/fixtures/oracle_mapping_golden/mapping.json +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/fixtures/oracle_mapping_golden/mapping.mcp.json +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/fixtures/simulate_v1_request.json +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/fixtures/vault_info_golden/clean.json +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/fixtures/vault_info_golden/clean.mcp.json +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/fixtures/vault_info_golden/clean.txt +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/fixtures/vault_info_golden/deployment_error.json +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/fixtures/vault_info_golden/deployment_error.mcp.json +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/fixtures/vault_info_golden/deployment_error.txt +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/fixtures/vault_info_golden/fetched.cli.json +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/fixtures/vault_info_golden/fetched.json +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/fixtures/vault_info_golden/fetched.mcp.json +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/fixtures/vault_info_golden/full.json +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/fixtures/vault_info_golden/full.mcp.json +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/fixtures/vault_info_golden/full.txt +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/fixtures/vault_info_golden/rich.json +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/fixtures/vault_info_golden/rich.mcp.json +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/fixtures/vault_info_golden/rich.txt +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_about.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_access_manager.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_cache.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_chains.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_cli_changelog.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_cli_commands.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_cli_config_store.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_cli_helpers.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_cli_hypercore.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_cli_lending_health.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_cli_market_golden.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_cli_market_morpho_blue.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_cli_oracle_mapping.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_cli_oracle_mapping_golden.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_cli_pending_requests_scan.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_cli_role_accounts.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_cli_vault_info_golden.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_cli_vault_predates_getters.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_cli_vault_validate.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_contract_call.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_contract_encoder.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_crosschain_ccip_events.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_crosschain_ccip_indexer.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_crosschain_ccip_quote.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_crosschain_chainlink.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_crosschain_contracts.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_crosschain_encoding.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_crosschain_errors.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_crosschain_layering.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_crosschain_readiness.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_crosschain_readiness_report.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_crosschain_transport.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_docs_typing.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_erc20.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_error_hierarchy.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_examples_composed_hypercore.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_examples_crosschain.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_examples_euler_looping_arbitrum.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_examples_vaults.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_explorer.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_external_state_events.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_external_state_executor.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_fee_manager.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_fuse_encoding.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_fusion_instance.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_guide.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_hypercore_deployment.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_hypercore_events.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_hypercore_flow.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_hypercore_live.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_hypercore_outcome.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_hypercore_readers.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_hypercore_simulation.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_logs.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_mcp_hypercore.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_mcp_models.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_mcp_server.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_mcp_vault_validate.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_multicall.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_oracle.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_oracle_mapping.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_plasma_vault.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_public_api.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_public_type_reachability.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_reader_encoding.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_revert_decoding.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_rewards_manager.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_roles.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_sdk_layering.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_simulate_borrow_aave_v3_mainnet.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_simulate_claim_merkl_wrapper_base.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_simulate_claim_morpho_rewards.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_simulate_crosschain_canary.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_simulate_crosschain_hypercore.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_simulate_crosschain_hyperevm.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_simulate_crosschain_lifecycle.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_simulate_euler_v2_batch_base.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_simulate_euler_v2_credit_market_base.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_simulate_euler_v2_swap_deploy_base.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_simulate_euler_v2_swap_reconfigure_base.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_simulate_euler_v2_swap_registry_base.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_simulate_examples_arbitrum.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_simulate_examples_base.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_simulate_examples_crosschain.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_simulate_external_state_executor_base.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_simulate_fusion_factory_clone_base.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_simulate_looping_aave_v3_base.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_simulate_looping_morpho_blue_base.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_simulate_morpho_blue.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_simulate_pilot_v3.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_simulate_pilot_v4_uniswap.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_simulate_pilot_v5_ramses.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_simulate_pilot_v5_scheduled.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_simulate_plasma_vault_setters_base.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_simulate_readers_integration.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_simulate_release_funds.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_simulate_swap_on_base.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_simulate_vault_from_scratch_base.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_simulation_errors.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_simulation_observation.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_solidity_compiler.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_substrates.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_types_and_events.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_unpriceable_tokens.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_validation.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_vault_data_block.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_vault_dep_graph.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_vault_info_fetched_golden.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_vault_reconciliation_onchain_base.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_vault_simulator_blocks.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_web3_context.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/test_withdraw_manager.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/typed_client/consumer.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/tests/typed_client/rejected.py +0 -0
- {ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/uv.lock +0 -0
|
@@ -12,12 +12,13 @@ jobs:
|
|
|
12
12
|
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
|
|
13
13
|
|
|
14
14
|
build:
|
|
15
|
-
needs: notify
|
|
16
15
|
uses: ./.github/workflows/python-build.yml
|
|
17
16
|
secrets:
|
|
18
17
|
ETHEREUM_PROVIDER_URL: ${{ secrets.ETHEREUM_PROVIDER_URL }}
|
|
19
18
|
ARBITRUM_PROVIDER_URL: ${{ secrets.ARBITRUM_PROVIDER_URL }}
|
|
20
19
|
BASE_PROVIDER_URL: ${{ secrets.BASE_PROVIDER_URL }}
|
|
20
|
+
with:
|
|
21
|
+
require-live: ETHEREUM_PROVIDER_URL,ARBITRUM_PROVIDER_URL,BASE_PROVIDER_URL,GITHUB
|
|
21
22
|
|
|
22
23
|
report:
|
|
23
24
|
if: ${{ always() }}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
branches:
|
|
6
|
+
- "**"
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
notify:
|
|
10
|
+
uses: ./.github/workflows/notify-slack.yml
|
|
11
|
+
secrets:
|
|
12
|
+
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
|
|
13
|
+
|
|
14
|
+
# No secrets: what the code proves offline, on every supported Python.
|
|
15
|
+
offline:
|
|
16
|
+
strategy:
|
|
17
|
+
fail-fast: false
|
|
18
|
+
matrix:
|
|
19
|
+
python-version: ["3.11", "3.12", "3.13"]
|
|
20
|
+
uses: ./.github/workflows/python-build.yml
|
|
21
|
+
with:
|
|
22
|
+
python-version: ${{ matrix.python-version }}
|
|
23
|
+
format-code-enabled: ${{ matrix.python-version == '3.12' }}
|
|
24
|
+
static-code-analysis-enabled: ${{ matrix.python-version == '3.12' }}
|
|
25
|
+
build-enabled: ${{ matrix.python-version == '3.12' }}
|
|
26
|
+
coverage-artifact-name: coverage-offline-${{ matrix.python-version }}
|
|
27
|
+
|
|
28
|
+
# Live providers, for pull requests from this repository only (a fork gets
|
|
29
|
+
# no secrets); a provider or GitHub outage fails it instead of skipping.
|
|
30
|
+
live:
|
|
31
|
+
if: ${{ github.event.pull_request.head.repo.full_name == github.repository }}
|
|
32
|
+
uses: ./.github/workflows/python-build.yml
|
|
33
|
+
secrets:
|
|
34
|
+
ETHEREUM_PROVIDER_URL: ${{ secrets.ETHEREUM_PROVIDER_URL }}
|
|
35
|
+
ARBITRUM_PROVIDER_URL: ${{ secrets.ARBITRUM_PROVIDER_URL }}
|
|
36
|
+
BASE_PROVIDER_URL: ${{ secrets.BASE_PROVIDER_URL }}
|
|
37
|
+
with:
|
|
38
|
+
format-code-enabled: false
|
|
39
|
+
static-code-analysis-enabled: false
|
|
40
|
+
build-enabled: false
|
|
41
|
+
coverage-artifact-name: coverage-live
|
|
42
|
+
require-live: ETHEREUM_PROVIDER_URL,ARBITRUM_PROVIDER_URL,BASE_PROVIDER_URL,GITHUB
|
|
43
|
+
|
|
44
|
+
report:
|
|
45
|
+
if: ${{ always() }}
|
|
46
|
+
needs:
|
|
47
|
+
- notify
|
|
48
|
+
- offline
|
|
49
|
+
- live
|
|
50
|
+
uses: ./.github/workflows/report-slack.yml
|
|
51
|
+
secrets:
|
|
52
|
+
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
|
|
53
|
+
with:
|
|
54
|
+
success: ${{ needs.offline.result == 'success' && contains(fromJSON('["success", "skipped"]'), needs.live.result) }}
|
|
55
|
+
slack-status-msg-id: ${{ needs.notify.outputs.slack-status-msg-id }}
|
|
@@ -5,11 +5,11 @@ on:
|
|
|
5
5
|
|
|
6
6
|
secrets:
|
|
7
7
|
ARBITRUM_PROVIDER_URL:
|
|
8
|
-
required:
|
|
8
|
+
required: false
|
|
9
9
|
BASE_PROVIDER_URL:
|
|
10
|
-
required:
|
|
10
|
+
required: false
|
|
11
11
|
ETHEREUM_PROVIDER_URL:
|
|
12
|
-
required:
|
|
12
|
+
required: false
|
|
13
13
|
|
|
14
14
|
inputs:
|
|
15
15
|
python-version:
|
|
@@ -42,6 +42,16 @@ on:
|
|
|
42
42
|
type: boolean
|
|
43
43
|
default: true
|
|
44
44
|
required: false
|
|
45
|
+
coverage-artifact-name:
|
|
46
|
+
description: "Name of the uploaded coverage artifact (unique per call in a run)"
|
|
47
|
+
type: string
|
|
48
|
+
default: coverage-report
|
|
49
|
+
required: false
|
|
50
|
+
require-live:
|
|
51
|
+
description: "Comma-separated live sources whose tests must run, not skip (tests/_live.py)"
|
|
52
|
+
type: string
|
|
53
|
+
default: ""
|
|
54
|
+
required: false
|
|
45
55
|
|
|
46
56
|
jobs:
|
|
47
57
|
python:
|
|
@@ -93,6 +103,7 @@ jobs:
|
|
|
93
103
|
ETHEREUM_PROVIDER_URL: ${{ secrets.ETHEREUM_PROVIDER_URL }}
|
|
94
104
|
ARBITRUM_PROVIDER_URL: ${{ secrets.ARBITRUM_PROVIDER_URL }}
|
|
95
105
|
BASE_PROVIDER_URL: ${{ secrets.BASE_PROVIDER_URL }}
|
|
106
|
+
IPOR_FUSION_REQUIRE_LIVE: ${{ inputs.require-live }}
|
|
96
107
|
id: tests
|
|
97
108
|
|
|
98
109
|
- name: Coverage summary
|
|
@@ -104,6 +115,6 @@ jobs:
|
|
|
104
115
|
if: inputs.test-enabled && always() && steps.tests.outcome != 'skipped'
|
|
105
116
|
uses: actions/upload-artifact@v4
|
|
106
117
|
with:
|
|
107
|
-
name: coverage-
|
|
118
|
+
name: ${{ inputs.coverage-artifact-name }}
|
|
108
119
|
path: coverage.xml
|
|
109
120
|
|
|
@@ -27,7 +27,7 @@ uv sync --all-extras --locked # install, same as
|
|
|
27
27
|
uv run ruff format ./ # format (CI runs --check)
|
|
28
28
|
uv run ruff check ./ # lint: bandit S, C901 max-complexity 10, PLR limits
|
|
29
29
|
uv run pyright # types, basic mode, src + tests + examples + scripts
|
|
30
|
-
uv run pytest # all tests + coverage gate (fail_under =
|
|
30
|
+
uv run pytest # all tests + coverage gate (fail_under = 98, mcp/ included)
|
|
31
31
|
uv run pytest -m "cli or mcp" --no-cov # fast offline subset
|
|
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
|
|
@@ -54,13 +54,26 @@ the sequence in `.github/workflows/python-build.yml`. When ruff `C901` trips,
|
|
|
54
54
|
extract helpers, never suppress.
|
|
55
55
|
|
|
56
56
|
**Offline vs network.** `test_cli_*` and `test_mcp_*` need no network
|
|
57
|
-
(auto-tagged `cli` / `mcp` in `conftest.py
|
|
57
|
+
(auto-tagged `cli` / `mcp` in `conftest.py`, `test_examples_*` `examples`;
|
|
58
|
+
everything else is `sdk`). Most `sdk` tests are offline too: the suite with no
|
|
59
|
+
provider set still clears the coverage gate.
|
|
58
60
|
Integration tests (`test_simulate_*`, `test_vault_info_onchain_*`) run against
|
|
59
61
|
live RPCs via `eth_simulateV1`, no Docker or Anvil (`ANVIL_WALLET` is just a
|
|
60
62
|
named address constant). They auto-skip unless `ETHEREUM_PROVIDER_URL`,
|
|
61
63
|
`BASE_PROVIDER_URL` and `ARBITRUM_PROVIDER_URL` are set (`.env` is loaded via
|
|
62
|
-
python-dotenv) and the provider supports `eth_simulateV1`.
|
|
63
|
-
|
|
64
|
+
python-dotenv) and the provider supports `eth_simulateV1`. Every such gate goes
|
|
65
|
+
through `tests/_live.py` (`provider_url`, `archive_context`, `unavailable`): it
|
|
66
|
+
skips locally and fails when the source is named in `IPOR_FUSION_REQUIRE_LIVE`
|
|
67
|
+
(comma-separated env var names, `GITHUB` for the Solidity mirrors). CI
|
|
68
|
+
(`ci.yml`) runs an offline job without secrets on Python 3.11, 3.12 and 3.13
|
|
69
|
+
and, for pull requests from this repository, a live job with the three
|
|
70
|
+
provider secrets that requires all of them and GitHub: a skip for one of those
|
|
71
|
+
sources being unavailable is a failure there (state-dependent skips such as a
|
|
72
|
+
pilot vault's preconditions remain skips, and the offline job still reads the
|
|
73
|
+
public Solidity sources from GitHub). Skip only for an unavailable source; an
|
|
74
|
+
exception from the
|
|
75
|
+
SDK is a test failure, never a skip. Never print `.env` or a provider URL: they
|
|
76
|
+
embed API keys, so skip and fail messages carry the exception type only.
|
|
64
77
|
`HYPEREVM_PROVIDER_URL` is used by the HyperEVM lifecycle/deployment tests and
|
|
65
78
|
the readiness script; general PR CI does not supply it.
|
|
66
79
|
`test_simulate_crosschain_hyperevm.py` rehearses Arbitrum → HyperEVM on pinned blocks,
|
|
@@ -950,6 +950,13 @@ and `log_index` was passed through as given (a hex string from a raw log); the e
|
|
|
950
950
|
These apply to every caller, type-checked or not. Changes described in full above
|
|
951
951
|
are listed with a link.
|
|
952
952
|
|
|
953
|
+
- **`CcipLane.open` refuses a spoke it cannot reach.** The executor's
|
|
954
|
+
`ccipRoute(spoke)` must be registered and point at the dispatcher at the
|
|
955
|
+
executor's own address; otherwise `open` (and `open_lane` / `open_lanes`)
|
|
956
|
+
raises `CrosschainDiscoveryError`, where 3.x built a lane whose default send
|
|
957
|
+
parameters carried a zero fee cap and gas limit and failed only on chain. A
|
|
958
|
+
registered but disabled route still opens, but `default_send` raises.
|
|
959
|
+
|
|
953
960
|
- **The reconciliation counts the rewards claim manager.** `PlasmaVault.totalAssets`
|
|
954
961
|
adds `RewardsClaimManager.balanceOf()` (vested rewards) to the vault's idle
|
|
955
962
|
underlying and its markets; 3.x left it out, so a vault with vested rewards showed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "ipor-fusion"
|
|
3
|
-
version = "4.0.0.
|
|
3
|
+
version = "4.0.0.dev210"
|
|
4
4
|
description = "The IPOR Fusion SDK for Python"
|
|
5
5
|
authors = [
|
|
6
6
|
{ name = "Adam Mydlarz", email = "amydlarz@gmail.com" },
|
|
@@ -123,12 +123,10 @@ executionEnvironments = [
|
|
|
123
123
|
[tool.coverage.run]
|
|
124
124
|
source = ["src/ipor_fusion"]
|
|
125
125
|
data_file = ".coverage_data/.coverage"
|
|
126
|
-
omit = ["src/ipor_fusion/mcp/*"]
|
|
127
126
|
|
|
128
127
|
[tool.coverage.report]
|
|
129
128
|
show_missing = true
|
|
130
|
-
fail_under =
|
|
131
|
-
omit = ["src/ipor_fusion/mcp/*"]
|
|
129
|
+
fail_under = 98
|
|
132
130
|
|
|
133
131
|
[tool.semantic_release]
|
|
134
132
|
# Runs inside the python-semantic-release Docker container, not on the runner
|
{ipor_fusion-4.0.0.dev203 → ipor_fusion-4.0.0.dev210}/src/ipor_fusion/crosschain/ccip/lane.py
RENAMED
|
@@ -22,7 +22,8 @@ from ipor_fusion.crosschain.fuses.ccip import (
|
|
|
22
22
|
)
|
|
23
23
|
from ipor_fusion.crosschain.lane import CrosschainLane, LaneFuses, LaneObservation
|
|
24
24
|
from ipor_fusion.crosschain.messages import CommandStatus, CrosschainTransportKind
|
|
25
|
-
from ipor_fusion.
|
|
25
|
+
from ipor_fusion.errors import CrosschainDiscoveryError
|
|
26
|
+
from ipor_fusion.types import Amount, ChainId, Period, ProposalId
|
|
26
27
|
|
|
27
28
|
|
|
28
29
|
class CcipLane(CrosschainLane):
|
|
@@ -30,7 +31,12 @@ class CcipLane(CrosschainLane):
|
|
|
30
31
|
send parameters come from the executor's registered ``route`` (fee
|
|
31
32
|
ceiling, fee token, token or message gas limit). ``decimal_conversion_rate``
|
|
32
33
|
is the executor's ``DECIMAL_CONVERSION_RATE`` (local per shared unit), used
|
|
33
|
-
to report the dispatcher observation in local decimals.
|
|
34
|
+
to report the dispatcher observation in local decimals.
|
|
35
|
+
|
|
36
|
+
`open` refuses a spoke the executor has no route to, or whose route names
|
|
37
|
+
another peer than the dispatcher at the executor's address; a registered but
|
|
38
|
+
disabled route opens (state can still be read) and refuses the default send
|
|
39
|
+
parameters."""
|
|
34
40
|
|
|
35
41
|
transport_kind = CrosschainTransportKind.CHAINLINK_CCIP
|
|
36
42
|
enforces_min_received = False
|
|
@@ -77,13 +83,15 @@ class CcipLane(CrosschainLane):
|
|
|
77
83
|
fuses: LaneFuses,
|
|
78
84
|
) -> CcipLane:
|
|
79
85
|
hub_executor = CcipCrosschainExecutor(executor)
|
|
86
|
+
route = hub_executor.ccip_route(spoke_ctx.chain_id).call(hub_ctx)
|
|
87
|
+
_require_route_to_dispatcher(route, hub_executor.address, spoke_ctx.chain_id)
|
|
80
88
|
return cls(
|
|
81
89
|
hub_ctx=hub_ctx,
|
|
82
90
|
spoke_ctx=spoke_ctx,
|
|
83
91
|
executor=hub_executor,
|
|
84
92
|
dispatcher=CcipCrosschainDispatcher(executor),
|
|
85
93
|
fuses=fuses,
|
|
86
|
-
route=
|
|
94
|
+
route=route,
|
|
87
95
|
decimal_conversion_rate=hub_executor.decimal_conversion_rate().call(
|
|
88
96
|
hub_ctx
|
|
89
97
|
),
|
|
@@ -93,6 +101,11 @@ class CcipLane(CrosschainLane):
|
|
|
93
101
|
return self.executor.balance_staleness_max()
|
|
94
102
|
|
|
95
103
|
def default_send(self, *, token: bool) -> CcipSendParams:
|
|
104
|
+
if not self.route.enabled:
|
|
105
|
+
raise CrosschainDiscoveryError(
|
|
106
|
+
f"CCIP route of executor {self.executor.address} to chain "
|
|
107
|
+
f"{self.spoke_chain_id} is disabled"
|
|
108
|
+
)
|
|
96
109
|
return CcipSendParams.from_route(self.route, token=token)
|
|
97
110
|
|
|
98
111
|
def default_command_send(self) -> CcipSendParams:
|
|
@@ -156,3 +169,19 @@ def _to_lane(observation: CcipObservation, rate: int) -> LaneObservation:
|
|
|
156
169
|
command_config_epoch=observation.command_config_epoch,
|
|
157
170
|
tracked_position_set_hash=observation.tracked_position_set_hash,
|
|
158
171
|
)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def _require_route_to_dispatcher(
|
|
175
|
+
route: CcipRouteConfig, executor: ChecksumAddress, spoke: ChainId
|
|
176
|
+
) -> None:
|
|
177
|
+
"""The executor sends to the dispatcher at its own address on the spoke; an
|
|
178
|
+
unregistered route reads back as all zeros."""
|
|
179
|
+
if route.chain_selector == 0 or int(route.peer, 16) == 0:
|
|
180
|
+
raise CrosschainDiscoveryError(
|
|
181
|
+
f"CCIP executor {executor} has no route to chain {spoke}"
|
|
182
|
+
)
|
|
183
|
+
if route.peer != executor:
|
|
184
|
+
raise CrosschainDiscoveryError(
|
|
185
|
+
f"CCIP executor {executor} routes chain {spoke} to {route.peer}, "
|
|
186
|
+
"not to the dispatcher at its own address"
|
|
187
|
+
)
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"""Gates of the tests that need a live provider or network source.
|
|
2
|
+
|
|
3
|
+
Locally a missing or unreachable source skips the test. A run that names the
|
|
4
|
+
source in ``IPOR_FUSION_REQUIRE_LIVE`` (comma-separated: provider env var names
|
|
5
|
+
such as ``BASE_PROVIDER_URL``, or ``GITHUB`` for the Solidity mirrors) fails
|
|
6
|
+
instead, so a green CI run means the live checks ran rather than skipped.
|
|
7
|
+
Messages carry the exception type only: provider URLs embed API keys.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import os
|
|
13
|
+
from typing import NoReturn
|
|
14
|
+
|
|
15
|
+
import pytest
|
|
16
|
+
import requests
|
|
17
|
+
from web3 import Web3
|
|
18
|
+
from web3.exceptions import Web3Exception
|
|
19
|
+
|
|
20
|
+
from ipor_fusion import Web3Context
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def required() -> frozenset[str]:
|
|
24
|
+
return frozenset(
|
|
25
|
+
name.strip()
|
|
26
|
+
for name in os.environ.get("IPOR_FUSION_REQUIRE_LIVE", "").split(",")
|
|
27
|
+
if name.strip()
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def unavailable(source: str, reason: str) -> NoReturn:
|
|
32
|
+
"""Skip, or fail when this run requires ``source``."""
|
|
33
|
+
if source in required():
|
|
34
|
+
pytest.fail(f"{source} is required in this run: {reason}", pytrace=False)
|
|
35
|
+
pytest.skip(reason)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def provider_url(env_var: str) -> str:
|
|
39
|
+
url = os.environ.get(env_var)
|
|
40
|
+
if not url:
|
|
41
|
+
unavailable(env_var, f"{env_var} not set")
|
|
42
|
+
return url
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
# A provider that cannot be reached, refuses the key, or has pruned the state.
|
|
46
|
+
_INFRA_ERRORS = (Web3Exception, requests.RequestException, OSError)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def archive_context(env_var: str, block: int, contract: str) -> Web3Context:
|
|
50
|
+
"""A context pinned to ``block`` on the provider in ``env_var``, once it
|
|
51
|
+
has connected and served ``contract``'s code at that block (historical
|
|
52
|
+
state, not just the header); later SDK errors are test failures, not
|
|
53
|
+
infrastructure."""
|
|
54
|
+
url = provider_url(env_var)
|
|
55
|
+
# The failure is reported outside the except block: a chained traceback
|
|
56
|
+
# would print the request URL, which embeds the API key.
|
|
57
|
+
failure: str | None = None
|
|
58
|
+
try:
|
|
59
|
+
ctx = Web3Context.from_url(url)
|
|
60
|
+
code = ctx.web3.eth.get_code(Web3.to_checksum_address(contract), block)
|
|
61
|
+
except _INFRA_ERRORS as exc:
|
|
62
|
+
failure = type(exc).__name__
|
|
63
|
+
if failure is not None:
|
|
64
|
+
unavailable(env_var, f"{env_var} cannot serve state at {block}: {failure}")
|
|
65
|
+
if not code:
|
|
66
|
+
unavailable(env_var, f"{env_var} returned no code for {contract} at {block}")
|
|
67
|
+
return ctx.at_block(block)
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import importlib.util
|
|
2
|
-
import os
|
|
3
2
|
from collections.abc import Callable
|
|
4
3
|
from pathlib import Path
|
|
5
4
|
from types import ModuleType
|
|
6
5
|
from unittest.mock import patch
|
|
7
6
|
|
|
8
7
|
import pytest
|
|
8
|
+
from _live import provider_url, unavailable
|
|
9
9
|
from _multicall import SequentialMulticall
|
|
10
10
|
from dotenv import load_dotenv
|
|
11
11
|
from web3 import Web3
|
|
@@ -72,40 +72,38 @@ def pytest_collection_modifyitems(config, items):
|
|
|
72
72
|
|
|
73
73
|
|
|
74
74
|
def _connected_web3(env_var: str) -> Web3:
|
|
75
|
-
"""Build a Web3 client from `env_var`; skip the test if missing/unreachable
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
pytest.skip(f"{env_var} not set")
|
|
79
|
-
w3 = Web3(Web3.HTTPProvider(url))
|
|
75
|
+
"""Build a Web3 client from `env_var`; skip the test if missing/unreachable
|
|
76
|
+
(fail when the run requires it, see `_live`)."""
|
|
77
|
+
w3 = Web3(Web3.HTTPProvider(provider_url(env_var)))
|
|
80
78
|
if not w3.is_connected():
|
|
81
|
-
|
|
79
|
+
unavailable(env_var, f"cannot reach RPC at {env_var}")
|
|
82
80
|
return w3
|
|
83
81
|
|
|
84
82
|
|
|
85
|
-
def _ensure_simulate_v1(web3: Web3) -> None:
|
|
83
|
+
def _ensure_simulate_v1(web3: Web3, env_var: str) -> None:
|
|
86
84
|
"""Skip if the provider does not implement `eth_simulateV1`."""
|
|
87
85
|
if not is_simulate_v1_supported(web3):
|
|
88
|
-
|
|
86
|
+
unavailable(env_var, f"{env_var} does not support eth_simulateV1")
|
|
89
87
|
|
|
90
88
|
|
|
91
89
|
@pytest.fixture(scope="session")
|
|
92
90
|
def web3_eth() -> Web3:
|
|
93
91
|
w3 = _connected_web3("ETHEREUM_PROVIDER_URL")
|
|
94
|
-
_ensure_simulate_v1(w3)
|
|
92
|
+
_ensure_simulate_v1(w3, "ETHEREUM_PROVIDER_URL")
|
|
95
93
|
return w3
|
|
96
94
|
|
|
97
95
|
|
|
98
96
|
@pytest.fixture(scope="session")
|
|
99
97
|
def web3_base() -> Web3:
|
|
100
98
|
w3 = _connected_web3("BASE_PROVIDER_URL")
|
|
101
|
-
_ensure_simulate_v1(w3)
|
|
99
|
+
_ensure_simulate_v1(w3, "BASE_PROVIDER_URL")
|
|
102
100
|
return w3
|
|
103
101
|
|
|
104
102
|
|
|
105
103
|
@pytest.fixture(scope="session")
|
|
106
104
|
def web3_arb() -> Web3:
|
|
107
105
|
w3 = _connected_web3("ARBITRUM_PROVIDER_URL")
|
|
108
|
-
_ensure_simulate_v1(w3)
|
|
106
|
+
_ensure_simulate_v1(w3, "ARBITRUM_PROVIDER_URL")
|
|
109
107
|
return w3
|
|
110
108
|
|
|
111
109
|
|
|
@@ -113,5 +111,5 @@ def web3_arb() -> Web3:
|
|
|
113
111
|
def web3_hyperevm() -> Web3:
|
|
114
112
|
"""HyperEVM simulation fixture; general PR CI does not supply its RPC secret."""
|
|
115
113
|
w3 = _connected_web3("HYPEREVM_PROVIDER_URL")
|
|
116
|
-
_ensure_simulate_v1(w3)
|
|
114
|
+
_ensure_simulate_v1(w3, "HYPEREVM_PROVIDER_URL")
|
|
117
115
|
return w3
|
|
@@ -3,7 +3,7 @@ the per-transport fuses encode, normalizes reads, and finds its own parts."""
|
|
|
3
3
|
|
|
4
4
|
from __future__ import annotations
|
|
5
5
|
|
|
6
|
-
from dataclasses import astuple
|
|
6
|
+
from dataclasses import astuple, replace
|
|
7
7
|
from unittest.mock import MagicMock
|
|
8
8
|
|
|
9
9
|
import pytest
|
|
@@ -662,7 +662,8 @@ def _deployment_ctx() -> MagicMock:
|
|
|
662
662
|
probe_vault = PlasmaVault(VAULT)
|
|
663
663
|
probe_exec = StargateCrosschainExecutor(EXECUTOR)
|
|
664
664
|
route_raw = encode(
|
|
665
|
-
["(uint64,address,address,uint96,uint96,uint256,bool)"],
|
|
665
|
+
["(uint64,address,address,uint96,uint96,uint256,bool)"],
|
|
666
|
+
[replace(ROUTE, peer=CCIP_EXECUTOR).as_tuple()],
|
|
666
667
|
)
|
|
667
668
|
by_selector: dict[tuple[str, bytes], bytes] = {
|
|
668
669
|
(VAULT, _sel(probe_vault.get_market_substrates(MARKET))): encode(
|
|
@@ -845,7 +846,10 @@ class TestDeploymentDiscovery:
|
|
|
845
846
|
CCIP_EXECUTOR,
|
|
846
847
|
]
|
|
847
848
|
ccip = next(lane for lane in lanes if isinstance(lane, CcipLane))
|
|
848
|
-
assert
|
|
849
|
+
assert (
|
|
850
|
+
ccip.route == replace(ROUTE, peer=CCIP_EXECUTOR)
|
|
851
|
+
and ccip.fuses == CCIP_FUSES
|
|
852
|
+
)
|
|
849
853
|
assert ccip.decimal_conversion_rate == DECIMAL_CONVERSION_RATE
|
|
850
854
|
|
|
851
855
|
|
|
@@ -1036,6 +1040,57 @@ class TestContextOwnership:
|
|
|
1036
1040
|
assert observation.accounted_balance == 7
|
|
1037
1041
|
hub.call.assert_not_called()
|
|
1038
1042
|
|
|
1043
|
+
def _ccip_hub(self, route: CcipRouteConfig) -> MagicMock:
|
|
1044
|
+
return _ctx_answering(
|
|
1045
|
+
{
|
|
1046
|
+
selector("ccipRoute(uint256)"): encode(
|
|
1047
|
+
["(uint64,address,address,uint96,uint96,uint256,bool)"],
|
|
1048
|
+
[route.as_tuple()],
|
|
1049
|
+
),
|
|
1050
|
+
selector("DECIMAL_CONVERSION_RATE()"): encode(
|
|
1051
|
+
["uint256"], [DECIMAL_CONVERSION_RATE]
|
|
1052
|
+
),
|
|
1053
|
+
}
|
|
1054
|
+
)
|
|
1055
|
+
|
|
1056
|
+
def test_ccip_open_refuses_a_spoke_without_a_route(self):
|
|
1057
|
+
unregistered = CcipRouteConfig(
|
|
1058
|
+
chain_selector=0,
|
|
1059
|
+
peer=ZERO_ADDRESS, # type: ignore[arg-type]
|
|
1060
|
+
fee_token=ZERO_ADDRESS, # type: ignore[arg-type]
|
|
1061
|
+
message_gas_limit=0,
|
|
1062
|
+
token_gas_limit=0,
|
|
1063
|
+
max_fee=0,
|
|
1064
|
+
enabled=False,
|
|
1065
|
+
)
|
|
1066
|
+
with pytest.raises(CrosschainDiscoveryError, match="has no route to chain"):
|
|
1067
|
+
CcipLane.open(
|
|
1068
|
+
self._ccip_hub(unregistered),
|
|
1069
|
+
self._wrong_chain(SPOKE),
|
|
1070
|
+
executor=EXECUTOR,
|
|
1071
|
+
fuses=FUSES,
|
|
1072
|
+
)
|
|
1073
|
+
|
|
1074
|
+
def test_ccip_open_refuses_a_route_to_another_peer(self):
|
|
1075
|
+
foreign = replace(ROUTE, peer=REMOTE_VAULT)
|
|
1076
|
+
with pytest.raises(CrosschainDiscoveryError, match="not to the dispatcher"):
|
|
1077
|
+
CcipLane.open(
|
|
1078
|
+
self._ccip_hub(foreign),
|
|
1079
|
+
self._wrong_chain(SPOKE),
|
|
1080
|
+
executor=EXECUTOR,
|
|
1081
|
+
fuses=FUSES,
|
|
1082
|
+
)
|
|
1083
|
+
|
|
1084
|
+
def test_a_disabled_route_opens_but_refuses_default_send(self):
|
|
1085
|
+
lane = CcipLane.open(
|
|
1086
|
+
self._ccip_hub(replace(ROUTE, enabled=False)),
|
|
1087
|
+
self._wrong_chain(SPOKE),
|
|
1088
|
+
executor=EXECUTOR,
|
|
1089
|
+
fuses=FUSES,
|
|
1090
|
+
)
|
|
1091
|
+
with pytest.raises(CrosschainDiscoveryError, match="is disabled"):
|
|
1092
|
+
lane.default_send(token=True)
|
|
1093
|
+
|
|
1039
1094
|
def test_ccip_open_reads_the_route_on_the_hub(self):
|
|
1040
1095
|
route_raw = encode(
|
|
1041
1096
|
["(uint64,address,address,uint96,uint96,uint256,bool)"], [ROUTE.as_tuple()]
|
|
@@ -22,7 +22,8 @@ pytest_plugins = ["pytester"]
|
|
|
22
22
|
def test_readiness_requires_explicit_opt_in(pytester, args, passed, deselected):
|
|
23
23
|
pytester.makeconftest(Path(__file__).with_name("conftest.py").read_text())
|
|
24
24
|
pytester.makepyfile(
|
|
25
|
-
_multicall=Path(__file__).with_name("_multicall.py").read_text()
|
|
25
|
+
_multicall=Path(__file__).with_name("_multicall.py").read_text(),
|
|
26
|
+
_live=Path(__file__).with_name("_live.py").read_text(),
|
|
26
27
|
)
|
|
27
28
|
pytester.makeini("[pytest]\nmarkers =\n sdk\n cli\n mcp\n readiness\n")
|
|
28
29
|
pytester.makepyfile(
|
|
@@ -14,6 +14,7 @@ from functools import cache
|
|
|
14
14
|
|
|
15
15
|
import pytest
|
|
16
16
|
import requests
|
|
17
|
+
from _live import unavailable
|
|
17
18
|
from eth_utils import function_signature_to_4byte_selector
|
|
18
19
|
|
|
19
20
|
from ipor_fusion.config.roles import Roles
|
|
@@ -58,11 +59,11 @@ def _solidity_source(path: str) -> str:
|
|
|
58
59
|
try:
|
|
59
60
|
resp = requests.get(url, timeout=15)
|
|
60
61
|
except requests.RequestException as exc:
|
|
61
|
-
|
|
62
|
+
unavailable("GITHUB", f"cannot fetch {path} from GitHub: {type(exc).__name__}")
|
|
62
63
|
if resp.status_code == 404:
|
|
63
64
|
pytest.fail(f"{url} not found — wrong pinned ref or moved file")
|
|
64
65
|
if resp.status_code != 200:
|
|
65
|
-
|
|
66
|
+
unavailable("GITHUB", f"GitHub returned HTTP {resp.status_code} for {path}")
|
|
66
67
|
return resp.text
|
|
67
68
|
|
|
68
69
|
|
|
@@ -17,11 +17,10 @@ historical block requires an archive node.
|
|
|
17
17
|
|
|
18
18
|
from __future__ import annotations
|
|
19
19
|
|
|
20
|
-
import os
|
|
21
20
|
from collections import Counter
|
|
22
21
|
from unittest.mock import patch
|
|
23
22
|
|
|
24
|
-
import
|
|
23
|
+
from _live import archive_context, provider_url
|
|
25
24
|
from web3 import Web3
|
|
26
25
|
from web3.providers.rpc import HTTPProvider
|
|
27
26
|
|
|
@@ -35,15 +34,8 @@ EXPECTED_IDLE_USDC = 10 * 10**6 # 10 USDC (6 decimals) sitting idle on the vaul
|
|
|
35
34
|
|
|
36
35
|
|
|
37
36
|
def test_idle_underlying_reconciles_on_real_base_vault():
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
pytest.skip("BASE_PROVIDER_URL not set")
|
|
41
|
-
|
|
42
|
-
try: # pylint: disable=broad-except
|
|
43
|
-
validation = validate_vault(Web3Context.from_url(url), VAULT, block=BLOCK)
|
|
44
|
-
except Exception as exc: # pylint: disable=broad-except
|
|
45
|
-
# Infra problem (provider unreachable or non-archive) — skip, don't fail.
|
|
46
|
-
pytest.skip(f"BASE on-chain read failed (provider missing/non-archive?): {exc}")
|
|
37
|
+
ctx = archive_context("BASE_PROVIDER_URL", BLOCK, VAULT)
|
|
38
|
+
validation = validate_vault(ctx, VAULT, block=BLOCK)
|
|
47
39
|
|
|
48
40
|
# Sanity: confirm we are reading the expected vault/chain state at this block.
|
|
49
41
|
recon = validation.reconciliation
|
|
@@ -64,9 +56,8 @@ MAX_ETH_CALLS = 20
|
|
|
64
56
|
|
|
65
57
|
|
|
66
58
|
def test_fetch_stays_within_round_trip_budget():
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
pytest.skip("BASE_PROVIDER_URL not set")
|
|
59
|
+
archive_context("BASE_PROVIDER_URL", BLOCK, VAULT) # skips without an archive
|
|
60
|
+
url = provider_url("BASE_PROVIDER_URL")
|
|
70
61
|
|
|
71
62
|
methods: Counter[str] = Counter()
|
|
72
63
|
make_request = HTTPProvider.make_request
|
|
@@ -75,15 +66,12 @@ def test_fetch_stays_within_round_trip_budget():
|
|
|
75
66
|
methods[method] += 1
|
|
76
67
|
return make_request(provider, method, params)
|
|
77
68
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
data = fetch_vault_data(ctx, VAULT, block=BLOCK)
|
|
85
|
-
except Exception as exc: # pylint: disable=broad-except
|
|
86
|
-
pytest.skip(f"BASE on-chain read failed (provider missing/non-archive?): {exc}")
|
|
69
|
+
# web3 binds make_request when the provider is built, so the context
|
|
70
|
+
# must be created under the patch for any request to be counted.
|
|
71
|
+
with patch.object(HTTPProvider, "make_request", counting):
|
|
72
|
+
ctx = Web3Context.from_url(url)
|
|
73
|
+
methods.clear()
|
|
74
|
+
data = fetch_vault_data(ctx, VAULT, block=BLOCK)
|
|
87
75
|
|
|
88
76
|
assert data.total_assets == EXPECTED_IDLE_USDC
|
|
89
77
|
assert methods["eth_call"] > 0, "requests were not counted"
|
|
@@ -16,12 +16,10 @@ code under test runs unguarded, so a regression fails instead of skipping.
|
|
|
16
16
|
|
|
17
17
|
from __future__ import annotations
|
|
18
18
|
|
|
19
|
-
import
|
|
20
|
-
|
|
21
|
-
import pytest
|
|
19
|
+
from _live import archive_context
|
|
22
20
|
from web3 import Web3
|
|
23
21
|
|
|
24
|
-
from ipor_fusion import PlasmaVault
|
|
22
|
+
from ipor_fusion import PlasmaVault
|
|
25
23
|
from ipor_fusion.vault_data import fetch_vault_data
|
|
26
24
|
|
|
27
25
|
VAULT = Web3.to_checksum_address("0x45aa96f0b3188D47a1DaFdbefCE1db6B37f58216")
|
|
@@ -30,22 +28,9 @@ CHAIN_ID = 8453
|
|
|
30
28
|
BLOCK = 50788912
|
|
31
29
|
|
|
32
30
|
|
|
33
|
-
def _connect(url: str) -> tuple[Web3Context, PlasmaVault]:
|
|
34
|
-
"""Skip on infra problems only (unreachable or non-archive provider)."""
|
|
35
|
-
try:
|
|
36
|
-
ctx = Web3Context.from_url(url).at_block(BLOCK)
|
|
37
|
-
pv = PlasmaVault(VAULT)
|
|
38
|
-
pv.underlying_asset_address().call(ctx) # state read at BLOCK needs archive
|
|
39
|
-
except Exception as exc: # noqa: BLE001
|
|
40
|
-
pytest.skip(f"BASE on-chain read failed (provider missing/non-archive?): {exc}")
|
|
41
|
-
return ctx, pv
|
|
42
|
-
|
|
43
|
-
|
|
44
31
|
def test_vault_without_withdraw_manager_fetches_on_real_base_vault():
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
pytest.skip("BASE_PROVIDER_URL not set")
|
|
48
|
-
ctx, pv = _connect(url)
|
|
32
|
+
ctx = archive_context("BASE_PROVIDER_URL", BLOCK, VAULT)
|
|
33
|
+
pv = PlasmaVault(VAULT)
|
|
49
34
|
|
|
50
35
|
withdraw_manager = pv.read_withdraw_manager_address(ctx)
|
|
51
36
|
data = fetch_vault_data(ctx, pv.address, block=BLOCK)
|