degenbot.balancer ================= .. py:module:: degenbot.balancer .. autoapi-nested-parse:: Balancer V2 weighted and stable pools with swap encoding. Submodules ---------- .. toctree:: :maxdepth: 1 /autoapi/degenbot/balancer/deployments/index /autoapi/degenbot/balancer/libraries/index /autoapi/degenbot/balancer/math/index /autoapi/degenbot/balancer/pools/index /autoapi/degenbot/balancer/stable_pools/index /autoapi/degenbot/balancer/types/index Package Contents ---------------- .. py:class:: BalancerV2Pool(*args: Any, **kwargs: Any) Bases: :py:obj:`degenbot.types.abstract.AbstractLiquidityPool` BalancerV2Pool class. .. py:attribute:: variant :type: ClassVar[str | None] :value: 'balancer_weighted' .. py:type:: PoolState :canonical: BalancerV2PoolState .. py:attribute:: FEE_DENOMINATOR :value: 1000000000000000000 .. py:attribute:: address :type: degenbot.types.chain.ChecksummedAddress .. py:attribute:: pool_id :type: bytes .. py:attribute:: pool_specialization :type: int .. py:attribute:: vault :type: degenbot.types.chain.ChecksummedAddress .. py:attribute:: scaling_factors :type: tuple[int, ...] .. py:attribute:: fee :type: fractions.Fraction .. py:attribute:: weights :type: tuple[int, ...] .. py:attribute:: pow_version :type: degenbot.balancer.libraries.constants.PowVersion .. py:method:: from_handle(py_pool: degenbot.types.Pool) -> Self :classmethod: Wrap a Rust-owned ``Pool`` handle as a Python companion. Internal seam (ADR-005, Polars-style ``_from_pydf`` pattern). Every identity field (vault, pool_id, tokens, weights, scaling_factors, swap_fee, pow_version, address) is read off the handle. :returns: A ``cls`` instance wrapping ``py_pool``. :raises DegenbotValueError: If the handle is not a Balancer weighted pool or any token is not registered. .. py:property:: balances :type: tuple[int, ...] Balances. Read from the Rust core via the ``Pool`` handle (ADR-005 slice 12b). Rust ``BotState`` is the single source of truth for the mutable ``balances`` slot; this getter returns the live tuple. .. py:property:: state :type: PoolState State. Built from one atomic Rust snapshot (``snapshot_balancer_weighted()`` — ``(balances, block)``) so callers see a coherent tuple (no torn read mid-``external_update``). Mirrors V3/V4's ``snapshot_v3()`` and Curve's ``snapshot_curve()`` contract. :raises DegenbotValueError: If the Rust snapshot is absent (the pool is not registered in Rust as a Balancer weighted pool — unreachable for a companion built over a registered handle). .. py:property:: tokens :type: tuple[degenbot.erc20.Erc20Token, ...] Tokens. .. py:method:: calculate_tokens_out_from_tokens_in(token_in: degenbot.erc20.Erc20Token, token_out: degenbot.erc20.Erc20Token, token_in_quantity: int, override_state: PoolState | None = None) -> int Calculate tokens out from tokens in. Thin driver shell over the Rust core: token resolution stays Python-side, the swap math is fully Rust-owned. :returns: The computed integer value. .. py:method:: calculate_tokens_in_from_tokens_out(token_in: degenbot.erc20.Erc20Token, token_out: degenbot.erc20.Erc20Token, token_out_quantity: int, override_state: PoolState | None = None) -> int Compute how many tokens must be sent to take `token_out_quantity` out. Thin driver shell over the Rust core. :returns: The computed integer value. .. py:method:: external_update(update: degenbot.balancer.types.BalancerV2WeightedPoolExternalUpdate) -> None Apply an external state update with new balances. Delegates to the Rust core (``Pool.apply_balancer_weighted_balance_update``) which journals the prior balances (genesis-anchor V2-style discipline) and lands the new balances + ``update_block`` atomically (ADR-005 slice 12b). The ``_state_lock`` + double-check-after-acquire pattern it used to apply is gone — Rust's internal write lock handles atomicity; the registration-state precondition is enforced by the Rust core's silent-no-op-on-older-block contract. :raises DegenbotValueError: If the Rust core rejects the update (the pool is not registered as a Balancer weighted pool — unreachable for a companion built over a registered handle). .. py:data:: INVARIANT_V1 :value: 1 .. py:data:: INVARIANT_V2 :value: 2 .. py:class:: BalancerV2StablePool(*args: Any, **kwargs: Any) Bases: :py:obj:`degenbot.types.abstract.AbstractLiquidityPool` Balancer V2 Stable Pool (MetaStablePool or ComposableStablePool). Supports token-to-token swaps using StableMath. For ComposableStablePools, the BPT token is automatically dropped from the invariant and swap calculations. Swap fee application order: GIVEN_IN: subtractFee → upscale → compute(outGivenIn) → downscaleDown GIVEN_OUT: upscale → compute(inGivenOut) → downscaleUp → addFee Invariant versions: V1 (INVARIANT_V1): always-roundDown, D_P accumulation. Used by older deployed ComposableStablePools (e.g. TUSD BSP, bb-s-USD). Matches the monorepo ``_calculate_invariant``. V2 (INVARIANT_V2): roundUp parameter, P_D accumulation. Used by MetaStablePools and newer deployed pools. The swap path calls with round_up=True. Matches ``_calculate_invariant_deployed``. Rate handling: ComposableStablePools have time-varying rates (yield accrual in bb-a-* tokens). The deployed contract refreshes rate caches before each swap via ``_beforeSwapJoinExit()``. For exact-integer matching, inject a ``BalancerRateProvider`` that replicates this cache-aware logic (read ``getTokenRateCache``, check expiry, call ``getRate()`` if expired). Without a live rate provider, the pool uses construction-time scaling factors and raises ``StaleRateResult`` for ComposableStablePools to warn that rates may be stale. MetaStablePools have no rate cache — they call ``getRate()`` directly. .. py:attribute:: variant :type: ClassVar[str | None] :value: 'balancer_stable' .. py:type:: PoolState :canonical: BalancerV2PoolState .. py:attribute:: FEE_DENOMINATOR :value: 1000000000000000000 .. py:attribute:: address :type: degenbot.types.chain.ChecksummedAddress .. py:attribute:: pool_id :type: bytes .. py:attribute:: pool_specialization :type: int .. py:attribute:: vault :type: degenbot.types.chain.ChecksummedAddress .. py:attribute:: scaling_factors :type: tuple[int, ...] .. py:attribute:: fee :type: fractions.Fraction .. py:attribute:: amp :type: int .. py:attribute:: bpt_idx :type: int | None .. py:attribute:: invariant_version :type: int .. py:method:: from_handle(py_pool: degenbot.types.Pool) -> Self :classmethod: Wrap a Rust-owned ``Pool`` handle as a Python companion. Internal seam (ADR-005, Polars-style ``_from_pydf`` pattern). Every identity field (vault, pool_id, tokens, amp, scaling_factors, swap_fee, bpt_idx, invariant_version) is read off the handle; the rate provider is the stored I/O trait object (queried via ``fetch_balancer_stable_rates`` / ``balancer_stable_rate_provider_is_static``); base scaling factors are derived from token decimals. :returns: A ``cls`` instance wrapping ``py_pool``. :raises DegenbotValueError: If the handle is not a Balancer stable pool or any token is not registered. .. py:property:: balances :type: tuple[int, ...] Balances. Read from the Rust core via the ``Pool`` handle (ADR-005 slice 12d). Rust ``BotState`` is the single source of truth for the mutable ``balances`` slot; this getter returns the live tuple (one U256 per token, including BPT for Composable pools). .. py:property:: state :type: PoolState State. Built from one atomic Rust snapshot (``snapshot_balancer_stable()`` — ``(balances, block)``) so callers see a coherent tuple (no torn read mid-``external_update``). Mirrors V3/V4's ``snapshot_v3()`` / Curve's ``snapshot_curve()`` / Weighted's ``snapshot_balancer_weighted()`` contract. :raises DegenbotValueError: If the Rust snapshot is absent (the pool is not registered in Rust as a Balancer stable pool — unreachable for a companion built over a registered handle). .. py:property:: tokens :type: tuple[degenbot.erc20.Erc20Token, ...] Tokens. .. py:property:: rate_provider :type: BalancerRateProvider | None The rate provider for per-block rate resolution, if available. With the sealed seam (ADR-005) the provider is the stored Rust I/O trait object, queried via the handle. This property returns ``None`` when the stored provider is static (the no-I/O fallback). .. py:property:: requires_io_at_calculation_time :type: bool Whether this pool may call its rate_provider during swap calculations. Returns True for ComposableStablePools with a live rate provider (time-varying rates from yield-bearing tokens). Returns False for MetaStablePools and for pools with only a static rate provider. .. py:method:: calculate_tokens_out_from_tokens_in(token_in: degenbot.erc20.Erc20Token, token_out: degenbot.erc20.Erc20Token, token_in_quantity: int, override_state: PoolState | None = None, block_identifier: int | str | None = None) -> int Compute the amount of token_out received for a GIVEN_IN swap. Thin driver shell over the Rust core: token/scale resolution stays Python-side (the MetaStable block-rate lookup is driver data), the swap math is fully Rust-owned. For ComposableStablePools without a live rate provider the result is wrapped in ``StaleRateResult`` because construction-time rates may be stale. :returns: The computed integer value (unwrapped from ``StaleRateResult`` semantics when rates cannot be stale). :raises StaleRateResult: See function documentation. .. py:method:: calculate_tokens_in_from_tokens_out(token_in: degenbot.erc20.Erc20Token, token_out: degenbot.erc20.Erc20Token, token_out_quantity: int, override_state: PoolState | None = None, block_identifier: int | str | None = None) -> int Compute how many tokens must be sent to take `token_out_quantity` out. Thin driver shell over the Rust core: the MetaStable block-rate resolution stays Python-side, the swap math is fully Rust-owned. :returns: The computed integer value. :raises StaleRateResult: See function documentation. .. py:method:: external_update(update: degenbot.balancer.types.BalancerV2StablePoolExternalUpdate) -> None Apply an external state update with new balances. Delegates to the Rust core (``Pool.apply_balancer_stable_balance_update``) which journals the prior balances (genesis-anchor V2-style discipline) and lands the new balances + ``update_block`` atomically (ADR-005 slice 12d). The ``_state_lock`` + double-check-after-acquire pattern is gone — Rust's internal write lock handles atomicity; the registration-state precondition is enforced by the Rust core's silent-no-op-on-older-block contract. :raises DegenbotValueError: If the Rust core rejects the update (the pool is not registered as a Balancer stable pool — unreachable for a companion built over a registered handle). .. py:class:: BalancerV2StablePoolExternalUpdate State update for a Balancer V2 stable pool. amp is currently omitted — stable pools treat amp as immutable after construction in this plan. A future slice may add amp tracking to support A ramping. .. py:attribute:: block_number :type: degenbot.types.aliases.BlockNumber .. py:attribute:: balances :type: tuple[int, ...] .. py:class:: BalancerV2WeightedPoolExternalUpdate State update for a Balancer V2 weighted pool. .. py:attribute:: block_number :type: degenbot.types.aliases.BlockNumber .. py:attribute:: balances :type: tuple[int, ...]