ipor-fusion 4.0.0.dev249__tar.gz → 4.0.0.dev255__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.dev249 → ipor_fusion-4.0.0.dev255}/PKG-INFO +2 -2
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/README.md +1 -1
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/pyproject.toml +1 -1
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/__init__.py +2 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/core/__init__.py +2 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/core/access.py +57 -1
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/readers/aave_v3.py +75 -19
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/readers/morpho.py +66 -14
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/vault_data.py +7 -2
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/vault_positions.py +30 -33
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_access_manager.py +46 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_cli_helpers.py +38 -27
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_public_api.py +2 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_reader_encoding.py +73 -1
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/.claude-plugin/marketplace.json +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/.claude-plugin/plugin.json +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/.env.example +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/.gemini/settings.json +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/.git-blame-ignore-revs +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/.github/workflows/cd.yml +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/.github/workflows/ci.yml +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/.github/workflows/commit-lint.yml +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/.github/workflows/notify-slack.yml +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/.github/workflows/python-build.yml +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/.github/workflows/python-publish.yml +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/.github/workflows/release.yml +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/.github/workflows/report-slack.yml +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/.gitignore +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/.mcp.json +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/.pre-commit-config.yaml +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/.python-version +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/AGENTS.md +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/CHANGELOG.md +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/CLAUDE.md +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/CODEOWNERS +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/LICENSE +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/MIGRATION_4.md +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/build.sh +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/examples/CONTRIBUTING.md +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/examples/README.md +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/examples/advanced_euler_v2_credit_market_base.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/examples/advanced_euler_v2_looping_arbitrum.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/examples/composed_hypercore_flow_arbitrum_hyperevm.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/examples/crosschain_ccip_usdc_arbitrum_hyperevm.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/examples/external_state_margin_leg_base.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/examples/simple_aave_v3_supply_base.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/pytest.ini +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/scripts/commit-check.sh +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/scripts/crosschain_readiness.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/skills/ipor-deploy-vault/SKILL.md +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/about.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/cache.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/chains.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/cli/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/cli/changelog_cmd.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/cli/config_cmd.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/cli/config_store.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/cli/main.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/cli/market_cmd.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/cli/options.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/cli/oracle_mapping_view.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/cli/vault_cmd.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/cli/vault_health.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/cli/vault_info_view.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/cli/vault_markets_view.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/cli/vault_rendering.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/config/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/config/roles.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/core/address.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/core/context.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/core/contract.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/core/erc20.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/core/external_state_executor.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/core/fee_manager.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/core/fuse.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/core/fuse_action.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/core/fusion_factory.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/core/logs.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/core/multicall.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/core/oracle.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/core/plasma_vault.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/core/rewards_manager.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/core/simulation.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/core/simulation_result.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/core/withdraw_manager.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/crosschain/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/crosschain/ccip/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/crosschain/ccip/chainlink.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/crosschain/ccip/codec.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/crosschain/ccip/contracts.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/crosschain/ccip/events.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/crosschain/ccip/indexer.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/crosschain/ccip/lane.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/crosschain/ccip/quote.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/crosschain/ccip/transport.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/crosschain/contracts.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/crosschain/discovery.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/crosschain/errors.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/crosschain/fuses/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/crosschain/fuses/base.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/crosschain/fuses/ccip.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/crosschain/fuses/stargate.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/crosschain/lane.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/crosschain/logs.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/crosschain/messages.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/crosschain/simulation.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/crosschain/stargate/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/crosschain/stargate/contracts.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/crosschain/stargate/lane.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/crosschain/stargate/layerzero.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/crosschain/stargate/transport.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/crosschain/transport.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/dependency_graph.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/entry_cli.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/entry_mcp.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/errors.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/explorer.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/field_docs.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/fuses/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/fuses/aave_v3.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/fuses/async_action.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/fuses/base.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/fuses/compound_v3.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/fuses/erc4626.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/fuses/euler_v2.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/fuses/events.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/fuses/external_state.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/fuses/external_state_event_decoding.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/fuses/fluid_instadapp.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/fuses/gearbox_v3.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/fuses/hypercore.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/fuses/hypercore_event_decoding.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/fuses/merkl.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/fuses/morpho.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/fuses/ramses_v2.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/fuses/uniswap_v3.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/fuses/universal.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/guide/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/guide/architecture.md +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/guide/glossary.md +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/guide/invariants.md +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/guide/quickstart.md +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/hypercore/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/market_ids.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/market_info.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/mcp/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/mcp/guide.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/mcp/models.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/mcp/server.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/morpho_api.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/py.typed +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/readers/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/readers/aave_v4.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/readers/compound_v3.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/readers/hypercore.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/readers/hypercore_outcome.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/readers/lending_health.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/readers/oracle_mapping.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/readers/oracle_mapping_nodes.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/readers/oracle_mapping_reader.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/readers/position_manager.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/readers/ramses_v2.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/readers/uniswap_v3.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/readers/unpriceable.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/substrates.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/types.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/validation.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/vault_erc20.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/vault_info.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/vault_managers.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/src/ipor_fusion/vault_reads.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/UniswapV3UniversalRouter.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/_crosschain.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/_crosschain_lifecycle.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/_crosschain_recovery.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/_euler_v2.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/_event_args.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/_fake_rpc.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/_foundry.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/_golden.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/_guide.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/_hypercore_model.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/_hypercore_shadow.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/_live.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/_multicall.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/_oracle_mapping_fake.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/_simulate.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/_validation.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/addresses.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/conftest.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/constants.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/fixtures/hypercore_hip3_flow.json +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/fixtures/hypercore_market55.json +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/fixtures/market_golden/meta_morpho_v1.json +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/fixtures/market_golden/meta_morpho_v1.mcp.json +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/fixtures/market_golden/meta_morpho_v2.json +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/fixtures/market_golden/meta_morpho_v2.mcp.json +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/fixtures/market_golden/morpho_api/market.json +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/fixtures/market_golden/morpho_api/vault_v1.json +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/fixtures/market_golden/morpho_api/vault_v2.json +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/fixtures/market_golden/morpho_api/vault_v2_not_found.json +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/fixtures/market_golden/morpho_blue.json +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/fixtures/market_golden/morpho_blue.mcp.json +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/fixtures/market_golden/morpho_blue_api_error.json +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/fixtures/market_golden/morpho_blue_api_error.mcp.json +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/fixtures/market_golden/morpho_blue_no_api.json +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/fixtures/market_golden/morpho_blue_no_api.mcp.json +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/fixtures/oracle_mapping_golden/mapping.json +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/fixtures/oracle_mapping_golden/mapping.mcp.json +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/fixtures/simulate_v1_request.json +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/fixtures/vault_info_golden/clean.json +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/fixtures/vault_info_golden/clean.mcp.json +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/fixtures/vault_info_golden/clean.txt +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/fixtures/vault_info_golden/deployment_error.json +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/fixtures/vault_info_golden/deployment_error.mcp.json +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/fixtures/vault_info_golden/deployment_error.txt +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/fixtures/vault_info_golden/fetched.cli.json +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/fixtures/vault_info_golden/fetched.json +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/fixtures/vault_info_golden/fetched.mcp.json +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/fixtures/vault_info_golden/full.json +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/fixtures/vault_info_golden/full.mcp.json +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/fixtures/vault_info_golden/full.txt +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/fixtures/vault_info_golden/rich.json +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/fixtures/vault_info_golden/rich.mcp.json +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/fixtures/vault_info_golden/rich.txt +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_about.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_cache.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_chains.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_cli_changelog.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_cli_commands.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_cli_config_store.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_cli_hypercore.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_cli_lending_health.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_cli_market_golden.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_cli_market_morpho_blue.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_cli_mcp_parity.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_cli_oracle_mapping.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_cli_oracle_mapping_golden.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_cli_pending_requests_scan.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_cli_role_accounts.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_cli_vault_info_golden.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_cli_vault_predates_getters.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_cli_vault_validate.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_contract_call.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_contract_encoder.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_crosschain_ccip_events.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_crosschain_ccip_indexer.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_crosschain_ccip_quote.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_crosschain_chainlink.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_crosschain_contracts.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_crosschain_encoding.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_crosschain_errors.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_crosschain_lane.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_crosschain_layering.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_crosschain_readiness.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_crosschain_readiness_report.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_crosschain_transport.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_docs_typing.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_erc20.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_error_hierarchy.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_examples_composed_hypercore.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_examples_crosschain.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_examples_euler_looping_arbitrum.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_examples_vaults.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_explorer.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_external_state_events.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_external_state_executor.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_fee_manager.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_fuse_encoding.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_fusion_instance.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_guide.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_hypercore_deployment.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_hypercore_events.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_hypercore_flow.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_hypercore_live.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_hypercore_outcome.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_hypercore_readers.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_hypercore_simulation.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_logs.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_mcp_hypercore.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_mcp_models.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_mcp_server.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_mcp_vault_validate.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_multicall.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_oracle.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_oracle_mapping.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_plasma_vault.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_public_type_reachability.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_readiness_selection.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_revert_decoding.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_rewards_manager.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_roles.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_sdk_layering.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_simulate_borrow_aave_v3_mainnet.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_simulate_claim_merkl_wrapper_base.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_simulate_claim_morpho_rewards.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_simulate_crosschain_canary.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_simulate_crosschain_hypercore.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_simulate_crosschain_hyperevm.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_simulate_crosschain_lifecycle.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_simulate_euler_v2_batch_base.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_simulate_euler_v2_credit_market_base.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_simulate_euler_v2_swap_deploy_base.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_simulate_euler_v2_swap_reconfigure_base.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_simulate_euler_v2_swap_registry_base.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_simulate_examples_arbitrum.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_simulate_examples_base.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_simulate_examples_crosschain.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_simulate_external_state_executor_base.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_simulate_fusion_factory_clone_base.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_simulate_looping_aave_v3_base.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_simulate_looping_morpho_blue_base.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_simulate_morpho_blue.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_simulate_pilot_v3.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_simulate_pilot_v4_uniswap.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_simulate_pilot_v5_ramses.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_simulate_pilot_v5_scheduled.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_simulate_plasma_vault_setters_base.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_simulate_readers_integration.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_simulate_release_funds.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_simulate_swap_on_base.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_simulate_vault_from_scratch_base.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_simulation_errors.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_simulation_observation.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_solidity_compiler.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_solidity_mirrors.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_substrates.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_types_and_events.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_unpriceable_tokens.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_validation.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_vault_data_block.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_vault_dep_graph.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_vault_info_fetched_golden.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_vault_info_onchain_base.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_vault_info_onchain_base_no_withdraw_manager.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_vault_reconciliation_onchain_base.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_vault_simulator_blocks.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_web3_context.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/test_withdraw_manager.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/typed_client/consumer.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/tests/typed_client/rejected.py +0 -0
- {ipor_fusion-4.0.0.dev249 → ipor_fusion-4.0.0.dev255}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: ipor-fusion
|
|
3
|
-
Version: 4.0.0.
|
|
3
|
+
Version: 4.0.0.dev255
|
|
4
4
|
Summary: The IPOR Fusion SDK for Python
|
|
5
5
|
Project-URL: homepage, https://ipor.io
|
|
6
6
|
Project-URL: repository, https://github.com/IPOR-Labs/ipor-fusion.py
|
|
@@ -325,7 +325,7 @@ Reverts on the deploy-and-configure path, keyed by selector so a failed transact
|
|
|
325
325
|
| `0x8745fbfd` | `DaoFeePackagesArrayEmpty()` | `clone()` was sent to `IporFusionFactoryImpl` | Send it to `IporFusionFactoryProxy` for that chain |
|
|
326
326
|
| `0x9996b315` | `AddressEmptyCode(address)` | `execute()` touched a market with no balance fuse | `add_balance_fuse(market_id, balance_fuse)` before the first `execute` on that market |
|
|
327
327
|
| `0x4bf4de4e` | `HandlerNotFound()` | A protocol called back into the vault during `execute()` (a flash loan) and no handler is registered for its address and selector | `update_callback_handler(handler, sender, selector)` before the first `execute` that triggers the callback |
|
|
328
|
-
| `0x068ca9d8` | `AccessManagedUnauthorized(address)` | The caller lacks the role the function requires: `FUSE_MANAGER` for `add_fuses`, `grant_market_substrates`, `add_balance_fuse`, `update_callback_handler`; `ALPHA` for `execute`; `WHITELIST` for `deposit` and `mint` on a private vault | `AccessManager.grant_role(role, account, Period(0))` from the role's admin (`OWNER` grants `ATOMIST`, `ATOMIST` grants the rest); for a reverting `deposit`, whitelist the depositor or convert the vault to public |
|
|
328
|
+
| `0x068ca9d8` | `AccessManagedUnauthorized(address)` | The caller lacks the role the function requires: `FUSE_MANAGER` for `add_fuses`, `grant_market_substrates`, `add_balance_fuse`, `update_callback_handler`; `ALPHA` for `execute`; `WHITELIST` for `deposit` and `mint` on a private vault | Check first with `AccessManager.can_call(caller, target, selector).call(ctx)` (a closed target or a missing role answers `immediate=False, delay=0`; a role with an execution delay answers its `delay`). `AccessManager.grant_role(role, account, Period(0))` from the role's admin (`OWNER` grants `ATOMIST`, `ATOMIST` grants the rest); for a reverting `deposit`, whitelist the depositor or convert the vault to public |
|
|
329
329
|
| `ValueError: Private key required for sending transactions` | SDK, before any transaction | `.send(ctx)` on a `Web3Context` without a key | `Web3Context(w3, chain_id, signer=..., private_key=...)` or `Web3Context.from_url(url, private_key=...)` |
|
|
330
330
|
|
|
331
331
|
`clone()` grants the owner only `Roles.OWNER_ROLE` (1). OWNER grants `Roles.ATOMIST_ROLE` (100), which administers `Roles.FUSE_MANAGER_ROLE` (300, configuration), `Roles.ALPHA_ROLE` (200, `execute`) and `Roles.WHITELIST_ROLE` (800, `deposit` on a private vault).
|
|
@@ -277,7 +277,7 @@ Reverts on the deploy-and-configure path, keyed by selector so a failed transact
|
|
|
277
277
|
| `0x8745fbfd` | `DaoFeePackagesArrayEmpty()` | `clone()` was sent to `IporFusionFactoryImpl` | Send it to `IporFusionFactoryProxy` for that chain |
|
|
278
278
|
| `0x9996b315` | `AddressEmptyCode(address)` | `execute()` touched a market with no balance fuse | `add_balance_fuse(market_id, balance_fuse)` before the first `execute` on that market |
|
|
279
279
|
| `0x4bf4de4e` | `HandlerNotFound()` | A protocol called back into the vault during `execute()` (a flash loan) and no handler is registered for its address and selector | `update_callback_handler(handler, sender, selector)` before the first `execute` that triggers the callback |
|
|
280
|
-
| `0x068ca9d8` | `AccessManagedUnauthorized(address)` | The caller lacks the role the function requires: `FUSE_MANAGER` for `add_fuses`, `grant_market_substrates`, `add_balance_fuse`, `update_callback_handler`; `ALPHA` for `execute`; `WHITELIST` for `deposit` and `mint` on a private vault | `AccessManager.grant_role(role, account, Period(0))` from the role's admin (`OWNER` grants `ATOMIST`, `ATOMIST` grants the rest); for a reverting `deposit`, whitelist the depositor or convert the vault to public |
|
|
280
|
+
| `0x068ca9d8` | `AccessManagedUnauthorized(address)` | The caller lacks the role the function requires: `FUSE_MANAGER` for `add_fuses`, `grant_market_substrates`, `add_balance_fuse`, `update_callback_handler`; `ALPHA` for `execute`; `WHITELIST` for `deposit` and `mint` on a private vault | Check first with `AccessManager.can_call(caller, target, selector).call(ctx)` (a closed target or a missing role answers `immediate=False, delay=0`; a role with an execution delay answers its `delay`). `AccessManager.grant_role(role, account, Period(0))` from the role's admin (`OWNER` grants `ATOMIST`, `ATOMIST` grants the rest); for a reverting `deposit`, whitelist the depositor or convert the vault to public |
|
|
281
281
|
| `ValueError: Private key required for sending transactions` | SDK, before any transaction | `.send(ctx)` on a `Web3Context` without a key | `Web3Context(w3, chain_id, signer=..., private_key=...)` or `Web3Context.from_url(url, private_key=...)` |
|
|
282
282
|
|
|
283
283
|
`clone()` grants the owner only `Roles.OWNER_ROLE` (1). OWNER grants `Roles.ATOMIST_ROLE` (100), which administers `Roles.FUSE_MANAGER_ROLE` (300, configuration), `Roles.ALPHA_ROLE` (200, `execute`) and `Roles.WHITELIST_ROLE` (800, `deposit` on a private vault).
|
|
@@ -26,6 +26,7 @@ from ipor_fusion.chains import (
|
|
|
26
26
|
from ipor_fusion.config.roles import Roles
|
|
27
27
|
from ipor_fusion.core.access import (
|
|
28
28
|
AccessManager,
|
|
29
|
+
CallPermission,
|
|
29
30
|
RoleAccount,
|
|
30
31
|
RoleAccountJson,
|
|
31
32
|
RoleAccountsJson,
|
|
@@ -464,6 +465,7 @@ __all__ = [
|
|
|
464
465
|
"AccessManager",
|
|
465
466
|
"RoleAccount",
|
|
466
467
|
"RoleAccountJson",
|
|
468
|
+
"CallPermission",
|
|
467
469
|
"RoleStatus",
|
|
468
470
|
"resolve_access_manager",
|
|
469
471
|
"role_account_sort_key",
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from ipor_fusion.core.access import (
|
|
2
2
|
AccessManager,
|
|
3
|
+
CallPermission,
|
|
3
4
|
RoleAccount,
|
|
4
5
|
RoleAccountJson,
|
|
5
6
|
RoleAccountsJson,
|
|
@@ -92,6 +93,7 @@ __all__ = [
|
|
|
92
93
|
"AccessManager",
|
|
93
94
|
"RoleAccount",
|
|
94
95
|
"RoleAccountJson",
|
|
96
|
+
"CallPermission",
|
|
95
97
|
"RoleStatus",
|
|
96
98
|
"RewardsManager",
|
|
97
99
|
"VestingData",
|
|
@@ -8,7 +8,7 @@ from eth_typing import ChecksumAddress
|
|
|
8
8
|
from hexbytes import HexBytes
|
|
9
9
|
from web3 import Web3
|
|
10
10
|
from web3.exceptions import ContractLogicError
|
|
11
|
-
from web3.types import LogReceipt
|
|
11
|
+
from web3.types import LogReceipt, Timestamp
|
|
12
12
|
|
|
13
13
|
from ipor_fusion.config.roles import Roles
|
|
14
14
|
from ipor_fusion.core.context import Web3Context
|
|
@@ -48,6 +48,30 @@ class RoleStatus:
|
|
|
48
48
|
execution_delay: Period
|
|
49
49
|
|
|
50
50
|
|
|
51
|
+
@dataclass(frozen=True, slots=True)
|
|
52
|
+
class CallPermission:
|
|
53
|
+
"""`AccessManager.canCall`: whether ``caller`` may call ``selector`` on ``target``.
|
|
54
|
+
|
|
55
|
+
``immediate`` is a call allowed now; a member with an execution delay gets
|
|
56
|
+
``immediate=False`` and that ``delay`` (it must schedule the call first). A closed
|
|
57
|
+
target or a non-member answers ``(False, 0)``. This is the role check only: the
|
|
58
|
+
IPOR manager's redemption lock and the target's own logic can still revert.
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
immediate: bool
|
|
62
|
+
delay: Period
|
|
63
|
+
|
|
64
|
+
@property
|
|
65
|
+
def allowed(self) -> bool:
|
|
66
|
+
"""Allowed now or after scheduling."""
|
|
67
|
+
return self.immediate or self.delay > 0
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _call_permission_decoder(values: tuple[bool, int]) -> CallPermission:
|
|
71
|
+
immediate, delay = values
|
|
72
|
+
return CallPermission(immediate=immediate, delay=Period(delay))
|
|
73
|
+
|
|
74
|
+
|
|
51
75
|
@dataclass(slots=True)
|
|
52
76
|
class RoleAccount:
|
|
53
77
|
"""Account-role membership record returned by AccessManager queries."""
|
|
@@ -159,6 +183,38 @@ class AccessManager(ContractWrapper):
|
|
|
159
183
|
decoder=_role_status_decoder,
|
|
160
184
|
)
|
|
161
185
|
|
|
186
|
+
def can_call(
|
|
187
|
+
self, caller: ChecksumAddress, target: ChecksumAddress, selector: bytes
|
|
188
|
+
) -> Call[CallPermission]:
|
|
189
|
+
"""`canCall(caller, target, selector)`: the closed-target check, the
|
|
190
|
+
function's role and the caller's membership and delay in one read."""
|
|
191
|
+
return self._view(
|
|
192
|
+
"canCall(address,address,bytes4)",
|
|
193
|
+
caller,
|
|
194
|
+
target,
|
|
195
|
+
selector,
|
|
196
|
+
output_types=["bool", "uint32"],
|
|
197
|
+
decoder=_call_permission_decoder,
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
def is_target_closed(self, target: ChecksumAddress) -> Call[bool]:
|
|
201
|
+
"""Whether the admin closed ``target``: every restricted call to it fails."""
|
|
202
|
+
return self._view(
|
|
203
|
+
"isTargetClosed(address)", target, output_types=["bool"], decoder=bool
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
def get_account_lock_time(self, account: ChecksumAddress) -> Call[Timestamp]:
|
|
207
|
+
"""Unix time until which ``account``, as the caller, cannot withdraw, redeem
|
|
208
|
+
or transfer shares (IPOR redemption delay). A deposit, mint or
|
|
209
|
+
``depositWithPermit`` sets it for its caller, not for the share receiver;
|
|
210
|
+
a time ``<= block.timestamp`` is unlocked."""
|
|
211
|
+
return self._view(
|
|
212
|
+
"getAccountLockTime(address)",
|
|
213
|
+
account,
|
|
214
|
+
output_types=["uint256"],
|
|
215
|
+
decoder=Timestamp,
|
|
216
|
+
)
|
|
217
|
+
|
|
162
218
|
# ── Composite reads on ``ctx``: event replay + batched hasRole reads ───
|
|
163
219
|
|
|
164
220
|
def read_owner(self, ctx: Web3Context) -> ChecksumAddress:
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
from collections.abc import Sequence
|
|
1
2
|
from dataclasses import dataclass
|
|
2
3
|
|
|
3
4
|
from eth_typing import ChecksumAddress
|
|
@@ -174,26 +175,81 @@ class AaveV3Reader(ContractWrapper):
|
|
|
174
175
|
zero when `asset` is not listed on this Pool.
|
|
175
176
|
"""
|
|
176
177
|
tokens = self.reserve_tokens(asset).call(ctx)
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
tokens.stable_debt_token,
|
|
178
|
+
listed = _listed_tokens(tokens)
|
|
179
|
+
balances = Multicall3(ctx).aggregate(
|
|
180
|
+
[ERC20(token).balance_of(user) for token in listed]
|
|
181
181
|
)
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
Multicall3(ctx).aggregate(
|
|
185
|
-
[ERC20(token).balance_of(user) for token in listed]
|
|
186
|
-
)
|
|
182
|
+
return _position_breakdown(
|
|
183
|
+
asset, tokens, dict(zip(listed, balances, strict=True))
|
|
187
184
|
)
|
|
188
|
-
|
|
189
|
-
|
|
185
|
+
|
|
186
|
+
def read_position_breakdowns(
|
|
187
|
+
self,
|
|
188
|
+
ctx: Web3Context,
|
|
189
|
+
assets: Sequence[ChecksumAddress],
|
|
190
|
+
user: ChecksumAddress,
|
|
191
|
+
) -> list[AaveV3PositionBreakdown | None]:
|
|
192
|
+
""":meth:`read_position_breakdown` for every asset in two Multicall3 batches
|
|
193
|
+
(every reserve's tokens, then every balance), each one round trip per
|
|
194
|
+
batch size, or one read per call without Multicall3; in ``assets`` order.
|
|
195
|
+
|
|
196
|
+
``None`` for an asset whose reserve data or a balance reverts, returns no
|
|
197
|
+
data or cannot be decoded; a transport error raises.
|
|
198
|
+
"""
|
|
199
|
+
multicall = Multicall3(ctx)
|
|
200
|
+
reserves = multicall.try_aggregate([self.reserve_tokens(a) for a in assets])
|
|
201
|
+
listed = {
|
|
202
|
+
token
|
|
203
|
+
for tokens in reserves
|
|
204
|
+
if tokens is not None
|
|
205
|
+
for token in _listed_tokens(tokens)
|
|
206
|
+
}
|
|
207
|
+
ordered = sorted(listed)
|
|
208
|
+
read = multicall.try_aggregate(
|
|
209
|
+
[ERC20(token).balance_of(user) for token in ordered]
|
|
190
210
|
)
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
211
|
+
balances = {
|
|
212
|
+
token: balance
|
|
213
|
+
for token, balance in zip(ordered, read, strict=True)
|
|
214
|
+
if balance is not None
|
|
215
|
+
}
|
|
216
|
+
return [
|
|
217
|
+
_position_breakdown(asset, tokens, balances)
|
|
218
|
+
if tokens is not None
|
|
219
|
+
and all(token in balances for token in _listed_tokens(tokens))
|
|
220
|
+
else None
|
|
221
|
+
for asset, tokens in zip(assets, reserves, strict=True)
|
|
222
|
+
]
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def _listed_tokens(tokens: AaveV3ReserveTokens) -> list[ChecksumAddress]:
|
|
226
|
+
return [
|
|
227
|
+
token
|
|
228
|
+
for token in (
|
|
229
|
+
tokens.a_token,
|
|
230
|
+
tokens.variable_debt_token,
|
|
231
|
+
tokens.stable_debt_token,
|
|
199
232
|
)
|
|
233
|
+
if token.lower() != ZERO_ADDRESS
|
|
234
|
+
]
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def _position_breakdown(
|
|
238
|
+
asset: ChecksumAddress,
|
|
239
|
+
tokens: AaveV3ReserveTokens,
|
|
240
|
+
balances: dict[ChecksumAddress, Amount],
|
|
241
|
+
) -> AaveV3PositionBreakdown:
|
|
242
|
+
"""A zero token address counts as a zero balance."""
|
|
243
|
+
|
|
244
|
+
def balance(token: ChecksumAddress) -> Amount:
|
|
245
|
+
return Amount(0) if token.lower() == ZERO_ADDRESS else balances[token]
|
|
246
|
+
|
|
247
|
+
return AaveV3PositionBreakdown(
|
|
248
|
+
asset=asset,
|
|
249
|
+
a_token=tokens.a_token,
|
|
250
|
+
variable_debt_token=tokens.variable_debt_token,
|
|
251
|
+
stable_debt_token=tokens.stable_debt_token,
|
|
252
|
+
supply=balance(tokens.a_token),
|
|
253
|
+
variable_debt=balance(tokens.variable_debt_token),
|
|
254
|
+
stable_debt=balance(tokens.stable_debt_token),
|
|
255
|
+
)
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import math
|
|
2
|
+
from collections.abc import Sequence
|
|
2
3
|
from dataclasses import dataclass
|
|
3
4
|
|
|
4
5
|
from eth_abi import encode
|
|
@@ -225,22 +226,45 @@ class MorphoReader(ContractWrapper):
|
|
|
225
226
|
market_read = batch.add(self.market(market_id))
|
|
226
227
|
params_read = batch.add(self.market_params(market_id))
|
|
227
228
|
batch.run()
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
pos.borrow_shares, market.total_borrow_assets, market.total_borrow_shares
|
|
231
|
-
)
|
|
232
|
-
supply_assets = to_assets_down(
|
|
233
|
-
pos.supply_shares, market.total_supply_assets, market.total_supply_shares
|
|
234
|
-
)
|
|
235
|
-
return MorphoPositionBreakdown(
|
|
236
|
-
market_id=market_id,
|
|
237
|
-
loan_token=params.loan_token,
|
|
238
|
-
collateral_token=params.collateral_token,
|
|
239
|
-
collateral=pos.collateral,
|
|
240
|
-
borrow_assets=Amount(borrow_assets),
|
|
241
|
-
supply_assets=Amount(supply_assets),
|
|
229
|
+
return _position_breakdown(
|
|
230
|
+
market_id, pos_read.value, market_read.value, params_read.value
|
|
242
231
|
)
|
|
243
232
|
|
|
233
|
+
def read_position_breakdowns(
|
|
234
|
+
self,
|
|
235
|
+
ctx: Web3Context,
|
|
236
|
+
market_ids: Sequence[MorphoBlueMarketId],
|
|
237
|
+
user: ChecksumAddress,
|
|
238
|
+
) -> list[MorphoPositionBreakdown | None]:
|
|
239
|
+
""":meth:`read_position_breakdown` for every market in one Multicall3
|
|
240
|
+
batch (one round trip per batch size, or one read per call without
|
|
241
|
+
Multicall3), in ``market_ids`` order.
|
|
242
|
+
|
|
243
|
+
``None`` for a market whose position, state or params read reverts,
|
|
244
|
+
returns no data or cannot be decoded; a transport error raises.
|
|
245
|
+
"""
|
|
246
|
+
batch = Multicall3(ctx).batch()
|
|
247
|
+
reads = [
|
|
248
|
+
(
|
|
249
|
+
batch.try_add(self.position(market_id, user)),
|
|
250
|
+
batch.try_add(self.market(market_id)),
|
|
251
|
+
batch.try_add(self.market_params(market_id)),
|
|
252
|
+
)
|
|
253
|
+
for market_id in market_ids
|
|
254
|
+
]
|
|
255
|
+
batch.run()
|
|
256
|
+
breakdowns: list[MorphoPositionBreakdown | None] = []
|
|
257
|
+
for market_id, (pos, market, params) in zip(market_ids, reads, strict=True):
|
|
258
|
+
if pos.value is None or market.value is None or params.value is None:
|
|
259
|
+
breakdowns.append(None)
|
|
260
|
+
else:
|
|
261
|
+
breakdowns.append(
|
|
262
|
+
_position_breakdown(
|
|
263
|
+
market_id, pos.value, market.value, params.value
|
|
264
|
+
)
|
|
265
|
+
)
|
|
266
|
+
return breakdowns
|
|
267
|
+
|
|
244
268
|
def market_params(self, market_id: MorphoBlueMarketId) -> Call[MorphoMarketParams]:
|
|
245
269
|
return self._view(
|
|
246
270
|
"idToMarketParams(bytes32)",
|
|
@@ -352,3 +376,31 @@ def _irm_borrow_rate_view(
|
|
|
352
376
|
to=params.irm, data=selector + payload, output_types=["uint256"]
|
|
353
377
|
)
|
|
354
378
|
return call.call(ctx)
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
def _position_breakdown(
|
|
382
|
+
market_id: MorphoBlueMarketId,
|
|
383
|
+
pos: MorphoPosition,
|
|
384
|
+
market: MorphoMarket,
|
|
385
|
+
params: MorphoMarketParams,
|
|
386
|
+
) -> MorphoPositionBreakdown:
|
|
387
|
+
return MorphoPositionBreakdown(
|
|
388
|
+
market_id=market_id,
|
|
389
|
+
loan_token=params.loan_token,
|
|
390
|
+
collateral_token=params.collateral_token,
|
|
391
|
+
collateral=pos.collateral,
|
|
392
|
+
borrow_assets=Amount(
|
|
393
|
+
to_assets_up(
|
|
394
|
+
pos.borrow_shares,
|
|
395
|
+
market.total_borrow_assets,
|
|
396
|
+
market.total_borrow_shares,
|
|
397
|
+
)
|
|
398
|
+
),
|
|
399
|
+
supply_assets=Amount(
|
|
400
|
+
to_assets_down(
|
|
401
|
+
pos.supply_shares,
|
|
402
|
+
market.total_supply_assets,
|
|
403
|
+
market.total_supply_shares,
|
|
404
|
+
)
|
|
405
|
+
),
|
|
406
|
+
)
|
|
@@ -572,12 +572,17 @@ def _fetch_lending_reads(
|
|
|
572
572
|
),
|
|
573
573
|
)
|
|
574
574
|
morpho_unread: list[str] = []
|
|
575
|
-
|
|
576
|
-
|
|
575
|
+
f_morpho = pool.submit(
|
|
576
|
+
_fetch_morpho_positions,
|
|
577
|
+
ctx,
|
|
578
|
+
vault_addr,
|
|
579
|
+
markets.market_substrates,
|
|
580
|
+
morpho_unread,
|
|
577
581
|
)
|
|
578
582
|
aave_positions = _fetch_aave_positions(
|
|
579
583
|
ctx, pool, vault_addr, markets.aave_pools, markets.market_substrates
|
|
580
584
|
)
|
|
585
|
+
morpho_positions = f_morpho.result()
|
|
581
586
|
lending_health = f_health.result()
|
|
582
587
|
return _LendingReads(
|
|
583
588
|
lending_health=lending_health,
|
|
@@ -51,7 +51,6 @@ def _collect_morpho_substrates(
|
|
|
51
51
|
|
|
52
52
|
def _fetch_morpho_positions(
|
|
53
53
|
ctx: Web3Context,
|
|
54
|
-
pool: ThreadPoolExecutor,
|
|
55
54
|
vault_addr: ChecksumAddress,
|
|
56
55
|
market_substrates: dict[MarketId, list[bytes]],
|
|
57
56
|
unread: list[str],
|
|
@@ -62,35 +61,34 @@ def _fetch_morpho_positions(
|
|
|
62
61
|
Each IPOR Morpho market (MORPHO_MARKET_IDS) can hold multiple morpho
|
|
63
62
|
market_id substrates; each substrate has an independent position. The
|
|
64
63
|
on-chain balance fuse reports a single netted number per IPOR market —
|
|
65
|
-
this helper exposes the three-way decomposition behind that number.
|
|
64
|
+
this helper exposes the three-way decomposition behind that number. Every
|
|
65
|
+
substrate of every market is read in one batch; a batch failure that
|
|
66
|
+
``_safe_call`` suppresses (an RPC error or timeout) leaves them all unread.
|
|
66
67
|
"""
|
|
67
68
|
per_market_substrates = _collect_morpho_substrates(market_substrates)
|
|
68
69
|
if not per_market_substrates:
|
|
69
70
|
return None
|
|
70
71
|
|
|
71
72
|
reader = MorphoReader(morpho_blue_address(ctx.chain_id))
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
]
|
|
82
|
-
for mid, morpho_mids in per_market_substrates.items()
|
|
83
|
-
}
|
|
73
|
+
flat = [
|
|
74
|
+
morpho_mid
|
|
75
|
+
for morpho_mids in per_market_substrates.values()
|
|
76
|
+
for morpho_mid in morpho_mids
|
|
77
|
+
]
|
|
78
|
+
reads = iter(
|
|
79
|
+
_safe_call(partial(reader.read_position_breakdowns, ctx, flat, vault_addr))
|
|
80
|
+
or [None] * len(flat)
|
|
81
|
+
)
|
|
84
82
|
|
|
85
83
|
result: dict[MarketId, list[MorphoPositionBreakdown]] = {}
|
|
86
|
-
for mid,
|
|
87
|
-
|
|
84
|
+
for mid, morpho_mids in per_market_substrates.items():
|
|
85
|
+
per_market = [next(reads) for _ in morpho_mids]
|
|
88
86
|
unread += [
|
|
89
87
|
f"Morpho position in market {morpho_mid}"
|
|
90
|
-
for morpho_mid, bd in zip(
|
|
88
|
+
for morpho_mid, bd in zip(morpho_mids, per_market, strict=True)
|
|
91
89
|
if bd is None
|
|
92
90
|
]
|
|
93
|
-
breakdowns = [bd for bd in
|
|
91
|
+
breakdowns = [bd for bd in per_market if bd is not None]
|
|
94
92
|
if breakdowns:
|
|
95
93
|
result[mid] = breakdowns
|
|
96
94
|
return result or None
|
|
@@ -165,8 +163,8 @@ def _fetch_aave_positions(
|
|
|
165
163
|
|
|
166
164
|
The on-chain `getUserAccountData` aggregates all reserves into a single
|
|
167
165
|
base-currency total — this helper exposes the per-asset decomposition.
|
|
168
|
-
Each market is read from its own Pool (`aave_pools`)
|
|
169
|
-
are skipped. Empty positions (vault allows the asset but holds none of it)
|
|
166
|
+
Each market is read from its own Pool (`aave_pools`), its assets in two
|
|
167
|
+
batches; markets without one are skipped. Empty positions (vault allows the asset but holds none of it)
|
|
170
168
|
are dropped.
|
|
171
169
|
"""
|
|
172
170
|
per_market_assets = {
|
|
@@ -177,24 +175,23 @@ def _fetch_aave_positions(
|
|
|
177
175
|
if not per_market_assets:
|
|
178
176
|
return None
|
|
179
177
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
178
|
+
futures: dict[MarketId, Future[list[AaveV3PositionBreakdown | None] | None]] = {
|
|
179
|
+
mid: pool.submit(
|
|
180
|
+
_safe_call,
|
|
181
|
+
partial(
|
|
182
|
+
AaveV3Reader(aave_pools[mid]).read_position_breakdowns,
|
|
183
|
+
ctx,
|
|
184
|
+
assets,
|
|
185
|
+
vault_addr,
|
|
186
|
+
),
|
|
187
|
+
)
|
|
189
188
|
for mid, assets in per_market_assets.items()
|
|
190
189
|
}
|
|
191
190
|
|
|
192
191
|
result: dict[MarketId, list[AaveV3PositionBreakdown]] = {}
|
|
193
|
-
for mid,
|
|
192
|
+
for mid, future in futures.items():
|
|
194
193
|
breakdowns = [
|
|
195
|
-
bd
|
|
196
|
-
for fut in per_market
|
|
197
|
-
if (bd := fut.result()) is not None and not bd.is_empty
|
|
194
|
+
bd for bd in future.result() or [] if bd is not None and not bd.is_empty
|
|
198
195
|
]
|
|
199
196
|
if breakdowns:
|
|
200
197
|
result[mid] = breakdowns
|
|
@@ -11,10 +11,12 @@ from web3.exceptions import ContractLogicError
|
|
|
11
11
|
# Top-level imports on purpose — they also exercise the __init__ exports.
|
|
12
12
|
from ipor_fusion import (
|
|
13
13
|
AccessManager,
|
|
14
|
+
CallPermission,
|
|
14
15
|
ContractNotFoundError,
|
|
15
16
|
NotPlasmaVaultError,
|
|
16
17
|
OwnerNotFoundError,
|
|
17
18
|
RoleAccount,
|
|
19
|
+
parse_access_manager_address,
|
|
18
20
|
resolve_access_manager,
|
|
19
21
|
role_account_sort_key,
|
|
20
22
|
)
|
|
@@ -255,3 +257,47 @@ class TestResolveAccessManager:
|
|
|
255
257
|
|
|
256
258
|
assert isinstance(manager, AccessManager)
|
|
257
259
|
assert manager.address == MANAGER_ADDR
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
SELECTOR = bytes.fromhex("6e553f65") # deposit(uint256,address)
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
class TestPermissionReads:
|
|
266
|
+
manager = AccessManager(parse_access_manager_address(MANAGER_ADDR))
|
|
267
|
+
|
|
268
|
+
def test_can_call_encodes_and_decodes(self):
|
|
269
|
+
call = self.manager.can_call(ALICE, VAULT_ADDR, SELECTOR)
|
|
270
|
+
|
|
271
|
+
selector = Web3.keccak(text="canCall(address,address,bytes4)")[:4]
|
|
272
|
+
assert call.calldata == selector + encode(
|
|
273
|
+
["address", "address", "bytes4"], [ALICE, VAULT_ADDR, SELECTOR]
|
|
274
|
+
)
|
|
275
|
+
assert call.decode(encode(["bool", "uint32"], [True, 0])) == CallPermission(
|
|
276
|
+
immediate=True, delay=Period(0)
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
@pytest.mark.parametrize(
|
|
280
|
+
("immediate", "delay", "allowed"),
|
|
281
|
+
[(True, 0, True), (False, 3600, True), (False, 0, False)],
|
|
282
|
+
)
|
|
283
|
+
def test_allowed_covers_scheduled_calls(self, immediate, delay, allowed):
|
|
284
|
+
permission = self.manager.can_call(ALICE, VAULT_ADDR, SELECTOR).decode(
|
|
285
|
+
encode(["bool", "uint32"], [immediate, delay])
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
assert permission.delay == delay
|
|
289
|
+
assert permission.allowed is allowed
|
|
290
|
+
|
|
291
|
+
def test_is_target_closed(self):
|
|
292
|
+
call = self.manager.is_target_closed(VAULT_ADDR)
|
|
293
|
+
|
|
294
|
+
assert call.calldata[:4] == Web3.keccak(text="isTargetClosed(address)")[:4]
|
|
295
|
+
assert call.decode(encode(["bool"], [True])) is True
|
|
296
|
+
|
|
297
|
+
def test_get_account_lock_time(self):
|
|
298
|
+
call = self.manager.get_account_lock_time(ALICE)
|
|
299
|
+
|
|
300
|
+
assert call.calldata == Web3.keccak(text="getAccountLockTime(address)")[
|
|
301
|
+
:4
|
|
302
|
+
] + encode(["address"], [ALICE])
|
|
303
|
+
assert call.decode(encode(["uint256"], [1_700_000_000])) == 1_700_000_000
|
|
@@ -1896,9 +1896,9 @@ class TestFetchAavePositions:
|
|
|
1896
1896
|
@patch("ipor_fusion.vault_positions.AaveV3Reader")
|
|
1897
1897
|
def test_drops_empty_breakdowns(self, mock_reader_cls):
|
|
1898
1898
|
mock_reader = MagicMock()
|
|
1899
|
-
mock_reader.
|
|
1900
|
-
supply=0, variable_debt=0, stable_debt=0
|
|
1901
|
-
|
|
1899
|
+
mock_reader.read_position_breakdowns.return_value = [
|
|
1900
|
+
_aave_breakdown(supply=0, variable_debt=0, stable_debt=0)
|
|
1901
|
+
]
|
|
1902
1902
|
mock_reader_cls.return_value = mock_reader
|
|
1903
1903
|
|
|
1904
1904
|
with ThreadPoolExecutor() as pool:
|
|
@@ -1914,7 +1914,7 @@ class TestFetchAavePositions:
|
|
|
1914
1914
|
@patch("ipor_fusion.vault_positions.AaveV3Reader")
|
|
1915
1915
|
def test_returns_breakdowns_for_active_positions(self, mock_reader_cls):
|
|
1916
1916
|
mock_reader = MagicMock()
|
|
1917
|
-
mock_reader.
|
|
1917
|
+
mock_reader.read_position_breakdowns.return_value = [_aave_breakdown(supply=42)]
|
|
1918
1918
|
mock_reader_cls.return_value = mock_reader
|
|
1919
1919
|
|
|
1920
1920
|
with ThreadPoolExecutor() as pool:
|
|
@@ -1933,8 +1933,8 @@ class TestFetchAavePositions:
|
|
|
1933
1933
|
breakdown comes from that market's Pool, never another market's."""
|
|
1934
1934
|
supply_in_pool = {pool: i for i, pool in enumerate(_AAVE_POOLS.values(), 1)}
|
|
1935
1935
|
mock_reader_cls.side_effect = lambda aave_pool: MagicMock(
|
|
1936
|
-
|
|
1937
|
-
return_value=_aave_breakdown(supply=supply_in_pool[aave_pool])
|
|
1936
|
+
read_position_breakdowns=MagicMock(
|
|
1937
|
+
return_value=[_aave_breakdown(supply=supply_in_pool[aave_pool])]
|
|
1938
1938
|
)
|
|
1939
1939
|
)
|
|
1940
1940
|
|
|
@@ -1956,24 +1956,20 @@ class TestFetchAavePositions:
|
|
|
1956
1956
|
class TestFetchMorphoPositions:
|
|
1957
1957
|
@patch("ipor_fusion.vault_positions.MorphoReader")
|
|
1958
1958
|
def test_returns_none_when_no_morpho_substrates(self, _mock_reader_cls):
|
|
1959
|
-
|
|
1960
|
-
result = _fetch_morpho_positions(MagicMock(), pool, _VAULT_ADDR, {}, [])
|
|
1959
|
+
result = _fetch_morpho_positions(MagicMock(), _VAULT_ADDR, {}, [])
|
|
1961
1960
|
assert result is None
|
|
1962
1961
|
|
|
1963
1962
|
@patch("ipor_fusion.vault_positions.MorphoReader")
|
|
1964
1963
|
def test_returns_breakdowns_for_morpho_substrates(self, mock_reader_cls):
|
|
1965
1964
|
mock_reader = MagicMock()
|
|
1966
|
-
mock_reader.
|
|
1965
|
+
mock_reader.read_position_breakdowns.return_value = [_morpho_breakdown()]
|
|
1967
1966
|
mock_reader_cls.return_value = mock_reader
|
|
1968
1967
|
|
|
1969
1968
|
ctx = MagicMock()
|
|
1970
1969
|
ctx.chain_id = 42161
|
|
1971
1970
|
morpho_sub = bytes.fromhex("ab" * 32)
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
result = _fetch_morpho_positions(
|
|
1975
|
-
ctx, pool, _VAULT_ADDR, {14: [morpho_sub]}, unread
|
|
1976
|
-
)
|
|
1971
|
+
unread: list[str] = []
|
|
1972
|
+
result = _fetch_morpho_positions(ctx, _VAULT_ADDR, {14: [morpho_sub]}, unread)
|
|
1977
1973
|
assert result == {14: [_morpho_breakdown()]}
|
|
1978
1974
|
assert unread == []
|
|
1979
1975
|
# Arbitrum has its own Morpho deployment — never the Ethereum address.
|
|
@@ -1981,23 +1977,38 @@ class TestFetchMorphoPositions:
|
|
|
1981
1977
|
|
|
1982
1978
|
@patch("ipor_fusion.vault_positions.MorphoReader")
|
|
1983
1979
|
def test_a_failed_substrate_is_named_and_the_rest_kept(self, mock_reader_cls):
|
|
1984
|
-
good, bad = bytes.fromhex(
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
mock_reader_cls.return_value.read_position_breakdown.side_effect = read
|
|
1980
|
+
good, bad, other = (bytes.fromhex(h * 32) for h in ("ab", "cd", "ef"))
|
|
1981
|
+
mock_reader_cls.return_value.read_position_breakdowns.return_value = [
|
|
1982
|
+
_morpho_breakdown(),
|
|
1983
|
+
None,
|
|
1984
|
+
_morpho_breakdown(),
|
|
1985
|
+
]
|
|
1992
1986
|
ctx = MagicMock()
|
|
1993
1987
|
ctx.chain_id = 42161
|
|
1994
1988
|
unread: list[str] = []
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
assert result == {14: [_morpho_breakdown()]}
|
|
1989
|
+
result = _fetch_morpho_positions(
|
|
1990
|
+
ctx, _VAULT_ADDR, {14: [good, bad, other]}, unread
|
|
1991
|
+
)
|
|
1992
|
+
assert result == {14: [_morpho_breakdown(), _morpho_breakdown()]}
|
|
2000
1993
|
assert unread == [f"Morpho position in market 0x{'cd' * 32}"]
|
|
1994
|
+
# One batch for every substrate, in order.
|
|
1995
|
+
(_ctx, market_ids, _vault), _ = (
|
|
1996
|
+
mock_reader_cls.return_value.read_position_breakdowns.call_args
|
|
1997
|
+
)
|
|
1998
|
+
assert market_ids == [f"0x{h * 32}" for h in ("ab", "cd", "ef")]
|
|
1999
|
+
|
|
2000
|
+
@patch("ipor_fusion.vault_positions.MorphoReader")
|
|
2001
|
+
def test_a_transport_error_leaves_every_substrate_unread(self, mock_reader_cls):
|
|
2002
|
+
mock_reader_cls.return_value.read_position_breakdowns.side_effect = (
|
|
2003
|
+
Web3RPCError("rate limited")
|
|
2004
|
+
)
|
|
2005
|
+
ctx = MagicMock()
|
|
2006
|
+
ctx.chain_id = 42161
|
|
2007
|
+
unread: list[str] = []
|
|
2008
|
+
subs = [bytes.fromhex("ab" * 32), bytes.fromhex("cd" * 32)]
|
|
2009
|
+
result = _fetch_morpho_positions(ctx, _VAULT_ADDR, {14: subs}, unread)
|
|
2010
|
+
assert result is None
|
|
2011
|
+
assert unread == [f"Morpho position in market 0x{h * 32}" for h in ("ab", "cd")]
|
|
2001
2012
|
|
|
2002
2013
|
|
|
2003
2014
|
class TestBreakdownTokenPrices:
|