balancer_pool.py

This page explains the python code of Balancer core

NOTE: this file was used as the initial porting of BPool code and the basis for system_policies. Not used in the model.

Path: balancer_pool.py file discussed in this article. The file starts with importing balancer constants, and BalancerMath

from model.balancer_constants import MAX_TOTAL_WEIGHT, MAX_WEIGHT, MIN_BALANCE, MIN_FEE, MAX_BOUND_TOKENS, INIT_POOL_SUPPLY, EXIT_FEE, MAX_IN_RATIO, MAX_OUT_RATIO, MIN_WEIGHT
from model.balancer_math import BalancerMath
from dataclasses import dataclass

Data classes for storing information about the pool itself and swaps

@dataclass
class TokenRecord:
    bound: bool
    name: str
    denorm: Decimal
    balance: Decimal

@dataclass
class SwapInResult:
    token_amount_out: Decimal
    spot_price_after: Decimal

@dataclass
class SwapOutResult:
    token_amount_in: Decimal
    spot_price_after: Decimal

The BalancerMath class and related functions

class BalancerPool(BalancerMath):

Balancer Pool functions

Pool initialization with initial balances and Pool Shares

def __init__(self, initial_pool_supply: Decimal = INIT_POOL_SUPPLY):
        self._swap_fee = MIN_FEE
        self._records = {}  # NOTE: we are assuming python 3.6+ ordered dictionaries
        self.total_weight = Decimal('0')
        self._pool_token_supply = initial_pool_supply
        self.factory_fees = Decimal('0')

Functions for requesting tokens weights

def get_total_denorm_weight(self):
        return self.total_weight

    def get_denorm_weight(self, token: str):
        return self._records[token].denorm

    def get_normal_weight(self, token: str):
        denorm = self._records[token].denorm
        return denorm / self.total_weight

Functions related to balances of tokens and Pool Shares

 def get_balance(self, token: str):
        if self._records.get(token) is not None:
            return self._records[token].balance
        else:
            return 0

    def get_num_tokens(self):
        return len(self._records)

    def get_pool_token_supply(self):
        return self._pool_token_supply

Functions for minting and burning Pool Shares (related to join and exit the pool)

    def _mint_pool_share(self, amount: Decimal):
        self._pool_token_supply += amount

    def _burn_pool_share(self, amount: Decimal):
        self._pool_token_supply -= amount

Set swap fee function

def set_swap_fee(self, amount: Decimal):
        self._swap_fee = amount

Functions for receiving spot price (with and without fee)

def get_spot_price(self, token_in: str, token_out: str) -> Decimal:
        min_pool_amount_out = self._records[token_in]
        out_record = self._records[token_out]
        return self.calc_spot_price(min_pool_amount_out.balance, min_pool_amount_out.denorm, out_record.balance, out_record.denorm, self._swap_fee)

    def get_spot_price_sans_fee(self, token_in: str, token_out: str) -> Decimal:
        min_pool_amount_out = self._records[token_in]
        out_record = self._records[token_out]
        return self.calc_spot_price(min_pool_amount_out.balance, min_pool_amount_out.denorm, out_record.balance, out_record.denorm, Decimal('0'))

Join and exit pool functions (liquidity provision and liquidity withdrawal)

def join_pool(self, pool_amount_out: Decimal, max_amounts_in: dict) -> dict:
        ratio = pool_amount_out / self._pool_token_supply
        if ratio == 0:
          return Exception("ERR_MATH_APPROX")
        results = {}
        for token in self._records:
            record = self._records[token]
            token_amount_in = ratio * record.balance
            if token_amount_in == 0:
                return Exception("ERR_MATH_APPROX")
            if token_amount_in > max_amounts_in[token]:
                raise Exception('ERR_LIMIT_IN')
            record.balance += token_amount_in
            results[token] = token_amount_in
        self._mint_pool_share(pool_amount_out)
        return results

    def exit_pool(self, pool_amount_in: Decimal, min_amounts_out: dict) -> dict:
        pool_total = self._pool_token_supply
        exit_fee = pool_amount_in * EXIT_FEE
        pool_amount_in_afer_exit_fee = pool_amount_in - exit_fee
        ratio = pool_amount_in_afer_exit_fee / pool_total

        return_dict = {
          "exit_fee_pool_token": exit_fee
        }
        self._burn_pool_share(pool_amount_in_afer_exit_fee)
        
        for token in self._records:
            record = self._records[token]
            token_amount_out = ratio * record.balance
            if token_amount_out == 0:
                raise Exception("ERR_MATH_APPROX")
            if token_amount_out < min_amounts_out[token]:
                raise Exception("ERR_LIMIT_OUT")
            record.balance -= token_amount_out
            return_dict[token] = token_amount_out
        return return_dict

Token swaps functions (two of them - for exact amount in and for exact amount out)

def swap_exact_amount_in(self, token_in: str, token_amount_in: Decimal, token_out: str, min_amount_out: Decimal, max_price: Decimal) -> SwapInResult:
        min_pool_amount_out = self._records[token_in]
        if not min_pool_amount_out.bound:
            raise Exception('ERR_NOT_BOUND')
        out_record = self._records[token_out]
        if not out_record.bound:
            raise Exception('ERR_NOT_BOUND')

        if token_amount_in > min_pool_amount_out.balance * MAX_IN_RATIO:
            raise Exception("ERR_MAX_IN_RATIO")


        spot_price_before = self.calc_spot_price(
                                    token_balance_in=min_pool_amount_out.balance,
                                    token_weight_in=min_pool_amount_out.denorm,
                                    token_balance_out=out_record.balance,
                                    token_weight_out=out_record.denorm,
                                    swap_fee=self._swap_fee
                                )

        if spot_price_before > max_price:
            raise Exception("ERR_BAD_LIMIT_PRICE")
        token_amount_out = self.calc_out_given_in(
                            token_balance_in=min_pool_amount_out.balance,
                            token_weight_in=min_pool_amount_out.denorm,
                            token_balance_out=out_record.balance,
                            token_weight_out=out_record.denorm,
                            token_amount_in=token_amount_in,
                            swap_fee=self._swap_fee
                            )
        # TODO require(token_amount_out >= min_amount_out, "ERR_LIMIT_OUT")

        min_pool_amount_out.balance += token_amount_in
        out_record.balance -= token_amount_out

        spot_price_after = self.calc_spot_price(
                                token_balance_in=min_pool_amount_out.balance,
                                token_weight_in=min_pool_amount_out.denorm,
                                token_balance_out=out_record.balance,
                                token_weight_out=out_record.denorm,
                                swap_fee=self._swap_fee
                           )
        if spot_price_after < spot_price_before:
            raise Exception("ERR_MATH_APPROX")
        if spot_price_after > max_price:
            raise Exception("ERR_LIMIT_PRICE")
        if spot_price_before > (token_amount_in / token_amount_out):
            raise Exception("ERR_MATH_APPROX")
        # NOTE: we are not doing user balances yet
        # _pullUnderlying(token_in, msg.sender, token_amount_in)
        # _pushUnderlying(token_out, msg.sender, token_amount_out)
        return SwapInResult(token_amount_out, spot_price_after)

    
    def swap_exact_amount_out(self, token_in: str, max_amount_in: Decimal, token_out: str, token_amount_out: Decimal, max_price: Decimal) -> SwapOutResult:
        min_pool_amount_out = self._records[token_in]
        if not min_pool_amount_out.bound:
            raise Exception('ERR_NOT_BOUND')
        out_record = self._records[token_out]
        if not out_record.bound:
            raise Exception('ERR_NOT_BOUND')

        if token_amount_out > (out_record.balance * MAX_OUT_RATIO):
            raise Exception("ERR_MAX_OUT_RATIO")

        spot_price_before = self.calc_spot_price(
                                token_balance_in=min_pool_amount_out.balance,
                                token_weight_in=min_pool_amount_out.denorm,
                                token_balance_out=out_record.balance,
                                token_weight_out=out_record.denorm,
                                swap_fee=self._swap_fee
                            )
        if spot_price_before > max_price:
            raise Exception('ERR_BAD_LIMIT_PRICE')
        
        token_amount_in = self.calc_in_given_out(
            token_balance_in=min_pool_amount_out.balance,
            token_weight_in=min_pool_amount_out.denorm,
            token_balance_out=out_record.balance,
            token_weight_out=out_record.denorm,
            token_amount_out=token_amount_out,
            swap_fee=self._swap_fee
        )
        if token_amount_in > max_amount_in:
            raise Exception('ERR_LIMIT_IN')

        min_pool_amount_out.balance += token_amount_in
        out_record.balance -= token_amount_out

        spot_price_after = self.calc_spot_price(
            token_balance_in=min_pool_amount_out.balance,
            token_weight_in=min_pool_amount_out.denorm,
            token_balance_out=out_record.balance,
            token_weight_out=out_record.denorm,
            swap_fee=self._swap_fee
            )
        if spot_price_after < spot_price_before:
            raise Exception('ERR_MATH_APPROX')
        if spot_price_after > max_price:
            raise Exception('LIMIT PRICE')
        if spot_price_before > (token_amount_in / token_amount_out):
            raise Exception('ERR_MATH_APPROX')

        # NOTE not modeling balance change for sender
        # _pullUnderlying(token_in, msg.sender, token_amount_in)
        # _pushUnderlying(token_out, msg.sender, token_amount_out)

        return SwapOutResult(token_amount_in=token_amount_in, spot_price_after=spot_price_after)

Join pool functions (liquidity provision)

def join_swap_extern_amount_in(self, token_in: str, token_amount_in: Decimal, min_pool_amount_out: Decimal) -> Decimal:
        # require(_finalized, "ERR_NOT_FINALIZED")
        if not self._records[token_in].bound:
            raise Exception("ERR_NOT_BOUND")
        if token_amount_in > self._records[token_in].balance *  MAX_IN_RATIO:
            raise Exception("ERR_MAX_IN_RATIO")
    
        in_record = self._records[token_in]

        pool_amount_out = self.calc_pool_out_given_single_in(
            token_balance_in=in_record.balance,
            token_weight_in=in_record.denorm,
            pool_supply=self._pool_token_supply,
            total_weight=self.total_weight,
            token_amount_in=token_amount_in,
            swap_fee=self._swap_fee
        )

        if pool_amount_out < min_pool_amount_out:
            raise Exception("ERR_LIMIT_OUT")

        in_record.balance += token_amount_in

        self._mint_pool_share(pool_amount_out)
        # NOTE user balance can be inferred from params (substract tai), pool out is already returning
        # _pushPoolShare(msg.sender, pool_amount_out)
        # _pullUnderlying(token_in, msg.sender, token_amount_in)
        return pool_amount_out

    

    def join_swap_pool_amount_out(self, token_in: str, pool_amount_out: Decimal, max_amount_in: Decimal) -> Decimal:
        if not self._records[token_in].bound:
            raise Exception("ERR_NOT_BOUND")
        
        in_record = self._records[token_in]

        token_amount_in = self.calc_single_in_given_pool_out(
          token_balance_in = in_record.balance,
          token_weight_in = in_record.denorm,
          pool_supply = self._pool_token_supply,
          total_weight = self.total_weight,
          pool_amount_out = pool_amount_out,
          swap_fee = self._swap_fee)

        if token_amount_in == 0:
          raise Exception("ERR_MATH_APPROX")

        if token_amount_in > max_amount_in:
          raise Exception("ERR_LIMIT_IN")

        if token_amount_in > in_record.balance * MAX_IN_RATIO:
          raise Exception("ERR_MAX_IN_RATIO")
        
        in_record.balance = in_record.balance + token_amount_in
        self._mint_pool_share(pool_amount_out)
        # NOTE not modeling balance change for sender
        # _pushPoolShare(msg.sender, poolAmountOut)
        # _pullUnderlying(tokenIn, msg.sender, tokenAmountIn)

        return token_amount_in

Exit pool functions (liquidity withdrawal)

def exit_swap_pool_amount_in(self, token_out: str, pool_amount_in: Decimal, min_amount_out: Decimal) -> Decimal:
        if not self._records[token_out].bound:
            raise Exception("ERR_NOT_BOUND")

        out_record = self._records[token_out]

        token_amount_out = self.calc_single_out_given_pool_in(
          token_balance_out=out_record.balance,
          token_weight_out=out_record.denorm,
          pool_supply=self._pool_token_supply,
          total_weight=self.total_weight,
          pool_amount_in=pool_amount_in, 
          swap_fee=self._swap_fee)

        if token_amount_out < min_amount_out:
          raise Exception("ERR_LIMIT_OUT")
        if token_amount_out > out_record.balance * MAX_OUT_RATIO:
          raise Exception("ERR_MAX_OUT_RATIO")
        
        out_record.balance = out_record.balance - token_amount_out

        exit_fee = pool_amount_in * EXIT_FEE
        self._burn_pool_share(pool_amount_in - exit_fee)

        # _pushPoolShare(_factory, exit_fee)
        # _pushUnderlying(token_out, msg.sender, token_amount_out)

        return token_amount_out

    def exit_swap_extern_amount_out(self, token_out: str, token_amount_out: Decimal, max_pool_amount_in: Decimal) -> Decimal:
        if not self._records[token_out].bound:
            raise Exception("ERR_NOT_BOUND")

        out_record = self._records[token_out]
        if token_amount_out > out_record.balance * MAX_OUT_RATIO:
            raise Exception("ERR_MAX_OUT_RATIO")


        pool_amount_in = self.calc_pool_in_given_single_out(
          token_balance_out=out_record.balance,
          token_weight_out=out_record.denorm,
          pool_supply=self._pool_token_supply,
          total_weight=self.total_weight,
          token_amount_out=token_amount_out,
          swap_fee=self._swap_fee
        )
        if pool_amount_in == 0:
          raise Exception("ERR_MATH_APPROX")
        if pool_amount_in > max_pool_amount_in:
          raise Exception("ERR_LIMIT_IN")
        
        out_record.balance -= token_amount_out

        exitFee = pool_amount_in * EXIT_FEE
        self._burn_pool_share(pool_amount_in - exitFee)

        #_pushPoolShare(_factory, exitFee);
        return pool_amount_in;

Last updated