Пример #1
0
def test_prepare_mediation_fee_config_flat_fee(cli_flat_fee, expected_channel_flat_fee):
    token_address = factories.make_token_address()
    fee_config = prepare_mediation_fee_config(
        cli_token_to_flat_fee=((token_address, cli_flat_fee),),
        cli_token_to_proportional_fee=((token_address, ProportionalFeeAmount(0)),),
        cli_token_to_proportional_imbalance_fee=((token_address, ProportionalFeeAmount(0)),),
    )

    assert fee_config.get_flat_fee(token_address) == expected_channel_flat_fee
Пример #2
0
def test_fee_round_trip(flat_fee, prop_fee, imbalance_fee, amount):
    """ Tests mediation fee deduction. """
    balance = TokenAmount(100_000)
    prop_fee_per_channel = ppm_fee_per_channel(ProportionalFeeAmount(prop_fee))
    imbalance_fee = calculate_imbalance_fees(
        channel_capacity=balance,
        proportional_imbalance_fee=ProportionalFeeAmount(imbalance_fee))
    payer_channel = factories.create(
        NettingChannelStateProperties(
            our_state=NettingChannelEndStateProperties(balance=TokenAmount(0)),
            partner_state=NettingChannelEndStateProperties(balance=balance),
            fee_schedule=FeeScheduleState(
                flat=FeeAmount(flat_fee),
                proportional=prop_fee_per_channel,
                imbalance_penalty=imbalance_fee,
            ),
        ))
    payer_channel_backwards = factories.create(
        NettingChannelStateProperties(
            partner_state=NettingChannelEndStateProperties(
                balance=TokenAmount(0)),
            our_state=NettingChannelEndStateProperties(balance=balance),
            fee_schedule=FeeScheduleState(
                flat=FeeAmount(flat_fee),
                proportional=prop_fee_per_channel,
                imbalance_penalty=imbalance_fee,
            ),
        ))
    payee_channel = factories.create(
        NettingChannelStateProperties(
            our_state=NettingChannelEndStateProperties(balance=balance),
            partner_state=NettingChannelEndStateProperties(
                balance=TokenAmount(0)),
            fee_schedule=FeeScheduleState(
                flat=FeeAmount(flat_fee),
                proportional=prop_fee_per_channel,
                imbalance_penalty=imbalance_fee,
            ),
        ))

    fee_calculation = get_initial_payment_for_final_target_amount(
        final_amount=PaymentAmount(amount),
        channels=[payer_channel_backwards, payee_channel])
    assert fee_calculation

    amount_after_fees = get_lock_amount_after_fees(
        lock=make_hash_time_lock_state(amount=fee_calculation.total_amount),
        payer_channel=payer_channel,
        payee_channel=payee_channel,
    )
    assert amount_after_fees

    assert abs(amount - amount_after_fees) < 100
Пример #3
0
def test_rebalancing_fee_calculation():
    sample = calculate_imbalance_fees(TokenAmount(200),
                                      ProportionalFeeAmount(50_000))  # 5%
    assert sample is not None
    assert len(sample) == NUM_DISCRETISATION_POINTS
    assert all(0 <= x <= 200 for x, _ in sample)
    assert max(x for x, _ in sample) == 200
    assert all(0 <= y <= 10 for _, y in sample)
    assert max(y
               for _, y in sample) == 10  # 5% of the 200 TokenAmount capacity

    sample = calculate_imbalance_fees(TokenAmount(100),
                                      ProportionalFeeAmount(20_000))  # 2%
    assert sample is not None
    assert len(sample) == NUM_DISCRETISATION_POINTS
    assert all(0 <= x <= 100 for x, _ in sample)
    assert max(x for x, _ in sample) == 100
    assert all(0 <= y <= 2 for _, y in sample)
    assert max(y for _, y in sample) == 2  # 2% of the 100 TokenAmount capacity

    sample = calculate_imbalance_fees(TokenAmount(15),
                                      ProportionalFeeAmount(50_000))  # 5%
    assert sample is not None
    assert len(sample) == 16
    assert all(0 <= x <= 16 for x, _ in sample)
    assert max(x for x, _ in sample) == 15
    assert all(0 <= y <= 1 for _, y in sample)
    assert max(y for _, y in sample) == 1  # 5% of the 5 rounded up

    # test rounding of the max_balance_fee calculation
    sample = calculate_imbalance_fees(TokenAmount(1000),
                                      ProportionalFeeAmount(5_490))  # 0.549%
    assert sample is not None
    assert len(sample) == NUM_DISCRETISATION_POINTS
    assert all(0 <= x <= 1000 for x, _ in sample)
    assert max(x for x, _ in sample) == 1000
    assert all(0 <= y <= 5 for _, y in sample)
    assert max(y for _, y in sample) == 5  # 5.49 is rounded to 5

    sample = calculate_imbalance_fees(TokenAmount(1000),
                                      ProportionalFeeAmount(5_500))  # 0.55%
    assert sample is not None
    assert len(sample) == NUM_DISCRETISATION_POINTS
    assert all(0 <= x <= 1000 for x, _ in sample)
    assert max(x for x, _ in sample) == 1000
    assert all(0 <= y <= 6 for _, y in sample)
    assert max(y for _, y in sample) == 6  # 5.5 is rounded to 6

    # test cases where no imbalance fee is created
    assert calculate_imbalance_fees(TokenAmount(0),
                                    ProportionalFeeAmount(1)) is None
    assert calculate_imbalance_fees(TokenAmount(10),
                                    ProportionalFeeAmount(0)) is None
Пример #4
0
def test_fee_estimate(flat_fee, prop_fee_cli, max_lin_imbalance_fee,
                      target_amount, expected_fee):
    """ Tests the backwards fee calculation. """
    capacity = TA(10_000)

    prop_fee = ppm_fee_per_channel(ProportionalFeeAmount(prop_fee_cli))
    imbalance_fee = None
    if max_lin_imbalance_fee > 0:
        # This created a simple asymmetric imbalance fee
        imbalance_fee = [(0, 0), (capacity, 0),
                         (2 * capacity, max_lin_imbalance_fee)]

    tn = TokenNetworkForTests(
        channels=[
            dict(participant1=1, participant2=2),
            dict(participant1=2, participant2=3)
        ],
        default_capacity=capacity,
    )

    tn.set_fee(2,
               1,
               flat=flat_fee,
               proportional=prop_fee,
               imbalance_penalty=imbalance_fee)
    tn.set_fee(2,
               3,
               flat=flat_fee,
               proportional=prop_fee,
               imbalance_penalty=imbalance_fee)
    assert tn.estimate_fee(1, 3, value=PA(target_amount)) == expected_fee
Пример #5
0
def test_update_fee(order, pathfinding_service_mock, token_network_model):
    pathfinding_service_mock.database.insert(
        "token_network", dict(address=token_network_model.address))
    if order == "normal":
        setup_channel(pathfinding_service_mock, token_network_model)

    fee_schedule = FeeScheduleState(
        flat=FeeAmount(1),
        proportional=ProportionalFeeAmount(int(0.1e9)),
        imbalance_penalty=[(TokenAmount(0), FeeAmount(0)),
                           (TokenAmount(10), FeeAmount(10))],
    )
    fee_update = PFSFeeUpdate(
        canonical_identifier=CanonicalIdentifier(
            chain_identifier=ChainID(1),
            token_network_address=token_network_model.address,
            channel_identifier=ChannelID(1),
        ),
        updating_participant=PARTICIPANT1,
        fee_schedule=fee_schedule,
        timestamp=datetime.now(timezone.utc),
        signature=EMPTY_SIGNATURE,
    )
    fee_update.sign(LocalSigner(PARTICIPANT1_PRIVKEY))
    pathfinding_service_mock.handle_message(fee_update)

    if order == "fee_update_before_channel_open":
        setup_channel(pathfinding_service_mock, token_network_model)

    cv = token_network_model.G[PARTICIPANT1][PARTICIPANT2]["view"]
    for key in ("flat", "proportional", "imbalance_penalty"):
        assert getattr(cv.fee_schedule_sender,
                       key) == getattr(fee_schedule, key)
Пример #6
0
def test_prepare_mediation_fee_config_prop_fee(cli_prop_fee):
    token_address = factories.make_token_address()
    fee_config = prepare_mediation_fee_config(
        cli_token_to_flat_fee=(),
        cli_token_to_proportional_fee=((token_address, ProportionalFeeAmount(cli_prop_fee)),),
        cli_token_to_proportional_imbalance_fee=((token_address, ProportionalFeeAmount(0)),),
    )

    cli_prop_fee_ratio = cli_prop_fee / 1e6
    channel_prop_fee_ratio = fee_config.get_proportional_fee(token_address) / 1e6

    assert isclose(
        1 + cli_prop_fee_ratio,
        1 + channel_prop_fee_ratio + channel_prop_fee_ratio * (1 + cli_prop_fee_ratio),
        rel_tol=1e-6,
    )
def get_fee_update_message(  # pylint: disable=too-many-arguments
    updating_participant: Address,
    chain_id=ChainID(61),
    channel_identifier=DEFAULT_CHANNEL_ID,
    token_network_address: TokenNetworkAddress = DEFAULT_TOKEN_NETWORK_ADDRESS,
    fee_schedule: FeeScheduleState = FeeScheduleState(
        cap_fees=True,
        flat=FeeAmount(1),
        proportional=ProportionalFeeAmount(1)),
    timestamp: datetime = datetime.utcnow(),
    privkey_signer: bytes = PRIVATE_KEY_1,
) -> PFSFeeUpdate:
    fee_message = PFSFeeUpdate(
        canonical_identifier=CanonicalIdentifier(
            chain_identifier=chain_id,
            channel_identifier=channel_identifier,
            token_network_address=token_network_address,
        ),
        updating_participant=updating_participant,
        fee_schedule=fee_schedule,
        timestamp=timestamp,
        signature=EMPTY_SIGNATURE,
    )

    fee_message.sign(LocalSigner(privkey_signer))

    return fee_message
Пример #8
0
def test_fee_add_remove_invariant(flat_fee, prop_fee, imbalance_fee, amount, balance1, balance2):
    """ First adding and then removing fees must yield the original value """
    total_balance = TokenAmount(100_000_000_000_000_000_000)
    prop_fee_per_channel = ppm_fee_per_channel(ProportionalFeeAmount(prop_fee))
    imbalance_fee = calculate_imbalance_fees(
        channel_capacity=total_balance,
        proportional_imbalance_fee=ProportionalFeeAmount(imbalance_fee),
    )
    fee_schedule = FeeScheduleState(
        cap_fees=False,
        flat=FeeAmount(flat_fee),
        proportional=prop_fee_per_channel,
        imbalance_penalty=imbalance_fee,
    )
    channel_in = factories.create(
        NettingChannelStateProperties(
            our_state=NettingChannelEndStateProperties(balance=total_balance - balance1),
            partner_state=NettingChannelEndStateProperties(balance=balance1),
            fee_schedule=fee_schedule,
        )
    )
    channel_out = factories.create(
        NettingChannelStateProperties(
            our_state=NettingChannelEndStateProperties(balance=balance2),
            partner_state=NettingChannelEndStateProperties(balance=total_balance - balance2),
            fee_schedule=fee_schedule,
        )
    )

    amount_with_fees = get_amount_with_fees(
        amount_without_fees=amount,
        schedule_in=channel_in.fee_schedule,
        schedule_out=channel_out.fee_schedule,
        receivable_amount=balance1,
        balance_in=total_balance - balance1,
        balance_out=balance2,
    )
    assume(amount_with_fees)
    assert amount_with_fees
    amount_without_fees = get_amount_without_fees(
        amount_with_fees=amount_with_fees, channel_in=channel_in, channel_out=channel_out
    )
    assume(amount_without_fees)
    assert amount - 1 <= amount_without_fees <= amount + 1
Пример #9
0
def test_basic_fee():
    flat_schedule = FeeScheduleState(flat=FeeAmount(2))
    assert flat_schedule.fee_payer(PaymentWithFeeAmount(10),
                                   balance=Balance(0)) == FeeAmount(2)

    prop_schedule = FeeScheduleState(
        proportional=ProportionalFeeAmount(int(0.01e6)))
    assert prop_schedule.fee_payer(PaymentWithFeeAmount(40),
                                   balance=Balance(0)) == FeeAmount(0)
    assert prop_schedule.fee_payer(PaymentWithFeeAmount(60),
                                   balance=Balance(0)) == FeeAmount(1)
    assert prop_schedule.fee_payer(PaymentWithFeeAmount(1000),
                                   balance=Balance(0)) == FeeAmount(10)

    combined_schedule = FeeScheduleState(flat=FeeAmount(2),
                                         proportional=ProportionalFeeAmount(
                                             int(0.01e6)))
    assert combined_schedule.fee_payer(PaymentWithFeeAmount(60),
                                       balance=Balance(0)) == FeeAmount(3)
Пример #10
0
def ppm_fee_per_channel(per_hop_fee: ProportionalFeeAmount) -> ProportionalFeeAmount:
    """
    Converts proportional-fee-per-mediation into proportional-fee-per-channel

    Input and output are given in parts-per-million (ppm).

    See https://raiden-network-specification.readthedocs.io/en/latest/mediation_fees.html
    #converting-per-hop-proportional-fees-in-per-channel-proportional-fees
    for how to get to this formula.
    """
    per_hop_ratio = Fraction(per_hop_fee, 10 ** 6)
    return ProportionalFeeAmount(round(per_hop_ratio / (per_hop_ratio + 2) * 10 ** 6))
Пример #11
0
def test_get_lock_amount_after_fees_imbalanced_channel(
    cap_fees, flat_fee, prop_fee, imbalance_fee, initial_amount, expected_amount
):
    """ Tests mediation fee deduction. """
    balance = TokenAmount(100_000)
    prop_fee_per_channel = ppm_fee_per_channel(ProportionalFeeAmount(prop_fee))
    imbalance_fee = calculate_imbalance_fees(
        channel_capacity=balance, proportional_imbalance_fee=ProportionalFeeAmount(imbalance_fee)
    )
    lock = make_hash_time_lock_state(amount=initial_amount)
    channel_in = factories.create(
        NettingChannelStateProperties(
            our_state=NettingChannelEndStateProperties(balance=TokenAmount(0)),
            partner_state=NettingChannelEndStateProperties(balance=balance),
            fee_schedule=FeeScheduleState(
                cap_fees=cap_fees,
                flat=FeeAmount(flat_fee),
                proportional=prop_fee_per_channel,
                imbalance_penalty=imbalance_fee,
            ),
        )
    )
    channel_out = factories.create(
        NettingChannelStateProperties(
            our_state=NettingChannelEndStateProperties(balance=balance),
            partner_state=NettingChannelEndStateProperties(balance=TokenAmount(0)),
            fee_schedule=FeeScheduleState(
                cap_fees=cap_fees,
                flat=FeeAmount(flat_fee),
                proportional=prop_fee_per_channel,
                imbalance_penalty=imbalance_fee,
            ),
        )
    )

    locked_after_fees = get_amount_without_fees(
        amount_with_fees=lock.amount, channel_in=channel_in, channel_out=channel_out
    )
    assert locked_after_fees == expected_amount
Пример #12
0
def test_update_fee(order, pathfinding_service_mock, token_network_model):
    metrics_state = save_metrics_state(metrics.REGISTRY)

    pathfinding_service_mock.database.insert(
        "token_network", dict(address=token_network_model.address)
    )
    if order == "normal":
        setup_channel(pathfinding_service_mock, token_network_model)
        exception_expected = False
    else:
        exception_expected = True

    fee_schedule = FeeScheduleState(
        flat=FeeAmount(1),
        proportional=ProportionalFeeAmount(int(0.1e9)),
        imbalance_penalty=[(TokenAmount(0), FeeAmount(0)), (TokenAmount(10), FeeAmount(10))],
    )
    fee_update = PFSFeeUpdate(
        canonical_identifier=CanonicalIdentifier(
            chain_identifier=ChainID(61),
            token_network_address=token_network_model.address,
            channel_identifier=ChannelID(1),
        ),
        updating_participant=PARTICIPANT1,
        fee_schedule=fee_schedule,
        timestamp=datetime.utcnow(),
        signature=EMPTY_SIGNATURE,
    )
    fee_update.sign(LocalSigner(PARTICIPANT1_PRIVKEY))
    pathfinding_service_mock.handle_message(fee_update)

    # Test for metrics having seen the processing of the message
    assert (
        metrics_state.get_delta(
            "messages_processing_duration_seconds_sum",
            labels={"message_type": "PFSFeeUpdate"},
        )
        > 0.0
    )
    assert metrics_state.get_delta(
        "messages_exceptions_total", labels={"message_type": "PFSFeeUpdate"}
    ) == float(exception_expected)

    if order == "fee_update_before_channel_open":
        setup_channel(pathfinding_service_mock, token_network_model)

    cv = token_network_model.G[PARTICIPANT1][PARTICIPANT2]["view"]
    for key in ("flat", "proportional", "imbalance_penalty"):
        assert getattr(cv.fee_schedule_sender, key) == getattr(fee_schedule, key)
Пример #13
0
def test_get_lock_amount_after_fees(flat_fee, prop_fee, initial_amount,
                                    expected_amount):
    """ Tests mediation fee deduction. """
    prop_fee_per_channel = ppm_fee_per_channel(ProportionalFeeAmount(prop_fee))
    lock = make_hash_time_lock_state(amount=initial_amount)
    payer_channel = factories.create(
        NettingChannelStateProperties(fee_schedule=FeeScheduleState(
            flat=flat_fee, proportional=prop_fee_per_channel)))
    payee_channel = factories.create(
        NettingChannelStateProperties(fee_schedule=FeeScheduleState(
            flat=flat_fee, proportional=prop_fee_per_channel)))

    locked_after_fees = get_lock_amount_after_fees(lock=lock,
                                                   payer_channel=payer_channel,
                                                   payee_channel=payee_channel)
    assert locked_after_fees == expected_amount
Пример #14
0
def test_get_lock_amount_after_fees(flat_fee, prop_fee, initial_amount, expected_amount):
    """ Tests mediation fee deduction. """
    prop_fee_per_channel = ppm_fee_per_channel(ProportionalFeeAmount(prop_fee))
    lock = make_hash_time_lock_state(amount=initial_amount)
    channel_in = factories.create(
        NettingChannelStateProperties(
            partner_state=NettingChannelEndStateProperties(balance=TokenAmount(2000)),
            fee_schedule=FeeScheduleState(flat=flat_fee, proportional=prop_fee_per_channel),
        )
    )
    channel_out = factories.create(
        NettingChannelStateProperties(
            our_state=NettingChannelEndStateProperties(balance=TokenAmount(2000)),
            fee_schedule=FeeScheduleState(flat=flat_fee, proportional=prop_fee_per_channel),
        )
    )

    locked_after_fees = get_amount_without_fees(
        amount_with_fees=lock.amount, channel_in=channel_in, channel_out=channel_out
    )
    assert locked_after_fees == expected_amount
Пример #15
0
def test_compounding_fees(flat_fee_cli, prop_fee_cli, estimated_fee):
    """ The transferred amount needs to include the fees for all mediators.
    Earlier mediators will apply the proportional fee not only on the payment
    amount, but also on the fees for later mediators.
    """
    flat_fee = flat_fee_cli // 2
    prop_fee = ppm_fee_per_channel(ProportionalFeeAmount(prop_fee_cli))

    tn = TokenNetworkForTests(
        channels=[
            dict(participant1=1, participant2=2),
            dict(participant1=2, participant2=3),
            dict(participant1=3, participant2=4),
        ],
        default_capacity=TA(10_000),
    )

    tn.set_fee(2, 1, flat=flat_fee, proportional=prop_fee)
    tn.set_fee(2, 3, flat=flat_fee, proportional=prop_fee)
    tn.set_fee(3, 2, flat=flat_fee, proportional=prop_fee)
    tn.set_fee(3, 4, flat=flat_fee, proportional=prop_fee)
    assert tn.estimate_fee(1, 4, value=PA(1_000)) == estimated_fee
Пример #16
0
def test_mfee2():
    """ Unit test for the fee calculation in the mfee2_proportional_fees scenario """
    amount = 10_000
    deposit = 100_000
    prop_fee = ppm_fee_per_channel(ProportionalFeeAmount(10_000))
    fee_schedule = FeeScheduleState(proportional=ProportionalFeeAmount(prop_fee))
    channels = make_channel_pair(fee_schedule, deposit)

    # How much do we need to send so that the target receives `amount`? PFS-like calculation.
    fee_calculation = get_initial_amount_for_amount_after_fees(
        amount_after_fees=PaymentAmount(amount), channels=[channels, channels]
    )
    assert fee_calculation
    amount_with_margin = calculate_safe_amount_with_fee(
        fee_calculation.amount_without_fees, FeeAmount(sum(fee_calculation.mediation_fees))
    )
    assert amount_with_margin == 10_213

    # print values for scenario
    print(deposit - amount_with_margin, amount_with_margin)
    for med_fee in running_sum(fee_calculation.mediation_fees):
        print(deposit - amount_with_margin + med_fee, amount_with_margin - med_fee)
Пример #17
0
def test_mediated_transfer_with_fees(raiden_network, number_of_nodes, deposit,
                                     token_addresses, network_wait, case_no):
    """
    Test mediation with a variety of fee schedules
    """
    apps = raiden_network
    token_address = token_addresses[0]
    chain_state = views.state_from_app(apps[0])
    token_network_registry_address = apps[0].raiden.default_registry.address
    token_network_address = views.get_token_network_address_by_token_address(
        chain_state, token_network_registry_address, token_address)
    assert token_network_address

    def set_fee_schedule(app: App, other_app: App,
                         fee_schedule: FeeScheduleState):
        channel_state = views.get_channelstate_by_token_network_and_partner(  # type: ignore
            chain_state=views.state_from_raiden(app.raiden),
            token_network_address=token_network_address,
            partner_address=other_app.raiden.address,
        )
        assert channel_state
        channel_state.fee_schedule = fee_schedule

    def get_best_routes_with_fees(*args, **kwargs):
        routes = get_best_routes_internal(*args, **kwargs)
        for r in routes:
            r.estimated_fee = fee_without_margin
        return routes

    def assert_balances(expected_transferred_amounts=List[int]):
        for i, transferred_amount in enumerate(expected_transferred_amounts):
            assert_synced_channel_state(  # type: ignore
                token_network_address=token_network_address,
                app0=apps[i],
                balance0=deposit - transferred_amount,
                pending_locks0=[],
                app1=apps[i + 1],
                balance1=deposit + transferred_amount,
                pending_locks1=[],
            )

    amount = PaymentAmount(35)
    fee_without_margin = FeeAmount(20)
    fee = FeeAmount(fee_without_margin +
                    calculate_fee_margin(amount, fee_without_margin))

    no_fees = FeeScheduleState(flat=FeeAmount(0),
                               proportional=ProportionalFeeAmount(0),
                               imbalance_penalty=None)
    no_fees_no_cap = FeeScheduleState(
        flat=FeeAmount(0),
        proportional=ProportionalFeeAmount(0),
        imbalance_penalty=None,
        cap_fees=False,
    )
    cases = [
        # The fee is added by the initiator, but no mediator deducts fees. As a
        # result, the target receives the fee.
        dict(
            fee_schedules=[no_fees, no_fees, no_fees],
            incoming_fee_schedules=[no_fees, no_fees, no_fees],
            expected_transferred_amounts=[
                amount + fee, amount + fee, amount + fee
            ],
        ),
        # The first mediator claims all of the fee.
        dict(
            fee_schedules=[no_fees,
                           FeeScheduleState(flat=fee), no_fees],
            incoming_fee_schedules=[no_fees, no_fees, no_fees],
            expected_transferred_amounts=[amount + fee, amount, amount],
        ),
        # The first mediator has a proportional fee of 20%
        dict(
            fee_schedules=[
                no_fees,
                FeeScheduleState(
                    proportional=ProportionalFeeAmount(int(0.20e6))),
                no_fees,
            ],
            incoming_fee_schedules=[no_fees, no_fees, no_fees],
            expected_transferred_amounts=[
                amount + fee,
                amount + fee -
                9,  # See test_get_lock_amount_after_fees for where 9 comes from
                amount + fee -
                9,  # See test_get_lock_amount_after_fees for where 9 comes from
            ],
        ),
        # Both mediators have a proportional fee of 20%
        dict(
            fee_schedules=[
                no_fees,
                FeeScheduleState(
                    proportional=ProportionalFeeAmount(int(0.20e6))),
                FeeScheduleState(
                    proportional=ProportionalFeeAmount(int(0.20e6))),
            ],
            incoming_fee_schedules=[no_fees, no_fees, no_fees],
            expected_transferred_amounts=[
                amount + fee,
                amount + fee -
                9,  # See test_get_lock_amount_after_fees for where 9 comes from
                # See test_get_lock_amount_after_fees for where 9 and 8 come from
                amount + fee - 9 - 8,
            ],
        ),
        # The first mediator has an imbalance fee that works like a 20%
        # proportional fee when using the channel in this direction.
        dict(
            fee_schedules=[
                no_fees,
                FeeScheduleState(
                    imbalance_penalty=[(0, 200), (1000, 0)]),  # type: ignore
                no_fees,
            ],
            incoming_fee_schedules=[no_fees, no_fees, no_fees],
            expected_transferred_amounts=[
                amount + fee,
                amount + fee - (amount + fee) // 5 + 2,
                amount + fee - (amount + fee) // 5 + 2,
            ],
        ),
        # Using the same fee_schedules as above on the incoming channel instead
        # of the outgoing channel of mediator 1 should yield the same result.
        dict(
            fee_schedules=[no_fees, no_fees, no_fees],
            incoming_fee_schedules=[
                FeeScheduleState(
                    imbalance_penalty=[(0, 0), (1000, 200)]),  # type: ignore
                None,
                None,
            ],
            expected_transferred_amounts=[
                amount + fee,
                amount + fee - (amount + fee) // 5,
                amount + fee - (amount + fee) // 5,
            ],
        ),
        # The first mediator has an imbalance fee which will add 1/20 token for
        # for every token transferred as a reward for moving the channel into a
        # better state. This causes the target to receive more than the `amount
        # + fees` which is sent by the initiator.
        # transferred amount is 55, so 3 token get added from imbalance fee
        # Here we also need to disable fee capping
        dict(
            fee_schedules=[
                no_fees,
                FeeScheduleState(
                    cap_fees=False,
                    imbalance_penalty=[(0, 0), (1000, 50)]  # type: ignore
                ),
                no_fees,
            ],
            incoming_fee_schedules=[no_fees_no_cap, no_fees, no_fees],
            expected_transferred_amounts=[
                amount + fee, amount + fee + 3, amount + fee + 3
            ],
        ),
        # Same case as above, but with fee capping enabled
        dict(
            fee_schedules=[
                no_fees,
                FeeScheduleState(
                    cap_fees=True,
                    imbalance_penalty=[(0, 0), (1000, 50)]  # type: ignore
                ),
                no_fees,
            ],
            incoming_fee_schedules=[no_fees, no_fees, no_fees],
            expected_transferred_amounts=[
                amount + fee, amount + fee, amount + fee
            ],
        ),
    ]

    case = cases[case_no]
    for i, fee_schedule in enumerate(case.get("fee_schedules", [])):
        if fee_schedule:
            set_fee_schedule(apps[i], apps[i + 1], fee_schedule)
Пример #18
0
DEFAULT_INITIAL_CHANNEL_TARGET = 3
DEFAULT_WAIT_FOR_SETTLE = True
DEFAULT_NUMBER_OF_BLOCK_CONFIRMATIONS = BlockTimeout(5)
DEFAULT_WAIT_BEFORE_LOCK_REMOVAL = BlockTimeout(
    2 * DEFAULT_NUMBER_OF_BLOCK_CONFIRMATIONS)
DEFAULT_CHANNEL_SYNC_TIMEOUT = 5

DEFAULT_SHUTDOWN_TIMEOUT = 2

DEFAULT_PATHFINDING_MAX_PATHS = 3
DEFAULT_PATHFINDING_MAX_FEE = TokenAmount(5 * 10**16)  # about .01$
# PFS has 200 000 blocks (~40days) to cash in
DEFAULT_PATHFINDING_IOU_TIMEOUT = BlockTimeout(2 * 10**5)

DEFAULT_MEDIATION_FLAT_FEE = FeeAmount(0)
DEFAULT_MEDIATION_PROPORTIONAL_FEE = ProportionalFeeAmount(
    4000)  # 0.4% in parts per million
DEFAULT_MEDIATION_PROPORTIONAL_IMBALANCE_FEE = ProportionalFeeAmount(
    3000  # 0.3% in parts per million
)
DEFAULT_MEDIATION_FEE_MARGIN: float = 0.03
PAYMENT_AMOUNT_BASED_FEE_MARGIN: float = 0.0005
INTERNAL_ROUTING_DEFAULT_FEE_PERC: float = 0.02
MAX_MEDIATION_FEE_PERC: float = 0.2

ORACLE_BLOCKNUMBER_DRIFT_TOLERANCE = 3
ETHERSCAN_API = "https://{network}.etherscan.io/api?module=proxy&action={action}"

RAIDEN_CONTRACT_VERSION = raiden_contracts.constants.CONTRACTS_VERSION

MIN_REI_THRESHOLD = TokenAmount(55 * 10**17)  # about 1.1$
Пример #19
0
class FeeScheduleState(State):
    # pylint: disable=not-an-iterable
    cap_fees: bool = True
    flat: FeeAmount = FeeAmount(0)
    proportional: ProportionalFeeAmount = ProportionalFeeAmount(
        0)  # as micros, e.g. 1% = 0.01e6
    imbalance_penalty: Optional[List[Tuple[TokenAmount, FeeAmount]]] = None
    _penalty_func: Optional[Interpolate] = field(init=False,
                                                 repr=False,
                                                 default=None)

    def __post_init__(self) -> None:
        self._update_penalty_func()

    def _update_penalty_func(self) -> None:
        if self.imbalance_penalty:
            typecheck(self.imbalance_penalty, list)
            x_list, y_list = tuple(zip(*self.imbalance_penalty))
            self._penalty_func = Interpolate(x_list, y_list)  # type: ignore

    def fee(self, balance: Balance, amount: Fraction) -> Fraction:
        return (self.flat +
                Fraction(self.proportional, int(1e6)) * Fraction(abs(amount)) +
                (self._penalty_func(balance + amount) -
                 self._penalty_func(balance))
                if self._penalty_func else Fraction(0))

    @staticmethod
    def mediation_fee_func(
        schedule_in: "FeeScheduleState",
        schedule_out: "FeeScheduleState",
        balance_in: Balance,
        balance_out: Balance,
        receivable: TokenAmount,
        amount_with_fees: PaymentWithFeeAmount,
        cap_fees: bool,
    ) -> Interpolate:
        """ Returns a function which calculates total_mediation_fee(amount_without_fees) """
        return _mediation_fee_func(
            schedule_in=schedule_in,
            schedule_out=schedule_out,
            balance_in=balance_in,
            balance_out=balance_out,
            receivable=receivable,
            amount_with_fees=amount_with_fees,
            amount_without_fees=None,
            cap_fees=cap_fees,
        )

    @staticmethod
    def mediation_fee_backwards_func(
        schedule_in: "FeeScheduleState",
        schedule_out: "FeeScheduleState",
        balance_in: Balance,
        balance_out: Balance,
        receivable: TokenAmount,
        amount_without_fees: PaymentWithFeeAmount,
        cap_fees: bool,
    ) -> Interpolate:
        """ Returns a function which calculates total_mediation_fee(amount_with_fees) """
        return _mediation_fee_func(
            schedule_in=schedule_in,
            schedule_out=schedule_out,
            balance_in=balance_in,
            balance_out=balance_out,
            receivable=receivable,
            amount_with_fees=None,
            amount_without_fees=amount_without_fees,
            cap_fees=cap_fees,
        )
Пример #20
0
def test_fee_round_trip(flat_fee, prop_fee, imbalance_fee, amount, balance1, balance2):
    """Tests mediation fee deduction.

    First we're doing a PFS-like calculation going backwards from the target
    amount to get the amount that the initiator has to send. Then we calculate
    the fees from a mediator's point of view and check if `amount_with_fees -
    fees = amount`.
    """
    # Find examples where there is a reasonable chance of succeeding
    amount = int(min(amount, balance1 * 0.95 - 1, balance2 * 0.95 - 1))
    assume(amount > 0)

    total_balance = TokenAmount(100_000_000_000_000_000_000)
    prop_fee_per_channel = ppm_fee_per_channel(ProportionalFeeAmount(prop_fee))
    imbalance_fee = calculate_imbalance_fees(
        channel_capacity=total_balance,
        proportional_imbalance_fee=ProportionalFeeAmount(imbalance_fee),
    )
    channel_in = factories.create(
        NettingChannelStateProperties(
            our_state=NettingChannelEndStateProperties(balance=total_balance - balance1),
            partner_state=NettingChannelEndStateProperties(balance=balance1),
            fee_schedule=FeeScheduleState(
                cap_fees=False,
                flat=FeeAmount(flat_fee),
                proportional=prop_fee_per_channel,
                imbalance_penalty=imbalance_fee,
            ),
        )
    )
    channel_out = factories.create(
        NettingChannelStateProperties(
            our_state=NettingChannelEndStateProperties(balance=balance2),
            partner_state=NettingChannelEndStateProperties(balance=total_balance - balance2),
            fee_schedule=FeeScheduleState(
                cap_fees=False,
                flat=FeeAmount(flat_fee),
                proportional=prop_fee_per_channel,
                imbalance_penalty=imbalance_fee,
            ),
        )
    )

    # How much do we need to send so that the target receives `amount`? PFS-like calculation.
    fee_calculation = get_initial_amount_for_amount_after_fees(
        amount_after_fees=PaymentAmount(amount), channels=[(channel_in, channel_out)]
    )
    assume(fee_calculation)  # There is not enough capacity for the payment in all cases
    assert fee_calculation

    # How much would a mediator send to the target? Ideally exactly `amount`.
    amount_without_margin_after_fees = get_amount_without_fees(
        amount_with_fees=fee_calculation.total_amount,
        channel_in=channel_in,
        channel_out=channel_out,
    )
    assume(amount_without_margin_after_fees)  # We might lack capacity for the payment
    assert abs(amount - amount_without_margin_after_fees) <= 1  # Equal except for rounding errors

    # If we add the fee margin, the mediator must always send at least `amount` to the target!
    amount_with_fee_and_margin = calculate_safe_amount_with_fee(
        fee_calculation.amount_without_fees, FeeAmount(sum(fee_calculation.mediation_fees))
    )
    amount_with_margin_after_fees = get_amount_without_fees(
        amount_with_fees=amount_with_fee_and_margin, channel_in=channel_in, channel_out=channel_out
    )
    assume(amount_with_margin_after_fees)  # We might lack capacity to add margins
    assert amount_with_margin_after_fees >= amount
Пример #21
0
class FeeScheduleState(State):
    # pylint: disable=not-an-iterable
    flat: FeeAmount = FeeAmount(0)
    proportional: ProportionalFeeAmount = ProportionalFeeAmount(
        0)  # as micros, e.g. 1% = 0.01e6
    imbalance_penalty: Optional[List[Tuple[TokenAmount, FeeAmount]]] = None
    _penalty_func: Optional[Interpolate] = field(init=False,
                                                 repr=False,
                                                 default=None)

    def __post_init__(self) -> None:
        self._update_penalty_func()

    def _update_penalty_func(self) -> None:
        if self.imbalance_penalty:
            assert isinstance(self.imbalance_penalty, list)
            x_list, y_list = tuple(zip(*self.imbalance_penalty))
            self._penalty_func = Interpolate(x_list, y_list)

    def imbalance_fee(self, amount: PaymentWithFeeAmount,
                      balance: Balance) -> FeeAmount:
        if self._penalty_func:
            # Total channel balance - node balance = balance (used as x-axis for the penalty)
            balance = self._penalty_func.x_list[-1] - balance
            try:
                return FeeAmount(
                    round(
                        self._penalty_func(balance + amount) -
                        self._penalty_func(balance)))
            except ValueError:
                raise UndefinedMediationFee()

        return FeeAmount(0)

    def fee_payer(self, amount: PaymentWithFeeAmount,
                  balance: Balance) -> FeeAmount:
        imbalance_fee = self.imbalance_fee(
            amount=PaymentWithFeeAmount(-amount), balance=balance)

        flat_fee = self.flat
        prop_fee = int(round(amount * self.proportional / 1e6))
        return FeeAmount(flat_fee + prop_fee + imbalance_fee)

    def fee_payee(self,
                  amount: PaymentWithFeeAmount,
                  balance: Balance,
                  iterations: int = 2) -> FeeAmount:
        def fee_out(imbalance_fee: FeeAmount) -> FeeAmount:
            return FeeAmount(
                round(amount - ((amount - self.flat - imbalance_fee) /
                                (1 + self.proportional / 1e6))))

        imbalance_fee = FeeAmount(0)
        for _ in range(iterations):
            imbalance_fee = self.imbalance_fee(
                amount=PaymentWithFeeAmount(amount - fee_out(imbalance_fee)),
                balance=balance)

        return fee_out(imbalance_fee)

    def reversed(self: T) -> T:
        if not self.imbalance_penalty:
            return replace(self)
        max_penalty = max(penalty for x, penalty in self.imbalance_penalty)
        reversed_instance = replace(
            self,
            imbalance_penalty=[(x, FeeAmount(max_penalty - penalty))
                               for x, penalty in self.imbalance_penalty],
        )
        self._update_penalty_func()
        return reversed_instance
Пример #22
0
def test_edge_weight(addresses):
    # pylint: disable=assigning-non-slot
    channel_id = ChannelID(1)
    participant1 = addresses[0]
    participant2 = addresses[1]
    capacity = TokenAmount(int(20 * 1e18))
    capacity_partner = TokenAmount(int(10 * 1e18))
    settle_timeout = BlockTimeout(15)
    channel = Channel(
        token_network_address=TokenNetworkAddress(bytes([1])),
        channel_id=channel_id,
        participant1=participant1,
        participant2=participant2,
        capacity1=capacity,
        capacity2=capacity_partner,
        settle_timeout=settle_timeout,
    )
    view, view_partner = channel.views
    amount = PaymentAmount(int(1e18))  # one RDN

    # no penalty
    assert (TokenNetwork.edge_weight(visited=dict(),
                                     view=view,
                                     view_from_partner=view_partner,
                                     amount=amount,
                                     fee_penalty=0) == 1)

    # channel already used in a previous route
    assert (TokenNetwork.edge_weight(
        visited={channel_id: 2},
        view=view,
        view_from_partner=view_partner,
        amount=amount,
        fee_penalty=0,
    ) == 3)

    # absolute fee
    view.fee_schedule_sender.flat = FeeAmount(int(0.03e18))
    assert (TokenNetwork.edge_weight(
        visited=dict(),
        view=view,
        view_from_partner=view_partner,
        amount=amount,
        fee_penalty=100,
    ) == 4)

    # relative fee
    view.fee_schedule_sender.flat = FeeAmount(0)
    view.fee_schedule_sender.proportional = ProportionalFeeAmount(int(0.01e6))
    assert (TokenNetwork.edge_weight(
        visited=dict(),
        view=view,
        view_from_partner=view_partner,
        amount=amount,
        fee_penalty=100,
    ) == 2)

    # partner has not enough capacity for refund (no_refund_weight) -> edge weight +1
    view_partner.capacity = TokenAmount(0)
    assert (TokenNetwork.edge_weight(
        visited=dict(),
        view=view,
        view_from_partner=view_partner,
        amount=amount,
        fee_penalty=100,
    ) == 3)
Пример #23
0
def test_fees_are_updated_during_startup(
    raiden_network, token_addresses, deposit, retry_timeout
) -> None:
    """
    Test that the supplied fee settings are correctly forwarded to all
    channels during node startup.
    """
    app0, app1 = raiden_network

    token_address = token_addresses[0]

    def get_channel_state(app) -> NettingChannelState:
        chain_state = views.state_from_app(app)
        token_network_registry_address = app.raiden.default_registry.address
        token_network_address = views.get_token_network_address_by_token_address(
            chain_state, token_network_registry_address, token_address
        )
        assert token_network_address
        channel_state = views.get_channelstate_by_token_network_and_partner(
            chain_state, token_network_address, app1.raiden.address
        )
        assert channel_state

        return channel_state

    waiting.wait_both_channel_deposit(
        app0, app1, app0.raiden.default_registry.address, token_address, deposit, retry_timeout
    )
    # This is the imbalance penalty generated for the deposit
    # with DEFAULT_MEDIATION_PROPORTIONAL_IMBALANCE_FEE
    # once both channels have deposited the default (200) deposit
    default_imbalance_penalty = [
        (0, 1),
        (20, 0),
        (40, 0),
        (60, 0),
        (80, 0),
        (100, 0),
        (120, 0),
        (140, 0),
        (160, 0),
        (180, 0),
        (200, 0),
        (220, 0),
        (240, 0),
        (260, 0),
        (280, 0),
        (300, 0),
        (320, 0),
        (340, 0),
        (360, 0),
        (380, 0),
        (400, 1),
    ]

    # Check that the defaults are used
    channel_state = get_channel_state(app0)
    assert channel_state.fee_schedule.flat == DEFAULT_MEDIATION_FLAT_FEE
    assert channel_state.fee_schedule.proportional == DEFAULT_MEDIATION_PROPORTIONAL_FEE
    assert channel_state.fee_schedule.imbalance_penalty == default_imbalance_penalty

    orginal_config = app0.raiden.config.copy()

    # Now restart app0, and set new flat fee for that token network
    flat_fee = FeeAmount(100)
    app0.stop()
    app0.raiden.config = deepcopy(orginal_config)
    app0.raiden.config["mediation_fees"].token_to_flat_fee = {token_address: flat_fee}
    app0.start()

    channel_state = get_channel_state(app0)
    assert channel_state.fee_schedule.flat == flat_fee
    assert channel_state.fee_schedule.proportional == DEFAULT_MEDIATION_PROPORTIONAL_FEE
    assert channel_state.fee_schedule.imbalance_penalty == default_imbalance_penalty

    # Now restart app0, and set new proportional fee
    prop_fee = ProportionalFeeAmount(123)
    app0.stop()
    app0.raiden.config = deepcopy(orginal_config)
    app0.raiden.config["mediation_fees"].token_to_proportional_fee = {token_address: prop_fee}
    app0.start()

    channel_state = get_channel_state(app0)
    assert channel_state.fee_schedule.flat == DEFAULT_MEDIATION_FLAT_FEE
    assert channel_state.fee_schedule.proportional == prop_fee
    assert channel_state.fee_schedule.imbalance_penalty == default_imbalance_penalty

    # Now restart app0, and set new proportional imbalance fee
    app0.stop()
    app0.raiden.config = deepcopy(orginal_config)
    app0.raiden.config["mediation_fees"].token_to_proportional_imbalance_fee = {
        token_address: 0.05e6
    }
    app0.start()

    channel_state = get_channel_state(app0)
    assert channel_state.fee_schedule.flat == DEFAULT_MEDIATION_FLAT_FEE
    assert channel_state.fee_schedule.proportional == DEFAULT_MEDIATION_PROPORTIONAL_FEE
    # with 5% imbalance fee
    full_imbalance_penalty = [
        (0, 20),
        (20, 18),
        (40, 16),
        (60, 14),
        (80, 12),
        (100, 10),
        (120, 8),
        (140, 6),
        (160, 4),
        (180, 2),
        (200, 0),
        (220, 2),
        (240, 4),
        (260, 6),
        (280, 8),
        (300, 10),
        (320, 12),
        (340, 14),
        (360, 16),
        (380, 18),
        (400, 20),
    ]

    assert channel_state.fee_schedule.imbalance_penalty == full_imbalance_penalty