ipor-fusion 4.0.0.dev151__tar.gz → 4.0.0.dev156__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.dev151 → ipor_fusion-4.0.0.dev156}/AGENTS.md +36 -19
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/MIGRATION_4.md +98 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/PKG-INFO +4 -4
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/README.md +3 -3
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/examples/advanced_euler_v2_credit_market_base.py +26 -22
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/examples/advanced_euler_v2_looping_arbitrum.py +27 -29
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/examples/composed_hypercore_flow_arbitrum_hyperevm.py +35 -34
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/examples/crosschain_ccip_usdc_arbitrum_hyperevm.py +87 -92
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/examples/external_state_margin_leg_base.py +170 -96
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/examples/simple_aave_v3_supply_base.py +20 -20
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/pyproject.toml +3 -2
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/__init__.py +111 -2
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/cli/vault_cmd.py +34 -3
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/cli/vault_info_view.py +4 -3
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/cli/vault_markets_view.py +7 -7
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/core/__init__.py +15 -2
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/core/access.py +12 -1
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/core/contract.py +10 -9
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/core/external_state_executor.py +3 -4
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/core/fusion_factory.py +34 -36
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/core/multicall.py +115 -6
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/core/simulation.py +90 -65
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/crosschain/ccip/events.py +5 -2
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/crosschain/ccip/indexer.py +22 -13
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/crosschain/lane.py +27 -19
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/crosschain/simulation.py +13 -6
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/crosschain/stargate/lane.py +1 -2
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/crosschain/stargate/transport.py +20 -17
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/crosschain/transport.py +2 -2
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/errors.py +3 -2
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/fuses/external_state_events.py +14 -9
- ipor_fusion-4.0.0.dev156/src/ipor_fusion/market_info.py +396 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/mcp/models.py +8 -6
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/morpho_api.py +192 -132
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/readers/hypercore.py +43 -43
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/readers/hypercore_outcome.py +17 -26
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/readers/lending_health.py +10 -8
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/readers/morpho.py +6 -7
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/readers/oracle_mapping_reader.py +5 -2
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/readers/unpriceable.py +11 -12
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/vault_data.py +74 -94
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/vault_erc20.py +18 -19
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/vault_info.py +500 -90
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/vault_managers.py +47 -61
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/vault_reads.py +3 -24
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/_crosschain_lifecycle.py +114 -110
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/_crosschain_recovery.py +155 -92
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/_multicall.py +10 -2
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/conftest.py +1 -1
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_cache.py +4 -1
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_cli_helpers.py +11 -47
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_cli_market_morpho_blue.py +8 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_cli_pending_requests_scan.py +5 -12
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_crosschain_ccip_events.py +11 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_crosschain_errors.py +0 -2
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_crosschain_transport.py +21 -6
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_examples_vaults.py +0 -2
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_external_state_events.py +9 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_external_state_executor.py +11 -18
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_hypercore_live.py +15 -21
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_hypercore_simulation.py +8 -8
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_mcp_models.py +218 -112
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_multicall.py +158 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_oracle.py +3 -3
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_public_api.py +103 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_simulate_borrow_aave_v3_mainnet.py +20 -22
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_simulate_claim_merkl_wrapper_base.py +12 -14
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_simulate_claim_morpho_rewards.py +3 -4
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_simulate_crosschain_canary.py +15 -14
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_simulate_crosschain_hypercore.py +9 -9
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_simulate_crosschain_hyperevm.py +164 -158
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_simulate_euler_v2_batch_base.py +5 -6
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_simulate_euler_v2_credit_market_base.py +5 -6
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_simulate_euler_v2_swap_deploy_base.py +9 -13
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_simulate_euler_v2_swap_reconfigure_base.py +5 -6
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_simulate_euler_v2_swap_registry_base.py +7 -11
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_simulate_external_state_executor_base.py +10 -10
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_simulate_looping_aave_v3_base.py +9 -10
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_simulate_looping_morpho_blue_base.py +7 -8
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_simulate_morpho_blue.py +8 -9
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_simulate_pilot_v3.py +34 -38
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_simulate_pilot_v4_uniswap.py +30 -35
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_simulate_pilot_v5_ramses.py +24 -28
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_simulate_pilot_v5_scheduled.py +45 -56
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_simulate_release_funds.py +12 -14
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_simulate_swap_on_base.py +16 -18
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_simulate_vault_from_scratch_base.py +15 -16
- ipor_fusion-4.0.0.dev156/tests/test_simulation_observation.py +126 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_unpriceable_tokens.py +9 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_vault_simulator_blocks.py +25 -25
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/typed_client/consumer.py +71 -3
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/typed_client/rejected.py +26 -0
- ipor_fusion-4.0.0.dev151/src/ipor_fusion/market_info.py +0 -223
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/.claude-plugin/marketplace.json +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/.claude-plugin/plugin.json +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/.env.example +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/.gemini/settings.json +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/.git-blame-ignore-revs +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/.github/workflows/cd.yml +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/.github/workflows/ci.yml +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/.github/workflows/commit-lint.yml +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/.github/workflows/notify-slack.yml +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/.github/workflows/python-build.yml +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/.github/workflows/python-publish.yml +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/.github/workflows/release.yml +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/.github/workflows/report-slack.yml +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/.gitignore +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/.mcp.json +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/.pre-commit-config.yaml +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/.python-version +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/CHANGELOG.md +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/CLAUDE.md +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/CODEOWNERS +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/LICENSE +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/build.sh +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/examples/CONTRIBUTING.md +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/examples/README.md +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/pytest.ini +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/scripts/commit-check.sh +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/scripts/crosschain_readiness.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/skills/ipor-deploy-vault/SKILL.md +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/about.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/cache.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/chains.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/cli/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/cli/changelog_cmd.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/cli/config_cmd.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/cli/config_store.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/cli/main.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/cli/market_cmd.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/cli/options.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/cli/oracle_mapping_view.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/cli/vault_health.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/cli/vault_rendering.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/config/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/config/roles.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/core/address.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/core/context.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/core/erc20.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/core/fee_manager.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/core/fuse.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/core/fuse_action.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/core/logs.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/core/oracle.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/core/plasma_vault.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/core/rewards_manager.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/core/withdraw_manager.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/crosschain/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/crosschain/ccip/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/crosschain/ccip/chainlink.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/crosschain/ccip/codec.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/crosschain/ccip/contracts.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/crosschain/ccip/lane.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/crosschain/ccip/quote.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/crosschain/ccip/transport.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/crosschain/contracts.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/crosschain/discovery.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/crosschain/errors.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/crosschain/fuses/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/crosschain/fuses/base.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/crosschain/fuses/ccip.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/crosschain/fuses/stargate.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/crosschain/logs.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/crosschain/messages.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/crosschain/stargate/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/crosschain/stargate/contracts.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/crosschain/stargate/layerzero.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/dependency_graph.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/entry_cli.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/entry_mcp.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/explorer.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/field_docs.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/fuses/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/fuses/aave_v3.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/fuses/async_action.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/fuses/base.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/fuses/compound_v3.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/fuses/erc4626.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/fuses/euler_v2.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/fuses/events.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/fuses/external_state.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/fuses/fluid_instadapp.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/fuses/gearbox_v3.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/fuses/hypercore.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/fuses/hypercore_events.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/fuses/merkl.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/fuses/morpho.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/fuses/ramses_v2.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/fuses/uniswap_v3.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/fuses/universal.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/guide/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/guide/architecture.md +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/guide/glossary.md +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/guide/invariants.md +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/guide/quickstart.md +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/market_ids.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/mcp/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/mcp/guide.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/mcp/server.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/py.typed +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/readers/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/readers/aave_v3.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/readers/aave_v4.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/readers/compound_v3.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/readers/oracle_mapping.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/readers/oracle_mapping_nodes.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/readers/position_manager.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/readers/ramses_v2.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/readers/uniswap_v3.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/substrates.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/types.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/validation.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/src/ipor_fusion/vault_positions.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/UniswapV3UniversalRouter.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/_crosschain.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/_euler_v2.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/_foundry.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/_guide.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/_hypercore_model.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/_hypercore_shadow.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/_simulate.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/_validation.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/addresses.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/constants.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/fixtures/hypercore_hip3_flow.json +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/fixtures/hypercore_market55.json +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/fixtures/vault_info_golden/clean.json +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/fixtures/vault_info_golden/clean.txt +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/fixtures/vault_info_golden/rich.json +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/fixtures/vault_info_golden/rich.txt +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_about.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_access_manager.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_chains.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_cli_changelog.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_cli_commands.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_cli_config_store.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_cli_hypercore.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_cli_lending_health.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_cli_oracle_mapping.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_cli_role_accounts.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_cli_vault_info_golden.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_cli_vault_predates_getters.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_cli_vault_validate.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_contract_call.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_contract_encoder.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_crosschain_ccip_indexer.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_crosschain_ccip_quote.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_crosschain_chainlink.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_crosschain_contracts.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_crosschain_encoding.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_crosschain_lane.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_crosschain_layering.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_crosschain_readiness.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_crosschain_readiness_report.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_docs_typing.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_erc20.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_error_hierarchy.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_examples_composed_hypercore.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_examples_crosschain.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_examples_euler_looping_arbitrum.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_explorer.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_fee_manager.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_fuse_encoding.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_fusion_instance.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_guide.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_hypercore_deployment.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_hypercore_events.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_hypercore_flow.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_hypercore_outcome.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_hypercore_readers.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_logs.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_mcp_hypercore.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_mcp_server.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_mcp_vault_validate.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_oracle_mapping.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_plasma_vault.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_public_type_reachability.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_reader_encoding.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_readiness_selection.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_revert_decoding.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_rewards_manager.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_roles.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_sdk_layering.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_simulate_crosschain_lifecycle.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_simulate_examples_arbitrum.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_simulate_examples_base.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_simulate_examples_crosschain.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_simulate_fusion_factory_clone_base.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_simulate_plasma_vault_setters_base.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_simulate_readers_integration.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_solidity_compiler.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_solidity_mirrors.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_substrates.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_types_and_events.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_validation.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_vault_data_block.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_vault_dep_graph.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_vault_info_onchain_base.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_vault_info_onchain_base_no_withdraw_manager.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_web3_context.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/tests/test_withdraw_manager.py +0 -0
- {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/uv.lock +0 -0
|
@@ -186,7 +186,7 @@ the lifecycle matrix follows that.
|
|
|
186
186
|
| Python 3.12 runtime, 3.11 floor | `.python-version` + CI `python-version` default; `requires-python`, ruff `target-version`, pyright `pythonVersion` |
|
|
187
187
|
| `IporFusionMarkets`, `Roles` | `market_ids.py`, `config/roles.py` mirror `IporFusionMarkets.sol`, `Roles.sol` in `ipor-fusion/contracts/libraries/`; drift-gated by `tests/test_solidity_mirrors.py` — bump its pinned ref in the same change that syncs the mirrors An id sourced from a contracts feature branch ahead of the pinned ref goes in the test's `_AHEAD_OF_UPSTREAM` allowlist and is removed when the ref catches up |
|
|
188
188
|
| substrate decoders | `substrates.py` registry mirrors each market's `contracts/fuses/<protocol>/*SubstrateLib.sol` or `*FuseLib.sol` |
|
|
189
|
-
| `vault_info` JSON
|
|
189
|
+
| `vault_info` / `fusion market` JSON shapes | the builders and their TypedDicts (`VaultInfoJson`, `MorphoBlueMarketJson`, `MetaMorphoV1Json` / `MetaMorphoV2Json`) in `vault_info.py` / `market_info.py`, models in `mcp/models.py` (`extra="forbid"`, the published schema), the `VaultInfoJson`-typed `_full_vault_info_dict` fixture in `test_mcp_models.py` |
|
|
190
190
|
| CLI command set | every CLI command has a matching tool in `mcp/server.py` (`changelog` maps to `server_info`) |
|
|
191
191
|
|
|
192
192
|
## Layout (`src/ipor_fusion/`)
|
|
@@ -207,14 +207,14 @@ the lifecycle matrix follows that.
|
|
|
207
207
|
balance-graph checks
|
|
208
208
|
- `vault_data.py` — public vault read: `fetch_vault_data` → `VaultData` (with `FeeData`,
|
|
209
209
|
`WithdrawManagerData`), `fetch_deployment_info`, cached `resolve_token_symbol` /
|
|
210
|
-
`resolve_token_decimals`; it composes `vault_reads.py` (the
|
|
211
|
-
|
|
210
|
+
`resolve_token_decimals`; it composes `vault_reads.py` (the degrade-to-``None``
|
|
211
|
+
helpers), `vault_managers.py` (fee and WithdrawManager
|
|
212
212
|
snapshots), `vault_positions.py` (Morpho/Aave position breakdowns and their token
|
|
213
213
|
prices) and `vault_erc20.py` (the ERC20_VAULT_BALANCE holdings);
|
|
214
214
|
`vault_info.py` — `build_vault_info_json(data)`, the `fusion vault info --json`
|
|
215
215
|
document rendered from the snapshot alone (contract names via an injected
|
|
216
|
-
`resolve_name`)
|
|
217
|
-
and `health_check_lists`; `explorer.py` Etherscan V2 (single endpoint, needs API key);
|
|
216
|
+
`resolve_name`) and typed block by block (`VaultInfoJson` and its nested
|
|
217
|
+
TypedDicts), and `health_check_lists`; `explorer.py` Etherscan V2 (single endpoint, needs API key);
|
|
218
218
|
`cache.py` the `ContractCache` protocol (`get`/`put` of immutable facts keyed
|
|
219
219
|
`CacheKey(chain_id, address, field)`), `FileContractCache` (one JSON file, atomic
|
|
220
220
|
replace, corrupt/unwritable degrades to memory) and `NullCache`; every SDK use goes
|
|
@@ -223,8 +223,14 @@ the lifecycle matrix follows that.
|
|
|
223
223
|
`$XDG_CACHE_HOME/ipor-fusion/`) or `get_contract_name(cache=...)`. Never cache an
|
|
224
224
|
absence (no code, no answer): it can change at a later block.
|
|
225
225
|
`morpho_api.py` the Morpho public GraphQL API client (`fetch_market`, `fetch_vault`,
|
|
226
|
-
`PUBLIC_ALLOCATOR_ADDRESSES
|
|
227
|
-
`
|
|
226
|
+
`PUBLIC_ALLOCATOR_ADDRESSES`; it narrows the GraphQL body from `object`);
|
|
227
|
+
`market_info.py` — `build_morpho_blue_market_json`, `build_meta_morpho_json`, the
|
|
228
|
+
`fusion market` JSON documents (`MorphoBlueMarketJson`, `MetaMorphoV1Json` /
|
|
229
|
+
`MetaMorphoV2Json`). A JSON document is a TypedDict (`NotRequired` for a key only
|
|
230
|
+
some entries carry), never `dict[str, Any]`; JSON parsed from the network or a
|
|
231
|
+
caller enters as `object` / `Mapping[str, object]` and is narrowed with
|
|
232
|
+
`isinstance`. These two modules skip `from __future__ import annotations`, so a
|
|
233
|
+
TypedDict's `__optional_keys__` is right at run time.
|
|
228
234
|
None of them imports `cli/` (`tests/test_sdk_layering.py`)
|
|
229
235
|
- `config/roles.py` — `Roles` IntEnum
|
|
230
236
|
- `core/` — `context` (`Web3Context`), `contract` (`Call`, `ContractWrapper`),
|
|
@@ -334,10 +340,14 @@ or examples.
|
|
|
334
340
|
never consults the executing context.
|
|
335
341
|
- Batch independent reads: `Multicall3(ctx).aggregate(calls)` (raises as
|
|
336
342
|
`.call(ctx)` would) or `.try_aggregate(calls)` (``None`` per failed read) runs
|
|
337
|
-
many `Call`s in one `eth_call` at one block
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
343
|
+
many `Call`s of one type in one `eth_call` at one block. Reads of different
|
|
344
|
+
types go through `batch = Multicall3(ctx).batch()`: `batch.add(call)` returns a
|
|
345
|
+
`BatchValue[T]` (raises from `batch.run()` as `.call(ctx)` would),
|
|
346
|
+
`batch.try_add(call)` a `BatchValue[T | None]`, and after one `batch.run()`
|
|
347
|
+
each `.value` keeps its type; never collect mixed reads in a `list[Call[Any]]`
|
|
348
|
+
or unpack an `aggregate` of them. Every batch, re-run and no-Multicall fallback
|
|
349
|
+
runs on its `ctx`. A loop of `.call(ctx)`s, or a thread pool of them, costs one
|
|
350
|
+
round trip each; RPC latency, not decoding, is what makes the vault tooling slow.
|
|
341
351
|
- Fuses (`fuses/`) are stateless encoders: a method returns an immutable
|
|
342
352
|
`FuseAction(fuse_address, calldata)` and touches no chain. `Fuse` builds it
|
|
343
353
|
via `self._action_raw(solidity_signature, values)`; the shared `_validate_*`
|
|
@@ -348,8 +358,11 @@ or examples.
|
|
|
348
358
|
pins one read.
|
|
349
359
|
- Simulate before sending: `VaultSimulator(web3, vault=, alpha=)` batches
|
|
350
360
|
`execute` and `observe` calls into one `eth_simulateV1` round trip, across
|
|
351
|
-
blocks via `next_block`.
|
|
352
|
-
|
|
361
|
+
blocks via `next_block`. `observe(call, label=)` returns a
|
|
362
|
+
`SimulationObservation[T]`; after `run()` its `.value` is the typed read (it
|
|
363
|
+
raises when the read reverted), from the latest run, so a crosschain relay
|
|
364
|
+
round re-resolves it. Labels only name calls in `result.calls` and failures.
|
|
365
|
+
Integration tests and examples use it instead of a local node.
|
|
353
366
|
- Amounts are raw on-chain integers; `types.py` `NewType`s (`Amount`, `Shares`,
|
|
354
367
|
`MarketId`, ...) name the unit, and nothing scales by decimals.
|
|
355
368
|
- Three address roles are nominal: `PlasmaVaultAddress`, `FuseAddress` and
|
|
@@ -431,12 +444,16 @@ value sets are `Literal` enums. The docstring keeps only what has no
|
|
|
431
444
|
structured home: a one-line summary, behavioral notes, cost warnings. Do not
|
|
432
445
|
repeat schema content as prose; it drifts.
|
|
433
446
|
|
|
434
|
-
`mcp/models.py` forbids extra keys
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
`
|
|
439
|
-
|
|
447
|
+
`mcp/models.py` forbids extra keys and stays the source of the published JSON
|
|
448
|
+
schema. Adding a field to `build_vault_info_json` (or a `fusion market` document)
|
|
449
|
+
means adding it to its TypedDict, to the matching model (with a default for
|
|
450
|
+
back-compat) and to the `_full_vault_info_dict` fixture;
|
|
451
|
+
`TestTypedDocumentsMirrorModels` in `test_mcp_models.py` holds every TypedDict to
|
|
452
|
+
its model block by block. The model keeps `dict[str, Any]` where the schema is
|
|
453
|
+
deliberately loose (`substrates`, `deployment`, `share_price`, `dependency_graph`,
|
|
454
|
+
`position_breakdown`): the SDK types those blocks, the schema does not validate
|
|
455
|
+
their inner keys. The substrate entries stay open-ended on both sides
|
|
456
|
+
(`SubstrateEntryJson`, `dict[str, str | bool]`): each decoder adds its own fields.
|
|
440
457
|
|
|
441
458
|
## Domain rules (verified against the contracts)
|
|
442
459
|
|
|
@@ -54,6 +54,10 @@ before it is fixed.
|
|
|
54
54
|
nominal types. Wrap literals and raw values, `ChainId(8453)`, `Period(0)`,
|
|
55
55
|
`Amount(n)`, `parse_fuse_address(...)`; values the SDK returns already carry
|
|
56
56
|
their type. See [Nominal types](#nominal-types).
|
|
57
|
+
9. **Typed JSON documents.** `build_vault_info_json` and the `fusion market`
|
|
58
|
+
builders return TypedDicts (`VaultInfoJson`, ...); code annotating them
|
|
59
|
+
`dict[str, Any]` switches to the TypedDict or `Mapping[str, object]`. See
|
|
60
|
+
[Typed JSON documents](#typed-json-documents).
|
|
57
61
|
|
|
58
62
|
Then read [Behavior changes](#behavior-changes): they apply to every caller,
|
|
59
63
|
type-checked or not.
|
|
@@ -138,6 +142,56 @@ placeholder wrapper.
|
|
|
138
142
|
| `executor.parse_balance_proposed(log)` / `find_balance_proposed(logs)` | `...(log, chain_id=...)` / `...(logs, chain_id=...)` |
|
|
139
143
|
| `CcipLane(executor=, dispatcher=, spoke_chain_id=, ...)` | `CcipLane(hub_ctx=, spoke_ctx=, executor=, dispatcher=, ...)` (same for `StargateLane`) |
|
|
140
144
|
| `transport.executor(ctx, address)` / `transport.dispatcher(ctx, address)` | `transport.executor(address)` / `transport.dispatcher(address)` |
|
|
145
|
+
| `sim.observe("label", call)` ... `result.get("label")` | `read = sim.observe(call)` ... `read.value` |
|
|
146
|
+
| `csim.observe(chain_id, "label", call)` ... `results[chain_id].get("label")` | `read = csim.observe(chain_id, call)` ... `read.value` |
|
|
147
|
+
| `result.observations`, `SimulatedCallResult.decoded` | gone: keep the observation, or `call.decode(bytes(r.return_data))` |
|
|
148
|
+
| `a, b = Multicall3(ctx).aggregate(cast(list[Call[Any]], [x, y]))` | `batch = Multicall3(ctx).batch()`; `a = batch.add(x)`; `b = batch.add(y)`; `batch.run()`; `a.value`, `b.value` |
|
|
149
|
+
|
|
150
|
+
### Typed batches and simulation observations
|
|
151
|
+
|
|
152
|
+
Reads of different types keep their types. `Multicall3(ctx).batch()` returns a
|
|
153
|
+
`Multicall3Batch`: `batch.add(call)` queues a `Call[T]` and returns a
|
|
154
|
+
`BatchValue[T]`; `batch.try_add(call)` returns a `BatchValue[T | None]`. One
|
|
155
|
+
`batch.run()` executes every queued read in one `eth_call` per `batch_size` reads
|
|
156
|
+
(one per read where Multicall3 is not deployed), exactly as `aggregate` does for the
|
|
157
|
+
same calls. A failing `add` read raises from `run()` what `Call.call(ctx)` would; a
|
|
158
|
+
failing `try_add` read is `None`, as in `try_aggregate`. Each read chooses its own
|
|
159
|
+
failure handling, so a mixed batch of required and optional reads is one round trip
|
|
160
|
+
and every `value` says in its type whether it can be `None`. Reading `value` before
|
|
161
|
+
`run()` raises `RuntimeError`, and a batch runs once. `aggregate` / `try_aggregate`
|
|
162
|
+
stay for lists of one type.
|
|
163
|
+
|
|
164
|
+
```python
|
|
165
|
+
batch = Multicall3(ctx).batch()
|
|
166
|
+
assets = batch.add(vault.total_assets()) # BatchValue[Amount]
|
|
167
|
+
fuses = batch.add(vault.get_fuses()) # BatchValue[list[FuseAddress]]
|
|
168
|
+
name = batch.try_add(vault.name()) # BatchValue[str | None]
|
|
169
|
+
batch.run()
|
|
170
|
+
assets.value, fuses.value, name.value
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
`VaultSimulator.observe(call, *, label=None)` returns a `SimulationObservation[T]`
|
|
174
|
+
instead of the simulator, so it no longer chains, and its label is optional (it
|
|
175
|
+
names the read in `result.calls` and in failures). After `run()`,
|
|
176
|
+
`observation.value` is the read decoded as its `Call` decodes it, from the latest
|
|
177
|
+
run; `observation.result` is its `SimulatedCallResult`.
|
|
178
|
+
`CrosschainSimulator.observe(chain_id, call, *, label=None)` returns the same,
|
|
179
|
+
resolved by every relay round. `SimulationResult.get`, `SimulationResult.observations`
|
|
180
|
+
and `SimulatedCallResult.decoded` are removed: a list of results of many types has no
|
|
181
|
+
honest element type, so the typed value lives on the observation. A write queued with
|
|
182
|
+
`add_call` is no longer decoded; decode its return data with the `Call` you queued.
|
|
183
|
+
|
|
184
|
+
```python
|
|
185
|
+
before = sim.observe(vault.total_assets()) # SimulationObservation[Amount]
|
|
186
|
+
sim.execute([action])
|
|
187
|
+
after = sim.observe(vault.total_assets())
|
|
188
|
+
sim.run()
|
|
189
|
+
after.value - before.value
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
`Call` is a frozen dataclass and covariant in its result type: assigning to a
|
|
193
|
+
field raises, and a `Call[Amount]` is a `Call[int]` and a `Call[object]`, so a
|
|
194
|
+
`Sequence[Call[object]]` takes any calls without a cast.
|
|
141
195
|
|
|
142
196
|
### Renamed methods
|
|
143
197
|
|
|
@@ -721,6 +775,45 @@ string). Calling an MCP tool function
|
|
|
721
775
|
directly from Python takes `ChainId`. Their amount, decimals and time fields stay
|
|
722
776
|
`int`: they are JSON output models, and the SDK values above fit them unchanged.
|
|
723
777
|
|
|
778
|
+
## Typed JSON documents
|
|
779
|
+
|
|
780
|
+
The JSON document builders return `TypedDict`s instead of `dict[str, Any]`; the
|
|
781
|
+
documents themselves (keys, values, order, the MCP schemas) are unchanged, so
|
|
782
|
+
untyped code needs none of this section.
|
|
783
|
+
|
|
784
|
+
| Builder | Returns |
|
|
785
|
+
|---|---|
|
|
786
|
+
| `build_vault_info_json` | `VaultInfoJson` |
|
|
787
|
+
| `build_morpho_blue_market_json` | `MorphoBlueMarketJson` |
|
|
788
|
+
| `build_meta_morpho_json` | `MetaMorphoV1Json \| MetaMorphoV2Json`, told apart by `doc["version"]` |
|
|
789
|
+
| `RoleAccount.to_dict` | `RoleAccountJson` |
|
|
790
|
+
|
|
791
|
+
Every nested block has its own TypedDict (`FeesJson`, `WithdrawManagerJson`,
|
|
792
|
+
`BalanceFuseJson`, `ReconciliationJson`, `HyperCoreJson`, `MorphoSupplyingVaultJson`,
|
|
793
|
+
...), all importable from `ipor_fusion` and from `ipor_fusion.vault_info` /
|
|
794
|
+
`ipor_fusion.market_info` (`RoleAccountJson` from `ipor_fusion.core`). Values carry the
|
|
795
|
+
SDK's nominal types (`doc["vault"]` is a `PlasmaVaultAddress`, `doc["chain_id"]` a
|
|
796
|
+
`ChainId`). A block that can be `null` is `X | None`: check it before indexing
|
|
797
|
+
(`fees = doc["fees"]; if fees is not None: fees["management_fee_percent"]`). A key
|
|
798
|
+
that only some entries carry is `NotRequired` (a balance fuse's `pct_of_total` and
|
|
799
|
+
`position_breakdown`, the Morpho API keys of the market document): read it with
|
|
800
|
+
`.get`. A decoded substrate entry stays open-ended, `SubstrateEntryJson`
|
|
801
|
+
(`dict[str, str | bool]`), since each decoder adds its own fields.
|
|
802
|
+
|
|
803
|
+
- A variable or parameter annotated `dict[str, Any]` no longer accepts these
|
|
804
|
+
documents (a `TypedDict` is not a `dict[str, Any]`): annotate it with the TypedDict,
|
|
805
|
+
or `Mapping[str, object]` for code that only passes the document on (`json.dumps`
|
|
806
|
+
accepts either).
|
|
807
|
+
- Parsing entry points take `Mapping[str, object]` instead of `Mapping[str, Any]`:
|
|
808
|
+
`decode_external_state_event(log)` and `FusionInstance.from_dict(data)`.
|
|
809
|
+
`VerifierResult.message` is a `Mapping[str, object]`, and `OutboundMessage.raw` is
|
|
810
|
+
`object`: narrow it before use (`isinstance(message.raw, MessageV1)` for CCIP; a
|
|
811
|
+
Stargate `Packet`, or a `(Packet, TaxiMessage)` pair for a token transfer).
|
|
812
|
+
- The `vault info` document's `deployment` is `DeploymentJson` (block, timestamp,
|
|
813
|
+
age) or `DeploymentErrorJson` (`{"error": ...}`) or `None`; `share_price` is
|
|
814
|
+
`SharePriceJson` (`asset`, and `usd` when the asset is priced); `dependency_graph`
|
|
815
|
+
is `DependencyGraphJson` (`edges`, `update_reach`, `update_groups`).
|
|
816
|
+
|
|
724
817
|
## Behavior changes
|
|
725
818
|
|
|
726
819
|
These apply to every caller, type-checked or not. Changes described in full above
|
|
@@ -775,5 +868,10 @@ are listed with a link.
|
|
|
775
868
|
- `LendingMarketHealth.substrate_id` for a Morpho market is `0x`-prefixed (the JSON
|
|
776
869
|
documents already were).
|
|
777
870
|
- `TransactionError.tx_hash` is `0x`-prefixed.
|
|
871
|
+
- An observed simulation read that reverted raises `SimulationError` from its
|
|
872
|
+
`value`, one that returned no data raises `EmptyCallResultError`, and one its
|
|
873
|
+
decoder cannot read raises the decoder's error. 3.x left it out of
|
|
874
|
+
`result.observations` (so `result.get(label)` raised `KeyError`) or reported an
|
|
875
|
+
undecodable read as `None`, indistinguishable from a read that returns `None`.
|
|
778
876
|
- Saved config entries are checksummed when loaded; the MCP `vault_add` tool rejects
|
|
779
877
|
a value that is not an address.
|
|
@@ -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.dev156
|
|
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
|
|
@@ -188,12 +188,12 @@ payload = vault.execute([action]).calldata # raw bytes for an external signer
|
|
|
188
188
|
|
|
189
189
|
# Simulate first via eth_simulateV1 (no local node); alpha is the account allowed to call execute()
|
|
190
190
|
sim = VaultSimulator(ctx.web3, vault=vault.address, alpha=Web3.to_checksum_address("0xALPHA"))
|
|
191
|
-
sim.observe(
|
|
191
|
+
before = sim.observe(vault.total_assets()) # SimulationObservation[Amount]
|
|
192
192
|
sim.execute([action])
|
|
193
|
-
sim.observe(
|
|
193
|
+
after = sim.observe(vault.total_assets())
|
|
194
194
|
result = sim.run()
|
|
195
195
|
if result.all_success:
|
|
196
|
-
print(
|
|
196
|
+
print(after.value - before.value)
|
|
197
197
|
```
|
|
198
198
|
|
|
199
199
|
## CLI Quickstart
|
|
@@ -140,12 +140,12 @@ payload = vault.execute([action]).calldata # raw bytes for an external signer
|
|
|
140
140
|
|
|
141
141
|
# Simulate first via eth_simulateV1 (no local node); alpha is the account allowed to call execute()
|
|
142
142
|
sim = VaultSimulator(ctx.web3, vault=vault.address, alpha=Web3.to_checksum_address("0xALPHA"))
|
|
143
|
-
sim.observe(
|
|
143
|
+
before = sim.observe(vault.total_assets()) # SimulationObservation[Amount]
|
|
144
144
|
sim.execute([action])
|
|
145
|
-
sim.observe(
|
|
145
|
+
after = sim.observe(vault.total_assets())
|
|
146
146
|
result = sim.run()
|
|
147
147
|
if result.all_success:
|
|
148
|
-
print(
|
|
148
|
+
print(after.value - before.value)
|
|
149
149
|
```
|
|
150
150
|
|
|
151
151
|
## CLI Quickstart
|
|
@@ -396,7 +396,7 @@ def run_simulation(web3: Web3) -> SimulationResult:
|
|
|
396
396
|
# - sim.add_call(call=X, from_=OWNER) -> X.send(ctx) by the owner/atomist
|
|
397
397
|
# (or X.calldata to a multisig / timelock).
|
|
398
398
|
# - sim.execute([action]) -> plasma_vault.execute([action]).send(ctx) by the alpha.
|
|
399
|
-
# - sim.observe(
|
|
399
|
+
# - read = sim.observe(X), then read.value -> X.call(ctx), a plain read.
|
|
400
400
|
sim = VaultSimulator(
|
|
401
401
|
web3=web3, vault=preview.plasma_vault, alpha=ALPHA, block=PINNED_BLOCK
|
|
402
402
|
)
|
|
@@ -467,14 +467,14 @@ def run_simulation(web3: Web3) -> SimulationResult:
|
|
|
467
467
|
)
|
|
468
468
|
|
|
469
469
|
# Read the granted substrate set back so we can assert it is EXACTLY our two.
|
|
470
|
-
sim.observe(
|
|
470
|
+
granted_substrates = sim.observe(plasma_vault.get_market_substrates(EULER_MARKET))
|
|
471
471
|
plan.append("read the granted substrate set back")
|
|
472
472
|
|
|
473
473
|
# Oracle coverage: the vault must be able to price every tracked asset. A fresh
|
|
474
474
|
# vault's price manager delegates to the global middleware for assets without
|
|
475
475
|
# an override, so these read the effective prices.
|
|
476
|
-
sim.observe(
|
|
477
|
-
sim.observe(
|
|
476
|
+
cbeth_price_obs = sim.observe(price_manager.get_asset_price(BASE_CBETH))
|
|
477
|
+
weth_price_obs = sim.observe(price_manager.get_asset_price(BASE_WETH))
|
|
478
478
|
plan.append("verify oracle coverage for cbETH and WETH")
|
|
479
479
|
|
|
480
480
|
# Fund the vault with cbETH collateral. SIMULATION ONLY: an impersonated
|
|
@@ -485,7 +485,7 @@ def run_simulation(web3: Web3) -> SimulationResult:
|
|
|
485
485
|
call=cbeth.transfer(preview.plasma_vault, COLLATERAL_FUND_AMOUNT),
|
|
486
486
|
from_=BASE_CBETH_WHALE,
|
|
487
487
|
)
|
|
488
|
-
sim.observe(
|
|
488
|
+
vault_cbeth_funded = sim.observe(cbeth.balance_of(preview.plasma_vault))
|
|
489
489
|
plan.append("fund the vault with cbETH collateral")
|
|
490
490
|
|
|
491
491
|
# ── Credit lifecycle, driven by the alpha via PlasmaVault.execute(...). ──
|
|
@@ -500,20 +500,22 @@ def run_simulation(web3: Web3) -> SimulationResult:
|
|
|
500
500
|
|
|
501
501
|
# Borrow WETH against the cbETH collateral; debt is now open.
|
|
502
502
|
sim.execute([borrow_action(BORROW_AMOUNT)])
|
|
503
|
-
sim.observe(
|
|
503
|
+
debt_after_borrow = sim.observe(_evault_debt_of(EVAULT_WETH, euler_account))
|
|
504
504
|
plan.append("borrow 1 WETH against the cbETH collateral")
|
|
505
505
|
|
|
506
506
|
# Re-value the market while the position is open so the balance fuse's
|
|
507
507
|
# collateral-minus-debt valuation surfaces into the vault's stored NAV.
|
|
508
508
|
sim.add_call(call=plasma_vault.update_markets_balances([EULER_MARKET]), from_=ALPHA)
|
|
509
|
-
sim.observe(
|
|
509
|
+
euler_value_open_obs = sim.observe(
|
|
510
|
+
plasma_vault.total_assets_in_market(EULER_MARKET)
|
|
511
|
+
)
|
|
510
512
|
plan.append("re-value the open Euler position")
|
|
511
513
|
|
|
512
514
|
# Repay the full debt. MAX_UINT256 caps at the outstanding amount; with no time
|
|
513
515
|
# advance in this run, interest is negligible and the borrowed WETH covers it.
|
|
514
516
|
# (Advancing time would accrue interest, requiring a small extra WETH buffer.)
|
|
515
517
|
sim.execute([repay_action(Amount(MAX_UINT256))])
|
|
516
|
-
sim.observe(
|
|
518
|
+
debt_after_repay = sim.observe(_evault_debt_of(EVAULT_WETH, euler_account))
|
|
517
519
|
plan.append("repay the full WETH debt")
|
|
518
520
|
|
|
519
521
|
# Unwind in reverse of setup: disable the WETH controller, then the cbETH
|
|
@@ -529,8 +531,10 @@ def run_simulation(web3: Web3) -> SimulationResult:
|
|
|
529
531
|
# to fully exit the position, then re-value the market so the exit surfaces.
|
|
530
532
|
sim.execute([withdraw_collateral_action(Amount(MAX_UINT256))])
|
|
531
533
|
sim.add_call(call=plasma_vault.update_markets_balances([EULER_MARKET]), from_=ALPHA)
|
|
532
|
-
sim.observe(
|
|
533
|
-
|
|
534
|
+
euler_value_closed_obs = sim.observe(
|
|
535
|
+
plasma_vault.total_assets_in_market(EULER_MARKET)
|
|
536
|
+
)
|
|
537
|
+
vault_cbeth_closed = sim.observe(cbeth.balance_of(preview.plasma_vault))
|
|
534
538
|
plan.append("withdraw cbETH; the Euler position is fully exited")
|
|
535
539
|
|
|
536
540
|
log.info("transaction plan:")
|
|
@@ -544,29 +548,29 @@ def run_simulation(web3: Web3) -> SimulationResult:
|
|
|
544
548
|
# is the single eth_simulateV1 call that executed it. From here we read the
|
|
545
549
|
# recorded observations back and verify the outcome, failing loud on any
|
|
546
550
|
# mismatch. ─────────────────────────────────────────────────────────────
|
|
547
|
-
cbeth_price =
|
|
548
|
-
weth_price =
|
|
551
|
+
cbeth_price = cbeth_price_obs.value
|
|
552
|
+
weth_price = weth_price_obs.value
|
|
549
553
|
_check(cbeth_price.amount > 0, "cbETH has no oracle price -- vault cannot value it")
|
|
550
554
|
_check(weth_price.amount > 0, "WETH has no oracle price -- vault cannot value it")
|
|
551
555
|
|
|
552
556
|
# No unrelated permissions: the granted set is EXACTLY the two substrates we
|
|
553
557
|
# built, and nothing else. Compare as sets (grant order is not guaranteed).
|
|
554
|
-
granted = {bytes(s) for s in
|
|
558
|
+
granted = {bytes(s) for s in granted_substrates.value}
|
|
555
559
|
_check(
|
|
556
560
|
granted == {bytes(s) for s in market_substrates()},
|
|
557
561
|
f"unexpected market substrates granted: {sorted(s.hex() for s in granted)}",
|
|
558
562
|
)
|
|
559
563
|
|
|
560
564
|
_check(
|
|
561
|
-
|
|
565
|
+
vault_cbeth_funded.value >= COLLATERAL_FUND_AMOUNT,
|
|
562
566
|
"vault did not receive the cbETH collateral",
|
|
563
567
|
)
|
|
564
568
|
# borrow opened debt; repay cleared it -- read straight from the eWETH eVault.
|
|
565
|
-
debt_open =
|
|
569
|
+
debt_open = debt_after_borrow.value
|
|
566
570
|
_check(debt_open >= BORROW_AMOUNT, f"borrow did not open debt: {debt_open}")
|
|
567
571
|
_check(
|
|
568
|
-
|
|
569
|
-
f"repay did not clear debt: {
|
|
572
|
+
debt_after_repay.value == 0,
|
|
573
|
+
f"repay did not clear debt: {debt_after_repay.value}",
|
|
570
574
|
)
|
|
571
575
|
# The balance fuse nets the debt against the collateral. The open position is
|
|
572
576
|
# worth the supplied cbETH MINUS the WETH debt, in the vault's WETH terms. To
|
|
@@ -575,7 +579,7 @@ def run_simulation(web3: Web3) -> SimulationResult:
|
|
|
575
579
|
# debt: too narrow a gap means the debt was ignored (a bare "> 0" or "< value"
|
|
576
580
|
# would miss it, since the collateral valuation rounds down), too wide means it
|
|
577
581
|
# was double-counted. The band is +/-50% of one debt to absorb rounding.
|
|
578
|
-
euler_value_open =
|
|
582
|
+
euler_value_open = euler_value_open_obs.value
|
|
579
583
|
collateral_value = (
|
|
580
584
|
COLLATERAL_SUPPLY_AMOUNT * cbeth_price.amount // weth_price.amount
|
|
581
585
|
)
|
|
@@ -589,14 +593,14 @@ def run_simulation(web3: Web3) -> SimulationResult:
|
|
|
589
593
|
# Fully exited after the withdraw: the Euler market values to dust, and the
|
|
590
594
|
# supplied cbETH is back on the vault (the ~2 cbETH idle buffer plus the ~10
|
|
591
595
|
# withdrawn -> roughly the funded amount, minus ERC4626 round-down dust).
|
|
592
|
-
euler_value_closed =
|
|
596
|
+
euler_value_closed = euler_value_closed_obs.value
|
|
593
597
|
_check(
|
|
594
598
|
euler_value_closed <= EULER_EXIT_DUST,
|
|
595
599
|
f"Euler position not fully exited: market value {euler_value_closed}",
|
|
596
600
|
)
|
|
597
601
|
_check(
|
|
598
|
-
|
|
599
|
-
f"withdrawn cbETH not returned to the vault: {
|
|
602
|
+
vault_cbeth_closed.value >= COLLATERAL_FUND_AMOUNT - EULER_EXIT_DUST,
|
|
603
|
+
f"withdrawn cbETH not returned to the vault: {vault_cbeth_closed.value}",
|
|
600
604
|
)
|
|
601
605
|
|
|
602
606
|
log.info(
|
|
@@ -605,7 +609,7 @@ def run_simulation(web3: Web3) -> SimulationResult:
|
|
|
605
609
|
cbeth_price.amount,
|
|
606
610
|
weth_price.amount,
|
|
607
611
|
debt_open,
|
|
608
|
-
|
|
612
|
+
debt_after_repay.value,
|
|
609
613
|
euler_value_open,
|
|
610
614
|
euler_value_closed,
|
|
611
615
|
result.gas_used,
|
{ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev156}/examples/advanced_euler_v2_looping_arbitrum.py
RENAMED
|
@@ -409,10 +409,10 @@ def run_simulation(web3: Web3) -> SimulationResult:
|
|
|
409
409
|
)
|
|
410
410
|
# update_callback_handler exercises FUSE_MANAGER_ROLE, held by OWNER above.
|
|
411
411
|
|
|
412
|
-
sim.observe(
|
|
413
|
-
sim.observe(
|
|
414
|
-
sim.observe(
|
|
415
|
-
sim.observe(
|
|
412
|
+
wbtc_price = sim.observe(price_manager.get_asset_price(ARBITRUM_WBTC))
|
|
413
|
+
usdc_price = sim.observe(price_manager.get_asset_price(ARBITRUM_USDC))
|
|
414
|
+
euler_substrates = sim.observe(vault.get_market_substrates(EULER_MARKET))
|
|
415
|
+
swapper_substrates = sim.observe(vault.get_market_substrates(SWAPPER_MARKET))
|
|
416
416
|
|
|
417
417
|
# Simulation-only seed. In production WBTC arrives through normal deposits.
|
|
418
418
|
sim.add_call(
|
|
@@ -423,56 +423,54 @@ def run_simulation(web3: Web3) -> SimulationResult:
|
|
|
423
423
|
sim.execute([build_loop_action()])
|
|
424
424
|
|
|
425
425
|
sim.add_call(call=vault.update_markets_balances([EULER_MARKET]), from_=ALPHA)
|
|
426
|
-
sim.observe(
|
|
427
|
-
"collateral_shares",
|
|
426
|
+
collateral_shares = sim.observe(
|
|
428
427
|
ERC20(EVAULT_WBTC).balance_of(euler_account),
|
|
429
428
|
)
|
|
430
|
-
sim.observe(
|
|
431
|
-
"usdc_debt",
|
|
429
|
+
usdc_debt = sim.observe(
|
|
432
430
|
_debt_of(EVAULT_USDC, euler_account),
|
|
433
431
|
)
|
|
434
|
-
sim.observe(
|
|
435
|
-
sim.observe(
|
|
436
|
-
sim.observe(
|
|
437
|
-
sim.observe(
|
|
432
|
+
vault_wbtc = sim.observe(wbtc.balance_of(preview.plasma_vault))
|
|
433
|
+
vault_usdc = sim.observe(usdc.balance_of(preview.plasma_vault))
|
|
434
|
+
euler_value = sim.observe(vault.total_assets_in_market(EULER_MARKET))
|
|
435
|
+
total_assets = sim.observe(vault.total_assets())
|
|
438
436
|
|
|
439
437
|
result = sim.run()
|
|
440
438
|
_assert_all_success(result)
|
|
441
439
|
|
|
442
|
-
_check(
|
|
443
|
-
_check(
|
|
440
|
+
_check(wbtc_price.value.amount > 0, "WBTC has no oracle price")
|
|
441
|
+
_check(usdc_price.value.amount > 0, "USDC has no oracle price")
|
|
444
442
|
_check(
|
|
445
|
-
{bytes(value) for value in
|
|
443
|
+
{bytes(value) for value in euler_substrates.value}
|
|
446
444
|
== {bytes(value) for value in euler_market_substrates()},
|
|
447
445
|
"Euler substrates differ from the narrow WBTC/USDC grant",
|
|
448
446
|
)
|
|
449
447
|
_check(
|
|
450
|
-
{bytes(value) for value in
|
|
448
|
+
{bytes(value) for value in swapper_substrates.value}
|
|
451
449
|
== {bytes(value) for value in swapper_market_substrates()},
|
|
452
450
|
"swapper substrates differ from the narrow pair/target grant",
|
|
453
451
|
)
|
|
454
|
-
_check(
|
|
452
|
+
_check(collateral_shares.value > 0, "Euler collateral was not supplied")
|
|
455
453
|
_check(
|
|
456
|
-
|
|
457
|
-
f"Euler USDC debt was not opened: {
|
|
454
|
+
usdc_debt.value >= BORROW_USDC,
|
|
455
|
+
f"Euler USDC debt was not opened: {usdc_debt.value}",
|
|
458
456
|
)
|
|
459
|
-
_check(
|
|
457
|
+
_check(vault_usdc.value == 0, "borrowed USDC was not fully swapped")
|
|
460
458
|
_check(
|
|
461
|
-
|
|
462
|
-
f"unexpected WBTC residue after flash repayment: {
|
|
459
|
+
vault_wbtc.value < Amount(1_000_000),
|
|
460
|
+
f"unexpected WBTC residue after flash repayment: {vault_wbtc.value}",
|
|
463
461
|
)
|
|
464
|
-
_check(
|
|
462
|
+
_check(euler_value.value > 0, "Euler net position has no value")
|
|
465
463
|
_check(
|
|
466
|
-
INITIAL_WBTC * 98 // 100 <=
|
|
467
|
-
f"loop NAV moved outside the 2% safety band: {
|
|
464
|
+
INITIAL_WBTC * 98 // 100 <= total_assets.value <= INITIAL_WBTC,
|
|
465
|
+
f"loop NAV moved outside the 2% safety band: {total_assets.value}",
|
|
468
466
|
)
|
|
469
467
|
|
|
470
468
|
log.info(
|
|
471
469
|
"OK -- collateral shares=%s, USDC debt=%s, idle WBTC=%s, NAV=%s, gas=%s",
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
470
|
+
collateral_shares.value,
|
|
471
|
+
usdc_debt.value,
|
|
472
|
+
vault_wbtc.value,
|
|
473
|
+
total_assets.value,
|
|
476
474
|
result.gas_used,
|
|
477
475
|
)
|
|
478
476
|
return result
|
|
@@ -83,6 +83,7 @@ from ipor_fusion import (
|
|
|
83
83
|
CrosschainLane,
|
|
84
84
|
CrosschainSimulator,
|
|
85
85
|
HyperCoreReader,
|
|
86
|
+
LaneObservation,
|
|
86
87
|
PlasmaVault,
|
|
87
88
|
SimulationResult,
|
|
88
89
|
TimeInForce,
|
|
@@ -784,9 +785,14 @@ def redeem_command_preview(web3_arb: Web3, web3_hype: Web3) -> dict[str, int]:
|
|
|
784
785
|
}
|
|
785
786
|
|
|
786
787
|
|
|
787
|
-
def _attest(
|
|
788
|
+
def _attest(
|
|
789
|
+
flow: _Tail,
|
|
790
|
+
tag: str,
|
|
791
|
+
observation: LaneObservation,
|
|
792
|
+
*,
|
|
793
|
+
hub_idle: int = 0,
|
|
794
|
+
) -> int:
|
|
788
795
|
lane, hub = flow.lane, flow.hub
|
|
789
|
-
observation = flow.simulator.results[HYPEREVM_CHAIN_ID].get(observation_label)
|
|
790
796
|
proposal = lane.attestation(
|
|
791
797
|
observation,
|
|
792
798
|
remote_block=flow.spoke.current_block_number,
|
|
@@ -816,18 +822,14 @@ def _attest(flow: _Tail, tag: str, observation_label: str, *, hub_idle: int = 0)
|
|
|
816
822
|
from_=ALPHA,
|
|
817
823
|
label=f"refresh_54_{tag}",
|
|
818
824
|
)
|
|
819
|
-
flow.simulator.observe(
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
flow.simulator.observe(
|
|
823
|
-
ARBITRUM_CHAIN_ID,
|
|
824
|
-
f"market_total_{tag}",
|
|
825
|
-
flow.hub_vault.total_assets_in_market(CROSSCHAIN_MARKET),
|
|
825
|
+
executor_nav = flow.simulator.observe(ARBITRUM_CHAIN_ID, lane.get_balance())
|
|
826
|
+
market_nav = flow.simulator.observe(
|
|
827
|
+
ARBITRUM_CHAIN_ID, flow.hub_vault.total_assets_in_market(CROSSCHAIN_MARKET)
|
|
826
828
|
)
|
|
827
|
-
|
|
829
|
+
flow.relay()
|
|
828
830
|
attested = int(observation.accounted_balance)
|
|
829
|
-
executor_balance =
|
|
830
|
-
market_total =
|
|
831
|
+
executor_balance = executor_nav.value
|
|
832
|
+
market_total = market_nav.value
|
|
831
833
|
_check(
|
|
832
834
|
executor_balance == attested + hub_idle,
|
|
833
835
|
f"{tag}: executor NAV {executor_balance} != attested {attested} + idle {hub_idle}",
|
|
@@ -908,23 +910,23 @@ def recall_attest_claim(web3_arb: Web3, web3_hype: Web3) -> dict[str, int]:
|
|
|
908
910
|
hub.execute([lane.recall(amount=remote_idle, min_return=remote_idle)])
|
|
909
911
|
flow.advance(BRIDGE_LATENCY)
|
|
910
912
|
flow.relay()
|
|
911
|
-
simulator.observe(ARBITRUM_CHAIN_ID,
|
|
912
|
-
simulator.observe(
|
|
913
|
-
ARBITRUM_CHAIN_ID,
|
|
913
|
+
idle_after_return = simulator.observe(ARBITRUM_CHAIN_ID, lane.idle_ledger())
|
|
914
|
+
settled_after_return = simulator.observe(
|
|
915
|
+
ARBITRUM_CHAIN_ID, lane.settled_remote_balance()
|
|
914
916
|
)
|
|
915
|
-
simulator.observe(
|
|
916
|
-
ARBITRUM_CHAIN_ID,
|
|
917
|
+
pending_after_return = simulator.observe(
|
|
918
|
+
ARBITRUM_CHAIN_ID, lane.pending_transfer_count()
|
|
917
919
|
)
|
|
918
|
-
simulator.observe(HYPEREVM_CHAIN_ID,
|
|
919
|
-
|
|
920
|
-
idle =
|
|
921
|
-
settled =
|
|
920
|
+
observation_after_return = simulator.observe(HYPEREVM_CHAIN_ID, lane.observation())
|
|
921
|
+
flow.relay()
|
|
922
|
+
idle = idle_after_return.value
|
|
923
|
+
settled = settled_after_return.value
|
|
922
924
|
_check(
|
|
923
925
|
idle == int(remote_idle),
|
|
924
926
|
f"the hub credited {idle}, the spoke sent {remote_idle} (no token fee on this lane)",
|
|
925
927
|
)
|
|
926
928
|
_check(
|
|
927
|
-
|
|
929
|
+
pending_after_return.value == 0,
|
|
928
930
|
"a transfer is still pending",
|
|
929
931
|
)
|
|
930
932
|
# The settled bucket keeps exactly the value of the residual shares; the
|
|
@@ -937,7 +939,7 @@ def recall_attest_claim(web3_arb: Web3, web3_hype: Web3) -> dict[str, int]:
|
|
|
937
939
|
flow.numbers.update({"recalled": idle, "settled_after_return": settled})
|
|
938
940
|
|
|
939
941
|
flow.advance(int(lane.executor.min_update_interval().call(hub_ctx)))
|
|
940
|
-
residual = _attest(flow, "residual",
|
|
942
|
+
residual = _attest(flow, "residual", observation_after_return.value, hub_idle=idle)
|
|
941
943
|
_check(
|
|
942
944
|
residual == settled, f"the residual attestation {residual} != settled {settled}"
|
|
943
945
|
)
|
|
@@ -948,32 +950,31 @@ def recall_attest_claim(web3_arb: Web3, web3_hype: Web3) -> dict[str, int]:
|
|
|
948
950
|
from_=ALPHA,
|
|
949
951
|
label="refresh_54_final",
|
|
950
952
|
)
|
|
951
|
-
simulator.observe(
|
|
952
|
-
ARBITRUM_CHAIN_ID,
|
|
953
|
+
vault_usdc_after = simulator.observe(
|
|
954
|
+
ARBITRUM_CHAIN_ID, flow.hub_usdc.balance_of(HUB_VAULT)
|
|
953
955
|
)
|
|
954
|
-
simulator.observe(ARBITRUM_CHAIN_ID,
|
|
955
|
-
simulator.observe(ARBITRUM_CHAIN_ID,
|
|
956
|
-
simulator.observe(
|
|
956
|
+
idle_after_claim = simulator.observe(ARBITRUM_CHAIN_ID, lane.idle_ledger())
|
|
957
|
+
nav_final = simulator.observe(ARBITRUM_CHAIN_ID, lane.get_balance())
|
|
958
|
+
market_final = simulator.observe(
|
|
957
959
|
ARBITRUM_CHAIN_ID,
|
|
958
|
-
"market_final",
|
|
959
960
|
flow.hub_vault.total_assets_in_market(CROSSCHAIN_MARKET),
|
|
960
961
|
)
|
|
961
|
-
|
|
962
|
-
vault_after =
|
|
962
|
+
flow.relay()
|
|
963
|
+
vault_after = vault_usdc_after.value
|
|
963
964
|
_check(
|
|
964
965
|
vault_after == vault_usdc_before + idle,
|
|
965
966
|
f"hub vault USDC {vault_after} != {vault_usdc_before} + {idle}",
|
|
966
967
|
)
|
|
967
968
|
_check(
|
|
968
|
-
|
|
969
|
+
idle_after_claim.value == 0,
|
|
969
970
|
"the idle ledger is not empty after the claim",
|
|
970
971
|
)
|
|
971
972
|
_check(
|
|
972
|
-
|
|
973
|
+
nav_final.value == residual,
|
|
973
974
|
"executor NAV != the residual",
|
|
974
975
|
)
|
|
975
976
|
_check(
|
|
976
|
-
|
|
977
|
+
market_final.value == residual,
|
|
977
978
|
"market 54 total != the residual",
|
|
978
979
|
)
|
|
979
980
|
log.info(
|