def test_mfee3(): """ Unit test for the fee calculation in the mfee3_only_imbalance_fees scenario """ amount = 500_000_000_000_000_000 deposit = TokenAmount(1_000_000_000_000_000_000) imbalance_penalty = calculate_imbalance_fees(deposit, ProportionalFeeAmount(10_000)) fee_schedule = FeeScheduleState(imbalance_penalty=imbalance_penalty, cap_fees=False) 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] ) 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 == 480_850_038_799_922_400 # print values for scenario print("{:_} {:_}".format(deposit - amount_with_margin, amount_with_margin)) for med_fee in running_sum(fee_calculation.mediation_fees): print( "{:_} {:_}".format( deposit - amount_with_margin + med_fee, amount_with_margin - med_fee ) )
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 get_amount_for_sending_before_and_after_fees( amount_to_leave_initiator: PaymentAmount, channels: List[NettingChannelState] ) -> Optional[PaymentAmountCalculation]: """ Calculates the amount needed to be sent by the initiator (before fees) in order for his balance to be reduced by `amount_to_leave_initiator`. Also returns the fees kept by the mediators. """ amount_at_target = amount_to_leave_initiator while amount_at_target != 0: calculation = get_initial_payment_for_final_target_amount( final_amount=amount_at_target, channels=channels ) if calculation is None: amount_at_target = PaymentAmount(amount_at_target - 1) continue total_amount_with_mediator_fees = calculation.total_amount mediation_fees = sum(calculation.mediation_fees) estimated_fee = max( mediation_fees, round(INTERNAL_ROUTING_DEFAULT_FEE_PERC * amount_at_target) ) estimated_total_amount_at_initiator = calculate_safe_amount_with_fee( payment_amount=amount_at_target, estimated_fee=FeeAmount(estimated_fee) ) send_amount = min( estimated_total_amount_at_initiator, total_amount_with_mediator_fees - mediation_fees ) send_amount_with_fees = max( estimated_total_amount_at_initiator, total_amount_with_mediator_fees ) if send_amount_with_fees <= amount_to_leave_initiator: return PaymentAmountCalculation( amount_to_send=PaymentAmount(send_amount), mediation_fees=calculation.mediation_fees, amount_with_fees=send_amount_with_fees, ) amount_at_target = PaymentAmount(amount_at_target - 1) return None
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)
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