ipor-fusion 4.0.0.dev43__tar.gz → 4.0.0.dev45__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.dev43 → ipor_fusion-4.0.0.dev45}/PKG-INFO +1 -1
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/pyproject.toml +1 -1
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/cache.py +32 -12
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/explorer.py +4 -4
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/vault_data.py +11 -10
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/fixtures/vault_info_golden/clean.json +2 -1
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/fixtures/vault_info_golden/clean.txt +1 -1
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/fixtures/vault_info_golden/rich.json +4 -2
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/fixtures/vault_info_golden/rich.txt +3 -3
- ipor_fusion-4.0.0.dev45/tests/test_cache.py +99 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/test_cli_vault_info_golden.py +5 -1
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/test_explorer.py +1 -1
- ipor_fusion-4.0.0.dev43/tests/test_cache.py +0 -44
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/.claude-plugin/marketplace.json +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/.claude-plugin/plugin.json +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/.env.example +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/.gemini/settings.json +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/.git-blame-ignore-revs +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/.github/workflows/cd.yml +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/.github/workflows/ci.yml +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/.github/workflows/commit-lint.yml +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/.github/workflows/notify-slack.yml +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/.github/workflows/python-build.yml +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/.github/workflows/python-publish.yml +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/.github/workflows/release.yml +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/.github/workflows/report-slack.yml +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/.gitignore +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/.mcp.json +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/.pre-commit-config.yaml +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/.python-version +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/AGENTS.md +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/CHANGELOG.md +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/CLAUDE.md +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/CODEOWNERS +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/LICENSE +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/README.md +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/build.sh +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/examples/CONTRIBUTING.md +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/examples/README.md +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/examples/advanced_euler_v2_credit_market_base.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/examples/advanced_euler_v2_looping_arbitrum.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/examples/composed_hypercore_flow_arbitrum_hyperevm.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/examples/crosschain_ccip_usdc_arbitrum_hyperevm.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/examples/external_state_margin_leg_base.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/examples/simple_aave_v3_supply_base.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/pytest.ini +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/scripts/commit-check.sh +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/scripts/crosschain_readiness.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/skills/ipor-deploy-vault/SKILL.md +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/about.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/chains.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/cli/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/cli/changelog_cmd.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/cli/config_cmd.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/cli/config_store.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/cli/main.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/cli/market_cmd.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/cli/options.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/cli/oracle_mapping_view.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/cli/vault_cmd.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/cli/vault_health.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/cli/vault_info_view.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/cli/vault_markets_view.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/cli/vault_rendering.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/config/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/config/roles.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/core/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/core/access.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/core/address.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/core/context.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/core/contract.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/core/erc20.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/core/external_state_executor.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/core/fee_manager.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/core/fuse_action.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/core/fusion_factory.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/core/logs.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/core/multicall.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/core/oracle.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/core/plasma_vault.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/core/rewards_manager.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/core/simulation.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/core/withdraw_manager.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/crosschain/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/crosschain/ccip/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/crosschain/ccip/chainlink.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/crosschain/ccip/codec.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/crosschain/ccip/contracts.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/crosschain/ccip/events.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/crosschain/ccip/indexer.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/crosschain/ccip/lane.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/crosschain/ccip/quote.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/crosschain/ccip/transport.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/crosschain/contracts.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/crosschain/discovery.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/crosschain/errors.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/crosschain/lane.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/crosschain/logs.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/crosschain/messages.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/crosschain/simulation.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/crosschain/stargate/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/crosschain/stargate/contracts.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/crosschain/stargate/lane.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/crosschain/stargate/layerzero.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/crosschain/stargate/transport.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/crosschain/transport.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/dependency_graph.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/entry_cli.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/entry_mcp.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/errors.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/field_docs.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/fuses/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/fuses/aave_v3.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/fuses/async_action.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/fuses/base.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/fuses/compound_v3.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/fuses/crosschain/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/fuses/crosschain/base.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/fuses/crosschain/ccip.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/fuses/crosschain/stargate.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/fuses/erc4626.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/fuses/euler_v2.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/fuses/events.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/fuses/external_state.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/fuses/external_state_events.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/fuses/fluid_instadapp.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/fuses/gearbox_v3.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/fuses/hypercore.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/fuses/hypercore_events.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/fuses/merkl.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/fuses/morpho.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/fuses/ramses_v2.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/fuses/uniswap_v3.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/fuses/universal.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/guide/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/guide/architecture.md +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/guide/glossary.md +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/guide/invariants.md +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/guide/quickstart.md +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/market_ids.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/market_info.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/mcp/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/mcp/guide.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/mcp/models.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/mcp/server.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/morpho_api.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/py.typed +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/readers/__init__.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/readers/aave_v3.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/readers/aave_v4.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/readers/compound_v3.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/readers/hypercore.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/readers/hypercore_outcome.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/readers/lending_health.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/readers/morpho.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/readers/oracle_mapping.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/readers/position_manager.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/readers/ramses_v2.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/readers/uniswap_v3.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/readers/unpriceable.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/substrates.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/types.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/validation.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/vault_info.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/UniswapV3UniversalRouter.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/_crosschain.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/_crosschain_lifecycle.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/_crosschain_recovery.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/_euler_v2.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/_foundry.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/_guide.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/_hypercore_model.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/_hypercore_shadow.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/_multicall.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/_simulate.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/_validation.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/addresses.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/conftest.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/constants.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/fixtures/hypercore_hip3_flow.json +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/fixtures/hypercore_market55.json +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/test_about.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/test_access_manager.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/test_chains.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/test_cli_changelog.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/test_cli_commands.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/test_cli_config_store.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/test_cli_helpers.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/test_cli_hypercore.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/test_cli_lending_health.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/test_cli_market_morpho_blue.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/test_cli_oracle_mapping.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/test_cli_pending_requests_scan.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/test_cli_role_accounts.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/test_cli_vault_predates_getters.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/test_cli_vault_validate.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/test_contract_call.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/test_contract_encoder.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/test_crosschain_ccip_events.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/test_crosschain_ccip_indexer.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/test_crosschain_ccip_quote.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/test_crosschain_chainlink.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/test_crosschain_contracts.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/test_crosschain_encoding.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/test_crosschain_errors.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/test_crosschain_lane.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/test_crosschain_layering.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/test_crosschain_readiness.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/test_crosschain_readiness_report.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/test_crosschain_transport.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/test_erc20.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/test_examples_composed_hypercore.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/test_examples_crosschain.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/test_examples_euler_looping_arbitrum.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/test_examples_vaults.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/test_external_state_events.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/test_external_state_executor.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/test_fee_manager.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/test_fuse_encoding.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/test_guide.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/test_hypercore_deployment.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/test_hypercore_events.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/test_hypercore_flow.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/test_hypercore_live.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/test_hypercore_outcome.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/test_hypercore_readers.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/test_hypercore_simulation.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/test_logs.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/test_mcp_hypercore.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/test_mcp_models.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/test_mcp_server.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/test_mcp_vault_validate.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/test_multicall.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/test_oracle.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/test_oracle_mapping.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/test_plasma_vault.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/test_reader_encoding.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/test_readiness_selection.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/test_revert_decoding.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/test_rewards_manager.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/test_roles.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/test_sdk_layering.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/test_simulate_borrow_aave_v3_mainnet.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/test_simulate_claim_merkl_wrapper_base.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/test_simulate_claim_morpho_rewards.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/test_simulate_crosschain_canary.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/test_simulate_crosschain_hypercore.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/test_simulate_crosschain_hyperevm.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/test_simulate_crosschain_lifecycle.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/test_simulate_euler_v2_batch_base.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/test_simulate_euler_v2_credit_market_base.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/test_simulate_euler_v2_swap_deploy_base.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/test_simulate_euler_v2_swap_reconfigure_base.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/test_simulate_euler_v2_swap_registry_base.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/test_simulate_examples_arbitrum.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/test_simulate_examples_base.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/test_simulate_examples_crosschain.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/test_simulate_external_state_executor_base.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/test_simulate_fusion_factory_clone_base.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/test_simulate_looping_aave_v3_base.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/test_simulate_looping_morpho_blue_base.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/test_simulate_morpho_blue.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/test_simulate_pilot_v3.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/test_simulate_pilot_v4_uniswap.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/test_simulate_pilot_v5_ramses.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/test_simulate_pilot_v5_scheduled.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/test_simulate_plasma_vault_setters_base.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/test_simulate_readers_integration.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/test_simulate_release_funds.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/test_simulate_swap_on_base.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/test_simulate_vault_from_scratch_base.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/test_solidity_compiler.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/test_solidity_mirrors.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/test_substrates.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/test_types_and_events.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/test_unpriceable_tokens.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/test_validation.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/test_vault_dep_graph.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/test_vault_info_onchain_base.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/test_vault_info_onchain_base_no_withdraw_manager.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/test_vault_simulator_blocks.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/test_web3_context.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/test_withdraw_manager.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/typed_client/consumer.py +0 -0
- {ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/uv.lock +0 -0
|
@@ -9,6 +9,8 @@ import os
|
|
|
9
9
|
import threading
|
|
10
10
|
from pathlib import Path
|
|
11
11
|
|
|
12
|
+
from eth_typing import HexAddress
|
|
13
|
+
|
|
12
14
|
|
|
13
15
|
def _xdg_cache_home() -> Path:
|
|
14
16
|
return Path(os.environ.get("XDG_CACHE_HOME", Path.home() / ".cache"))
|
|
@@ -21,11 +23,35 @@ DEPLOYMENT_CACHE_FILE = CACHE_DIR / "deployment_cache.json"
|
|
|
21
23
|
_cache_lock = threading.Lock()
|
|
22
24
|
|
|
23
25
|
|
|
26
|
+
def cache_key(scope: object, address: HexAddress) -> str:
|
|
27
|
+
"""``<scope>:<address>`` with the address lowercased: one entry per address
|
|
28
|
+
whatever spelling the caller holds. ``scope`` is a chain id or a field name
|
|
29
|
+
(``symbol``, ``decimals``)."""
|
|
30
|
+
return f"{scope}:{address.lower()}"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _canonical(entries: dict) -> dict:
|
|
34
|
+
"""Entries keyed by `cache_key` spelling. Caches written before 4.0 keyed
|
|
35
|
+
the address as the caller spelled it; an entry already canonical wins over
|
|
36
|
+
a differently spelled duplicate."""
|
|
37
|
+
out: dict = {}
|
|
38
|
+
for key, value in entries.items():
|
|
39
|
+
scope, sep, address = key.rpartition(":")
|
|
40
|
+
canonical = cache_key(scope, address) if sep else key
|
|
41
|
+
if canonical == key or canonical not in out:
|
|
42
|
+
out[canonical] = value
|
|
43
|
+
return out
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _read(path: Path) -> dict:
|
|
47
|
+
if not path.exists():
|
|
48
|
+
return {}
|
|
49
|
+
return _canonical(json.loads(path.read_text(encoding="utf-8")))
|
|
50
|
+
|
|
51
|
+
|
|
24
52
|
def load_contract_cache() -> dict[str, str]:
|
|
25
53
|
with _cache_lock:
|
|
26
|
-
|
|
27
|
-
return {}
|
|
28
|
-
return json.loads(CACHE_FILE.read_text(encoding="utf-8")) # type: ignore[no-any-return]
|
|
54
|
+
return _read(CACHE_FILE)
|
|
29
55
|
|
|
30
56
|
|
|
31
57
|
def save_contract_cache(cache: dict[str, str]) -> None:
|
|
@@ -37,9 +63,7 @@ def save_contract_cache(cache: dict[str, str]) -> None:
|
|
|
37
63
|
def update_contract_cache(key: str, value: str) -> None:
|
|
38
64
|
with _cache_lock:
|
|
39
65
|
CACHE_DIR.mkdir(parents=True, exist_ok=True)
|
|
40
|
-
existing: dict[str, str] =
|
|
41
|
-
if CACHE_FILE.exists():
|
|
42
|
-
existing = json.loads(CACHE_FILE.read_text(encoding="utf-8"))
|
|
66
|
+
existing: dict[str, str] = _read(CACHE_FILE)
|
|
43
67
|
existing[key] = value
|
|
44
68
|
CACHE_FILE.write_text(json.dumps(existing, indent=2), encoding="utf-8")
|
|
45
69
|
|
|
@@ -49,17 +73,13 @@ _deployment_cache_lock = threading.Lock()
|
|
|
49
73
|
|
|
50
74
|
def load_deployment_cache() -> dict[str, dict[str, int]]:
|
|
51
75
|
with _deployment_cache_lock:
|
|
52
|
-
|
|
53
|
-
return {}
|
|
54
|
-
return json.loads(DEPLOYMENT_CACHE_FILE.read_text(encoding="utf-8")) # type: ignore[no-any-return]
|
|
76
|
+
return _read(DEPLOYMENT_CACHE_FILE)
|
|
55
77
|
|
|
56
78
|
|
|
57
79
|
def update_deployment_cache(key: str, block: int, timestamp: int) -> None:
|
|
58
80
|
with _deployment_cache_lock:
|
|
59
81
|
CACHE_DIR.mkdir(parents=True, exist_ok=True)
|
|
60
|
-
existing: dict[str, dict[str, int]] =
|
|
61
|
-
if DEPLOYMENT_CACHE_FILE.exists():
|
|
62
|
-
existing = json.loads(DEPLOYMENT_CACHE_FILE.read_text(encoding="utf-8"))
|
|
82
|
+
existing: dict[str, dict[str, int]] = _read(DEPLOYMENT_CACHE_FILE)
|
|
63
83
|
existing[key] = {"block": block, "timestamp": timestamp}
|
|
64
84
|
DEPLOYMENT_CACHE_FILE.write_text(
|
|
65
85
|
json.dumps(existing, indent=2), encoding="utf-8"
|
|
@@ -12,7 +12,7 @@ from urllib.request import urlopen
|
|
|
12
12
|
|
|
13
13
|
from eth_typing import HexAddress, HexStr
|
|
14
14
|
|
|
15
|
-
from ipor_fusion.cache import load_contract_cache, update_contract_cache
|
|
15
|
+
from ipor_fusion.cache import cache_key, load_contract_cache, update_contract_cache
|
|
16
16
|
from ipor_fusion.types import ChainId, TxHash
|
|
17
17
|
|
|
18
18
|
ETHERSCAN_V2_URL = "https://api.etherscan.io/v2/api"
|
|
@@ -44,13 +44,13 @@ def get_contract_name(
|
|
|
44
44
|
chain_id: ChainId, address: HexAddress, api_key: str | None = None
|
|
45
45
|
) -> str:
|
|
46
46
|
cache = load_contract_cache()
|
|
47
|
-
|
|
47
|
+
key = cache_key(chain_id, address)
|
|
48
48
|
|
|
49
|
-
if cached := cache.get(
|
|
49
|
+
if cached := cache.get(key):
|
|
50
50
|
return cached
|
|
51
51
|
|
|
52
52
|
if name := _fetch_contract_name(chain_id, address, api_key):
|
|
53
|
-
update_contract_cache(
|
|
53
|
+
update_contract_cache(key, name)
|
|
54
54
|
return name
|
|
55
55
|
return ""
|
|
56
56
|
|
|
@@ -25,6 +25,7 @@ from web3.exceptions import ContractLogicError, TimeExhausted, Web3RPCError
|
|
|
25
25
|
from web3.types import ChecksumAddress, HexStr
|
|
26
26
|
|
|
27
27
|
from ipor_fusion.cache import (
|
|
28
|
+
cache_key,
|
|
28
29
|
load_contract_cache,
|
|
29
30
|
load_deployment_cache,
|
|
30
31
|
update_contract_cache,
|
|
@@ -325,14 +326,14 @@ def resolve_token_symbol(ctx: Web3Context, address: HexAddress) -> str:
|
|
|
325
326
|
"""ERC-20 symbol, cached; ``"no contract"`` when nothing is deployed at
|
|
326
327
|
``address`` and ``""`` when the symbol cannot be read."""
|
|
327
328
|
cache = load_contract_cache()
|
|
328
|
-
|
|
329
|
-
if cached := cache.get(
|
|
329
|
+
key = cache_key("symbol", address)
|
|
330
|
+
if cached := cache.get(key):
|
|
330
331
|
return cached
|
|
331
332
|
|
|
332
333
|
checksum = Web3.to_checksum_address(address)
|
|
333
334
|
code = ctx.web3.eth.get_code(checksum)
|
|
334
335
|
if not code or code == b"":
|
|
335
|
-
update_contract_cache(
|
|
336
|
+
update_contract_cache(key, _NO_CONTRACT)
|
|
336
337
|
return _NO_CONTRACT
|
|
337
338
|
|
|
338
339
|
try:
|
|
@@ -340,15 +341,15 @@ def resolve_token_symbol(ctx: Web3Context, address: HexAddress) -> str:
|
|
|
340
341
|
except Exception:
|
|
341
342
|
symbol = ""
|
|
342
343
|
if symbol:
|
|
343
|
-
update_contract_cache(
|
|
344
|
+
update_contract_cache(key, symbol)
|
|
344
345
|
return symbol
|
|
345
346
|
|
|
346
347
|
|
|
347
348
|
def resolve_token_decimals(ctx: Web3Context, address: HexAddress) -> int | None:
|
|
348
349
|
"""Resolve ERC-20 decimals, cached. Returns None if address is not a contract."""
|
|
349
350
|
cache = load_contract_cache()
|
|
350
|
-
|
|
351
|
-
if cached := cache.get(
|
|
351
|
+
key = cache_key("decimals", address)
|
|
352
|
+
if cached := cache.get(key):
|
|
352
353
|
return int(cached)
|
|
353
354
|
|
|
354
355
|
checksum = Web3.to_checksum_address(address)
|
|
@@ -360,7 +361,7 @@ def resolve_token_decimals(ctx: Web3Context, address: HexAddress) -> int | None:
|
|
|
360
361
|
decimals = ERC20(ctx, checksum).decimals().call()
|
|
361
362
|
except Exception:
|
|
362
363
|
return None
|
|
363
|
-
update_contract_cache(
|
|
364
|
+
update_contract_cache(key, str(decimals))
|
|
364
365
|
return decimals
|
|
365
366
|
|
|
366
367
|
|
|
@@ -1053,9 +1054,9 @@ def fetch_deployment_info(
|
|
|
1053
1054
|
Base/Optimism on the free Etherscan tier). ``None`` means either success or
|
|
1054
1055
|
a benign empty response.
|
|
1055
1056
|
"""
|
|
1056
|
-
|
|
1057
|
+
key = cache_key(chain_id, vault_address)
|
|
1057
1058
|
cache = load_deployment_cache()
|
|
1058
|
-
if entry := cache.get(
|
|
1059
|
+
if entry := cache.get(key):
|
|
1059
1060
|
return entry["block"], entry["timestamp"], None
|
|
1060
1061
|
|
|
1061
1062
|
tx_hash, error = get_deployment_tx(chain_id, vault_address, api_key)
|
|
@@ -1067,7 +1068,7 @@ def fetch_deployment_info(
|
|
|
1067
1068
|
block_number: int = tx["blockNumber"]
|
|
1068
1069
|
block_info = ctx.web3.eth.get_block(block_number)
|
|
1069
1070
|
timestamp: int = block_info["timestamp"]
|
|
1070
|
-
update_deployment_cache(
|
|
1071
|
+
update_deployment_cache(key, block_number, timestamp)
|
|
1071
1072
|
return block_number, timestamp, None
|
|
1072
1073
|
except Exception:
|
|
1073
1074
|
return None, None, "rpc-fetch-failed"
|
{ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/fixtures/vault_info_golden/clean.txt
RENAMED
|
@@ -36,7 +36,7 @@ Balance Fuses (1):
|
|
|
36
36
|
|
|
37
37
|
Substrates per Market:
|
|
38
38
|
AAVE_V3 (1):
|
|
39
|
-
0x00000000000000000000000000000000000000a1 (symbol=USDC)
|
|
39
|
+
0x00000000000000000000000000000000000000a1 (symbol=USDC, contract=FiatTokenProxy)
|
|
40
40
|
|
|
41
41
|
ERC20 Balances (vault holdings):
|
|
42
42
|
(no ERC20_VAULT_BALANCE market)
|
{ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/fixtures/vault_info_golden/rich.json
RENAMED
|
@@ -143,7 +143,8 @@
|
|
|
143
143
|
"AAVE_V3 (1)": [
|
|
144
144
|
{
|
|
145
145
|
"address": "0x00000000000000000000000000000000000000a1",
|
|
146
|
-
"symbol": "USDC"
|
|
146
|
+
"symbol": "USDC",
|
|
147
|
+
"contract": "FiatTokenProxy"
|
|
147
148
|
}
|
|
148
149
|
],
|
|
149
150
|
"MORPHO (14)": [
|
|
@@ -155,7 +156,8 @@
|
|
|
155
156
|
"ERC20_VAULT_BALANCE (7)": [
|
|
156
157
|
{
|
|
157
158
|
"address": "0x00000000000000000000000000000000000000a1",
|
|
158
|
-
"symbol": "USDC"
|
|
159
|
+
"symbol": "USDC",
|
|
160
|
+
"contract": "FiatTokenProxy"
|
|
159
161
|
},
|
|
160
162
|
{
|
|
161
163
|
"address": "0x00000000000000000000000000000000000000b1",
|
{ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/tests/fixtures/vault_info_golden/rich.txt
RENAMED
|
@@ -53,17 +53,17 @@ Dependency Balance Graph:
|
|
|
53
53
|
|
|
54
54
|
Substrates per Market:
|
|
55
55
|
AAVE_V3 (1):
|
|
56
|
-
0x00000000000000000000000000000000000000a1 (symbol=USDC)
|
|
56
|
+
0x00000000000000000000000000000000000000a1 (symbol=USDC, contract=FiatTokenProxy)
|
|
57
57
|
MORPHO (14):
|
|
58
58
|
0xabababababababababababababababababababababababababababababababab (morpho_market_id)[0m
|
|
59
59
|
ERC20_VAULT_BALANCE (7):
|
|
60
|
-
0x00000000000000000000000000000000000000a1 (symbol=USDC)
|
|
60
|
+
0x00000000000000000000000000000000000000a1 (symbol=USDC, contract=FiatTokenProxy)
|
|
61
61
|
0x00000000000000000000000000000000000000b1 (symbol=STALE)
|
|
62
62
|
0x00000000000000000000000000000000000000b2 (symbol=NOPRICE)
|
|
63
63
|
0x00000000000000000000000000000000000000b3 (symbol=EMPTY)
|
|
64
64
|
0x00000000000000000000000000000000000000b4 (symbol=BROKEN)
|
|
65
65
|
AAVE_V3 (1):
|
|
66
|
-
0x00000000000000000000000000000000000000a1 (symbol=USDC)
|
|
66
|
+
0x00000000000000000000000000000000000000a1 (symbol=USDC, contract=FiatTokenProxy)
|
|
67
67
|
|
|
68
68
|
ERC20 Balances (vault holdings):
|
|
69
69
|
Token Symbol Balance Price Note
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
from ipor_fusion import cache
|
|
6
|
+
from ipor_fusion.cache import load_contract_cache, save_contract_cache
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@pytest.fixture(autouse=True)
|
|
10
|
+
def _patch_paths(tmp_path, monkeypatch):
|
|
11
|
+
cache_dir = tmp_path / ".cache"
|
|
12
|
+
monkeypatch.setattr(cache, "CACHE_DIR", cache_dir)
|
|
13
|
+
monkeypatch.setattr(cache, "CACHE_FILE", cache_dir / "contract_cache.json")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class TestLoadContractCache:
|
|
17
|
+
def test_returns_empty_dict_when_no_file(self):
|
|
18
|
+
assert load_contract_cache() == {}
|
|
19
|
+
|
|
20
|
+
def test_loads_existing_cache(self, tmp_path):
|
|
21
|
+
cache_dir = tmp_path / ".cache"
|
|
22
|
+
cache_dir.mkdir()
|
|
23
|
+
cache = {"0xABC": "ERC20", "0xDEF": "PlasmaVault"}
|
|
24
|
+
(cache_dir / "contract_cache.json").write_text(
|
|
25
|
+
json.dumps(cache), encoding="utf-8"
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
assert load_contract_cache() == cache
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class TestSaveContractCache:
|
|
32
|
+
def test_creates_directory_and_writes_json(self, tmp_path):
|
|
33
|
+
cache = {"0xABC": "ERC20"}
|
|
34
|
+
save_contract_cache(cache)
|
|
35
|
+
|
|
36
|
+
cache_file = tmp_path / ".cache" / "contract_cache.json"
|
|
37
|
+
assert cache_file.exists()
|
|
38
|
+
assert json.loads(cache_file.read_text(encoding="utf-8")) == cache
|
|
39
|
+
|
|
40
|
+
def test_roundtrip(self):
|
|
41
|
+
original = {"0x111": "TokenA", "0x222": "TokenB"}
|
|
42
|
+
save_contract_cache(original)
|
|
43
|
+
loaded = load_contract_cache()
|
|
44
|
+
assert loaded == original
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class TestCanonicalKeys:
|
|
48
|
+
CHECKSUM = "0xAbCdEf0123456789aBcDeF0123456789AbCdEf01"
|
|
49
|
+
|
|
50
|
+
def _write(self, tmp_path, name, entries):
|
|
51
|
+
cache_dir = tmp_path / ".cache"
|
|
52
|
+
cache_dir.mkdir(exist_ok=True)
|
|
53
|
+
(cache_dir / name).write_text(json.dumps(entries), encoding="utf-8")
|
|
54
|
+
|
|
55
|
+
def test_a_key_lowercases_only_the_address(self):
|
|
56
|
+
assert cache.cache_key("symbol", self.CHECKSUM) == (
|
|
57
|
+
"symbol:" + self.CHECKSUM.lower()
|
|
58
|
+
)
|
|
59
|
+
assert cache.cache_key(1, self.CHECKSUM) == "1:" + self.CHECKSUM.lower()
|
|
60
|
+
|
|
61
|
+
def test_a_legacy_key_spelled_by_the_caller_still_hits(self, tmp_path):
|
|
62
|
+
self._write(tmp_path, "contract_cache.json", {f"1:{self.CHECKSUM}": "Vault"})
|
|
63
|
+
assert load_contract_cache()[cache.cache_key(1, self.CHECKSUM)] == "Vault"
|
|
64
|
+
|
|
65
|
+
def test_an_already_canonical_entry_wins_over_a_duplicate(self, tmp_path):
|
|
66
|
+
self._write(
|
|
67
|
+
tmp_path,
|
|
68
|
+
"contract_cache.json",
|
|
69
|
+
{
|
|
70
|
+
f"symbol:{self.CHECKSUM.lower()}": "NEW",
|
|
71
|
+
f"symbol:{self.CHECKSUM}": "OLD",
|
|
72
|
+
},
|
|
73
|
+
)
|
|
74
|
+
assert load_contract_cache() == {f"symbol:{self.CHECKSUM.lower()}": "NEW"}
|
|
75
|
+
|
|
76
|
+
def test_an_update_rewrites_legacy_keys(self, tmp_path):
|
|
77
|
+
self._write(tmp_path, "contract_cache.json", {f"1:{self.CHECKSUM}": "Vault"})
|
|
78
|
+
cache.update_contract_cache(cache.cache_key("decimals", self.CHECKSUM), "6")
|
|
79
|
+
on_disk = json.loads(
|
|
80
|
+
(tmp_path / ".cache" / "contract_cache.json").read_text(encoding="utf-8")
|
|
81
|
+
)
|
|
82
|
+
assert on_disk == {
|
|
83
|
+
f"1:{self.CHECKSUM.lower()}": "Vault",
|
|
84
|
+
f"decimals:{self.CHECKSUM.lower()}": "6",
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
def test_deployment_entries_are_canonical(self, tmp_path, monkeypatch):
|
|
88
|
+
monkeypatch.setattr(
|
|
89
|
+
cache,
|
|
90
|
+
"DEPLOYMENT_CACHE_FILE",
|
|
91
|
+
tmp_path / ".cache" / "deployment_cache.json",
|
|
92
|
+
)
|
|
93
|
+
self._write(
|
|
94
|
+
tmp_path,
|
|
95
|
+
"deployment_cache.json",
|
|
96
|
+
{f"1:{self.CHECKSUM}": {"block": 5, "timestamp": 6}},
|
|
97
|
+
)
|
|
98
|
+
loaded = cache.load_deployment_cache()
|
|
99
|
+
assert loaded[cache.cache_key(1, self.CHECKSUM)] == {"block": 5, "timestamp": 6}
|
|
@@ -362,6 +362,10 @@ def test_vault_info_output_matches_fixture(scenario: str, json_output: bool) ->
|
|
|
362
362
|
json.loads(rendered)
|
|
363
363
|
|
|
364
364
|
|
|
365
|
+
# The CLI's cached lookup matches an address in any spelling; so does this one.
|
|
366
|
+
_NAMES_BY_ADDRESS = {address.lower(): name for address, name in NAMES.items()}
|
|
367
|
+
|
|
368
|
+
|
|
365
369
|
@pytest.mark.usefixtures("isolated_cache")
|
|
366
370
|
@pytest.mark.parametrize("scenario", sorted(SCENARIOS))
|
|
367
371
|
def test_public_builder_renders_the_cli_json(scenario: str) -> None:
|
|
@@ -377,7 +381,7 @@ def test_public_builder_renders_the_cli_json(scenario: str) -> None:
|
|
|
377
381
|
VAULT,
|
|
378
382
|
1,
|
|
379
383
|
"ethereum",
|
|
380
|
-
resolve_name=lambda address:
|
|
384
|
+
resolve_name=lambda address: _NAMES_BY_ADDRESS.get(address.lower(), ""),
|
|
381
385
|
)
|
|
382
386
|
expected = (FIXTURES / f"{scenario}.json").read_text(encoding="utf-8")
|
|
383
387
|
assert json.dumps(result, indent=2) + "\n" == expected
|
|
@@ -59,7 +59,7 @@ class TestGetContractName:
|
|
|
59
59
|
cache_file = tmp_path / ".cache" / "contract_cache.json"
|
|
60
60
|
assert cache_file.exists()
|
|
61
61
|
cached = json.loads(cache_file.read_text(encoding="utf-8"))
|
|
62
|
-
assert cached["1:
|
|
62
|
+
assert cached["1:0xdef"] == "PlasmaVault"
|
|
63
63
|
|
|
64
64
|
def test_returns_empty_for_unsupported_chain(self):
|
|
65
65
|
result = get_contract_name(99999, "0xABC", api_key="key123")
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import json
|
|
2
|
-
|
|
3
|
-
import pytest
|
|
4
|
-
|
|
5
|
-
from ipor_fusion import cache
|
|
6
|
-
from ipor_fusion.cache import load_contract_cache, save_contract_cache
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
@pytest.fixture(autouse=True)
|
|
10
|
-
def _patch_paths(tmp_path, monkeypatch):
|
|
11
|
-
cache_dir = tmp_path / ".cache"
|
|
12
|
-
monkeypatch.setattr(cache, "CACHE_DIR", cache_dir)
|
|
13
|
-
monkeypatch.setattr(cache, "CACHE_FILE", cache_dir / "contract_cache.json")
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
class TestLoadContractCache:
|
|
17
|
-
def test_returns_empty_dict_when_no_file(self):
|
|
18
|
-
assert load_contract_cache() == {}
|
|
19
|
-
|
|
20
|
-
def test_loads_existing_cache(self, tmp_path):
|
|
21
|
-
cache_dir = tmp_path / ".cache"
|
|
22
|
-
cache_dir.mkdir()
|
|
23
|
-
cache = {"0xABC": "ERC20", "0xDEF": "PlasmaVault"}
|
|
24
|
-
(cache_dir / "contract_cache.json").write_text(
|
|
25
|
-
json.dumps(cache), encoding="utf-8"
|
|
26
|
-
)
|
|
27
|
-
|
|
28
|
-
assert load_contract_cache() == cache
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
class TestSaveContractCache:
|
|
32
|
-
def test_creates_directory_and_writes_json(self, tmp_path):
|
|
33
|
-
cache = {"0xABC": "ERC20"}
|
|
34
|
-
save_contract_cache(cache)
|
|
35
|
-
|
|
36
|
-
cache_file = tmp_path / ".cache" / "contract_cache.json"
|
|
37
|
-
assert cache_file.exists()
|
|
38
|
-
assert json.loads(cache_file.read_text(encoding="utf-8")) == cache
|
|
39
|
-
|
|
40
|
-
def test_roundtrip(self):
|
|
41
|
-
original = {"0x111": "TokenA", "0x222": "TokenB"}
|
|
42
|
-
save_contract_cache(original)
|
|
43
|
-
loaded = load_contract_cache()
|
|
44
|
-
assert loaded == original
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/examples/advanced_euler_v2_credit_market_base.py
RENAMED
|
File without changes
|
{ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/examples/advanced_euler_v2_looping_arbitrum.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/examples/external_state_margin_leg_base.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/cli/oracle_mapping_view.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/cli/vault_markets_view.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/core/external_state_executor.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/core/withdraw_manager.py
RENAMED
|
File without changes
|
|
File without changes
|
{ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/crosschain/ccip/__init__.py
RENAMED
|
File without changes
|
{ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/crosschain/ccip/chainlink.py
RENAMED
|
File without changes
|
{ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/crosschain/ccip/codec.py
RENAMED
|
File without changes
|
{ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/crosschain/ccip/contracts.py
RENAMED
|
File without changes
|
{ipor_fusion-4.0.0.dev43 → ipor_fusion-4.0.0.dev45}/src/ipor_fusion/crosschain/ccip/events.py
RENAMED
|
File without changes
|