def test_mfee4(): """ Unit test for the fee calculation in the mfee4_combined_fees scenario """ amount = PaymentAmount(500_000_000_000_000_000) deposit = 1_000_000_000_000_000_000 prop_fee = ppm_fee_per_channel(ProportionalFeeAmount(10_000)) imbalance_penalty = calculate_imbalance_fees( TokenAmount(deposit * 2), ProportionalFeeAmount(20_000) ) fee_schedule = FeeScheduleState( flat=FeeAmount(100 // 2), proportional=prop_fee, imbalance_penalty=imbalance_penalty, cap_fees=False, ) channels = make_channel_pair(fee_schedule, deposit, 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( amount, FeeAmount(sum(fee_calculation.mediation_fees)) ) # Calculate mediation fees for both mediators med_fees = [] incoming_amount = amount_with_margin for _ in range(2): outgoing_amount = get_amount_without_fees( amount_with_fees=incoming_amount, channel_in=channels[0], channel_out=channels[1] ) assert outgoing_amount med_fees.append(incoming_amount - outgoing_amount) incoming_amount = outgoing_amount assert amount_with_margin == 543_503_066_141_505_551 # print values for scenario print("{:_} {:_}".format(deposit - amount_with_margin, deposit + amount_with_margin)) for med_fee in running_sum(med_fees): print( "{:_} {:_}".format( deposit - amount_with_margin + med_fee, deposit + amount_with_margin - med_fee ) )
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
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
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
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