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
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
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
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_initial_payment_for_final_target_amount( flat_fee: FeeAmount, prop_fee: ProportionalFeeAmount, balance: TokenAmount, final_amount: PaymentAmount, initial_amount: PaymentWithFeeAmount, expected_fees: List[FeeAmount], ): prop_fee = ppm_fee_per_channel(prop_fee) channel_set = make_channel_set([ NettingChannelStateProperties( canonical_identifier=factories.create( CanonicalIdentifierProperties( channel_identifier=ChannelID(1))), our_state=NettingChannelEndStateProperties(balance=TokenAmount(0)), partner_state=NettingChannelEndStateProperties(balance=balance), fee_schedule=FeeScheduleState(flat=flat_fee, proportional=prop_fee), ), NettingChannelStateProperties( canonical_identifier=factories.create( CanonicalIdentifierProperties( channel_identifier=ChannelID(2))), our_state=NettingChannelEndStateProperties(balance=balance), partner_state=NettingChannelEndStateProperties( balance=TokenAmount(0)), fee_schedule=FeeScheduleState(flat=flat_fee, proportional=prop_fee), ), ]) calculation = get_initial_amount_for_amount_after_fees( amount_after_fees=final_amount, channels=[(channel_set.channels[0], channel_set.channels[1])], ) assert calculation is not None assert calculation.total_amount == initial_amount assert calculation.mediation_fees == expected_fees
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
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