pool_state_updates.py

ActionDecoder's decoded actions (signals, in cadCAD lore) get passed here into s_update_pool, which redirects to the actual state update function like s_swap_exact_amount_in. The redirection is performed via a dict lookup:

pool_operation_mappings = {
    JoinSwapExternAmountInInput: s_join_swap_extern_amount_in,
    JoinParamsInput: s_join_pool,
    SwapExactAmountInInput: s_swap_exact_amount_in,
    SwapExactAmountOutInput: s_swap_exact_amount_out,
    ExitPoolInput: s_exit_pool,
    ExitSwapPoolAmountInInput: s_exit_swap_pool_amount_in,
    ExitSwapPoolExternAmountOutInput: s_exit_swap_extern_amount_out
}

Each state update function follows the same structure:

  1. Parse input from the action and get variables from current pool state (related token balances)

  2. Apply related Balancer Math

  3. Update fees if applicable

  4. Update pool balances

  5. Return pool so action decoder can signal updates

Some sanity checks are made, following BPool smart contracts. In REPLAY_OUTPUT mode no sanity checks are performed:

def s_join_pool_plot_output(params, step, history, current_state, input_params: JoinParamsInput, output_params: JoinParamsOutput) -> dict:
    pool = current_state['pool'].copy()
    pool_amount_out = input_params.pool_amount_out
    for token in output_params.tokens_in:
        pool['tokens'][token.symbol].balance += token.amount
    pool['pool_shares'] += pool_amount_out
    return pool

Min/Max limits in prices and token amounts are not included since the model won't late stuck transactions or tx reordering like the actual BPool contract.

Swaps

def s_swap_exact_amount_in(params, step, history, current_state, input_params: SwapExactAmountInInput,
                           output_params: SwapExactAmountInOutput) -> dict:
    pool = current_state['pool'].copy()
    # Parse action params
    token_in_symbol = input_params.token_in.symbol
    token_amount_in = input_params.token_in.amount
    token_out = input_params.min_token_out.symbol
    pool_token_in = pool['tokens'][token_in_symbol]
    swap_fee = pool['swap_fee']

    if not pool_token_in.bound:
        raise Exception('ERR_NOT_BOUND')
    out_record = pool['tokens'][token_out]
    if not out_record.bound:
        raise Exception('ERR_NOT_BOUND')

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

    swap_result = BalancerMath.calc_out_given_in(
        token_balance_in=pool_token_in.balance,
        token_weight_in=Decimal(pool_token_in.denorm_weight),
        token_balance_out=out_record.balance,
        token_weight_out=Decimal(out_record.denorm_weight),
        token_amount_in=token_amount_in,
        swap_fee=Decimal(swap_fee)
    )

    _, pool = s_pool_update_fee(pool, {token_in_symbol: swap_result.fee})
    pool_in_balance = pool_token_in.balance + token_amount_in
    pool_token_in.balance = pool_in_balance
    pool_out_balance = out_record.balance - swap_result.result
    out_record.balance = pool_out_balance
    return pool

Join Multi Asset

def s_join_pool(params, step, history, current_state, input_params: JoinParamsInput, output_params: JoinParamsOutput) -> dict:
    """
    Join a pool by providing liquidity for all token_symbols.
    """
    pool = current_state['pool'].copy()

    # tokens_in is a suggestion. The real fixed input is pool_amount_out - how many pool shares does the user want.
    # tokens_in will then be recalculated and that value used instead.
    pool_amount_out = input_params.pool_amount_out

    ratio = pool_amount_out / Decimal(pool['pool_shares'])
    if ratio == Decimal('0'):
        raise Exception("ERR_MATH_APPROX")

    for token in output_params.tokens_in:
        amount_expected = token.amount
        symbol = token.symbol
        amount = ratio * pool['tokens'][symbol].balance
        if amount != amount_expected and VERBOSE:
            print("WARNING: calculated that user should get {} {} but input specified that he should get {} {} instead".format(amount, symbol,
                                                                                                                               amount_expected,
                                                                                                                               symbol))
        pool['tokens'][symbol].balance += amount
    pool['pool_shares'] += pool_amount_out

    return pool

Join Single Asset

def s_join_swap_extern_amount_in(params, step, history, current_state, input_params: JoinSwapExternAmountInInput,
                                 output_params: JoinSwapExternAmountInOutput) -> dict:
    """
    Join a pool by providing liquidity for a single token_symbol.
    """
    pool = current_state['pool'].copy()
    tokens_in_symbol = input_params.token_in.symbol
    token_in_amount = input_params.token_in.amount
    pool_amount_out_expected = output_params.pool_amount_out
    swap_fee = pool['swap_fee']

    total_weight = calculate_total_denorm_weight(pool)

    join_swap = BalancerMath.calc_pool_out_given_single_in(
        token_balance_in=Decimal(pool['tokens'][tokens_in_symbol].balance),
        token_weight_in=Decimal(pool['tokens'][tokens_in_symbol].denorm_weight),
        pool_supply=Decimal(pool['pool_shares']),
        total_weight=total_weight,
        token_amount_in=token_in_amount,
        swap_fee=Decimal(swap_fee)
    )

    _, pool = s_pool_update_fee(pool, {tokens_in_symbol: join_swap.fee})

    pool_amount_out = join_swap.result
    if pool_amount_out != pool_amount_out_expected and VERBOSE:
        print(
            "WARNING: calculated that user should get {} pool shares but input specified that he should get {} pool shares instead.".format(
                pool_amount_out, pool_amount_out_expected))

    pool['pool_shares'] = Decimal(pool['pool_shares']) + pool_amount_out
    pool['tokens'][tokens_in_symbol].balance += token_in_amount

    return pool

Single Asset Exit

def s_exit_swap_extern_amount_out(params, step, history, current_state, input_params: ExitSwapPoolExternAmountOutInput,
                                  output_params: ExitSwapPoolExternAmountOutOutput) -> dict:
    """
    Exit a pool by withdrawing liquidity for a single token_symbol.
    """
    pool = current_state['pool'].copy()
    swap_fee = pool['swap_fee']
    token_out_symbol = input_params.token_out.symbol
    # Check that all tokens_out are bound
    if not pool['tokens'][token_out_symbol].bound:
        raise Exception("ERR_NOT_BOUND")
    # Check that user is not trying to withdraw too many tokens
    token_amount_out = input_params.token_out.amount
    if token_amount_out > Decimal(pool['tokens'][token_out_symbol].balance) * MAX_OUT_RATIO:
        raise Exception("ERR_MAX_OUT_RATIO")

    total_weight = calculate_total_denorm_weight(pool)

    exit_swap = BalancerMath.calc_pool_in_given_single_out(
        token_balance_out=Decimal(pool['tokens'][token_out_symbol].balance),
        token_weight_out=Decimal(pool['tokens'][token_out_symbol].denorm_weight),
        pool_supply=Decimal(pool['pool_shares']),
        total_weight=Decimal(total_weight),
        token_amount_out=token_amount_out,
        swap_fee=Decimal(swap_fee)
    )
    pool_amount_in = exit_swap.result

    _, pool = s_pool_update_fee(pool, {token_out_symbol: exit_swap.fee})

    if pool_amount_in == 0:
        raise Exception("ERR_MATH_APPROX")
    if pool_amount_in != output_params.pool_amount_in and VERBOSE:
        print(
            "WARNING: calculated that pool should get {} pool shares but input specified that pool should get {} pool shares instead".format(
                pool_amount_in, output_params.pool_amount_in))

    # Decrease token_symbol (give it to user)
    pool['tokens'][token_out_symbol].balance -= token_amount_out
    # Burn the user's incoming pool shares - exit fee
    exit_fee = pool_amount_in * EXIT_FEE
    pool['pool_shares'] = Decimal(pool['pool_shares']) - pool_amount_in - exit_fee

    return pool

Exit Multi Asset

def s_exit_pool(params, step, history, current_state, input_params: ExitPoolInput, output_params: ExitPoolOutput) -> dict:
    """
    Exit a pool by withdrawing liquidity for all token_symbol.
    """
    pool = current_state['pool'].copy()
    pool_shares = Decimal(pool['pool_shares'])
    pool_amount_in = input_params.pool_amount_in

    # Current Balancer implementation has 0 exit fees, but we are leaving this to generalize
    exit_fee = pool_amount_in * Decimal(EXIT_FEE)
    pool_amount_in_afer_exit_fee = pool_amount_in - exit_fee
    ratio = pool_amount_in_afer_exit_fee / pool_shares

    pool['pool_shares'] = pool_shares - pool_amount_in_afer_exit_fee

    for token_symbol in pool['tokens']:
        token_amount_out = ratio * pool['tokens'][token_symbol].balance
        if token_amount_out == Decimal('0'):
            raise Exception("ERR_MATH_APPROX")
        pool['tokens'][token_symbol].balance -= token_amount_out

    return pool

Last updated