From 34462be9cf354822fa6ca1eeae2eb4bee67d6a64 Mon Sep 17 00:00:00 2001 From: Felix Henneke Date: Fri, 19 Jan 2024 12:19:58 +0100 Subject: [PATCH] add protocol fees to transfers - new protocol_fee_eth field in payouts - subtract protocol fee from eth reimbursement - update tests: add one new test, update old tests (also for quote rewards) --- src/fetch/payouts.py | 10 ++++- tests/unit/test_payouts.py | 78 +++++++++++++++++++++++++++++--------- 2 files changed, 69 insertions(+), 19 deletions(-) diff --git a/src/fetch/payouts.py b/src/fetch/payouts.py index 45998382..93247683 100644 --- a/src/fetch/payouts.py +++ b/src/fetch/payouts.py @@ -33,6 +33,7 @@ "reward_cow", "secondary_reward_cow", "quote_reward_cow", + "protocol_fee_eth", } SLIPPAGE_COLUMNS = { "solver", @@ -79,10 +80,13 @@ def __init__( # pylint: disable=too-many-arguments primary_reward_cow: int, secondary_reward_cow: int, quote_reward_cow: int, + protocol_fee_eth: int, ): assert exec_cost >= 0, f"invalid execution cost {exec_cost}" assert secondary_reward_eth >= 0, "invalid secondary_reward_eth" assert secondary_reward_cow >= 0, "invalid secondary_reward_cow" + assert quote_reward_cow >= 0, "invalid quote reward" + assert protocol_fee_eth >= 0, "invalid protocol_fee_eth" self.solver = solver self.solver_name = solver_name @@ -94,6 +98,7 @@ def __init__( # pylint: disable=too-many-arguments self.secondary_reward_eth = secondary_reward_eth self.secondary_reward_cow = secondary_reward_cow self.quote_reward_cow = quote_reward_cow + self.protocol_fee_eth = protocol_fee_eth @classmethod def from_series(cls, frame: Series) -> RewardAndPenaltyDatum: @@ -120,11 +125,12 @@ def from_series(cls, frame: Series) -> RewardAndPenaltyDatum: secondary_reward_eth=int(frame["secondary_reward_eth"]), secondary_reward_cow=int(frame["secondary_reward_cow"]), quote_reward_cow=int(frame["quote_reward_cow"]), + protocol_fee_eth=int(frame["protocol_fee_eth"]), ) def total_outgoing_eth(self) -> int: """Total outgoing amount (including slippage) for the payout.""" - return self.payment_eth + self.secondary_reward_eth + self.slippage_eth + return self.payment_eth + self.secondary_reward_eth + self.slippage_eth - self.protocol_fee_eth def total_cow_reward(self) -> int: """Total outgoing COW token reward""" @@ -161,7 +167,7 @@ def as_payouts(self) -> list[Transfer]: total_eth_reward = int(self.total_eth_reward()) total_cow_reward = int(self.total_cow_reward()) - reimbursement_eth = int(self.exec_cost + self.slippage_eth) + reimbursement_eth = int(self.exec_cost + self.slippage_eth - self.protocol_fee_eth) # We do not have access to token conversion here, but we do have other converted values # x_eth:x_cow = y_eth:y_cow --> y_cow = y_eth * x_cow / x_eth reimbursement_cow = ( diff --git a/tests/unit/test_payouts.py b/tests/unit/test_payouts.py index fc4ff8e5..efd1c214 100644 --- a/tests/unit/test_payouts.py +++ b/tests/unit/test_payouts.py @@ -67,6 +67,12 @@ def setUp(self) -> None: 7, 6, ] + self.protocol_fees = [ + 300000000000000.0, + 200000000000000.0, + 100000000000000.0, + 0.0, + ] # Mocking TokenConversion! self.mock_converter = TokenConversion( eth_to_token=lambda t: int(t * 1000), token_to_eth=lambda t: t // 1000 @@ -79,6 +85,7 @@ def test_extend_payment_df(self): "payment_eth": self.eth_payments, "execution_cost_eth": self.execution_costs, "num_participating_batches": self.batch_participation, + "protocol_fee_eth": self.protocol_fees } base_payout_df = DataFrame(base_data_dict) result = extend_payment_df(base_payout_df, converter=self.mock_converter) @@ -88,6 +95,7 @@ def test_extend_payment_df(self): "payment_eth": self.eth_payments, "execution_cost_eth": self.execution_costs, "num_participating_batches": self.batch_participation, + "protocol_fee_eth": self.protocol_fees, "reward_eth": [ -200000000000000.00000, 10000000000000000.00000, @@ -142,6 +150,7 @@ def test_validate_df_columns(self): "payment_eth": [], "execution_cost_eth": [], "num_participating_batches": [], + "protocol_fee_eth": [], "reward_eth": [], "reward_cow": [], "secondary_reward_cow": [], @@ -191,6 +200,7 @@ def test_construct_payouts(self): "payment_eth": self.eth_payments, "execution_cost_eth": self.execution_costs, "num_participating_batches": self.batch_participation, + "protocol_fee_eth": self.protocol_fees, } ), converter=self.mock_converter, @@ -219,6 +229,7 @@ def test_construct_payouts(self): "payment_eth": [600000000000000.0, 1.045e16, -1e16, 0.0], "execution_cost_eth": [800000000000000.0, 450000000000000.0, 0.0, 0.0], "num_participating_batches": [7, 2, 7, 6], + "protocol_fee_eth": self.protocol_fees, "reward_eth": [-200000000000000.0, 1e16, -1e16, 0.0], "reward_cow": [ -200000000000000000, @@ -290,6 +301,12 @@ def test_prepare_transfers(self): 90000000000000000000.00000, 180000000000000000000.00000, ], + "protocol_fee_eth": [ + 300000000000000.0, + 200000000000000.0, + 100000000000000.0, + 0.0, + ], "solver_name": ["S_1", "S_2", "S_3", None], "eth_slippage_wei": [1.0, 0.0, -1.0, None], "reward_target": [ @@ -308,12 +325,12 @@ def test_prepare_transfers(self): Transfer( token=None, recipient=Address(self.solvers[0]), - amount_wei=663636363636364, + amount_wei=363636363636364, ), Transfer( token=None, recipient=Address(self.solvers[1]), - amount_wei=450000000000000, + amount_wei=250000000000000, ), Transfer( token=Token(COW_TOKEN_ADDRESS), @@ -344,7 +361,7 @@ def test_prepare_transfers(self): Overdraft( period, account=Address(self.solvers[2]), - wei=9936363636363638, + wei=10036363636363638, name="S_3", ) ], @@ -366,6 +383,7 @@ def sample_record( participation: int, slippage: int, num_quotes: int, + protocol_fee: int, ): """Assumes a conversion rate of ETH:COW <> 1:self.conversion_rate""" return RewardAndPenaltyDatum( @@ -379,32 +397,39 @@ def sample_record( secondary_reward_cow=participation * self.conversion_rate, slippage_eth=slippage, quote_reward_cow=QUOTE_REWARD * num_quotes, + protocol_fee_eth=protocol_fee, ) def test_invalid_input(self): with self.assertRaises(AssertionError): - self.sample_record(0, -1, 0, 0, 0) + self.sample_record(0, -1, 0, 0, 0, 0) + + with self.assertRaises(AssertionError): + self.sample_record(0, 0, -1, 0, 0, 0) + + with self.assertRaises(AssertionError): + self.sample_record(0, 0, 0, 0, -1, 0) with self.assertRaises(AssertionError): - self.sample_record(0, 0, -1, 0, 0) + self.sample_record(0, 0, 0, 0, 0, -1) - def test_reward_datum_0_0_0_0(self): - test_datum = self.sample_record(0, 0, 0, 0, 0) + def test_reward_datum_0_0_0_0_0(self): + test_datum = self.sample_record(0, 0, 0, 0, 0, 0) self.assertFalse(test_datum.is_overdraft()) self.assertEqual(test_datum.as_payouts(), []) - def test_reward_datum_1_1_0_0(self): + def test_reward_datum_1_1_0_0_0_0(self): cost = 1 - test_datum = self.sample_record(1, cost, 0, 0, 0) + test_datum = self.sample_record(1, cost, 0, 0, 0, 0) self.assertFalse(test_datum.is_overdraft()) self.assertEqual( test_datum.as_payouts(), [Transfer(token=None, recipient=self.solver, amount_wei=cost)], ) - def test_reward_datum_3_2_0_minus1(self): + def test_reward_datum_3_2_0_minus1_0_0(self): payment, cost, participation, slippage = 3, 2, 0, -1 - test_datum = self.sample_record(payment, cost, participation, slippage, 0) + test_datum = self.sample_record(payment, cost, participation, slippage, 0, 0) self.assertFalse(test_datum.is_overdraft()) self.assertEqual( test_datum.as_payouts(), @@ -421,11 +446,30 @@ def test_reward_datum_3_2_0_minus1(self): ), ], ) + def test_reward_datum_8_4_0_minus1_0_2(self): + payment, cost, participation, slippage, protocol_fee = 8, 4, 0, -1, 2 + test_datum = self.sample_record(payment, cost, participation, slippage, 0, protocol_fee) + self.assertFalse(test_datum.is_overdraft()) + self.assertEqual( + test_datum.as_payouts(), + [ + Transfer( + token=None, + recipient=self.solver, + amount_wei=cost + slippage - protocol_fee, + ), + Transfer( + token=self.cow_token, + recipient=self.reward_target, + amount_wei=(payment - cost) * self.conversion_rate, + ), + ], + ) def test_reward_datum_cost_exceeds_payment_degenerate(self): # Degenerate Case! payment, cost, participation, slippage = 1, 10, 0, -1 - test_datum = self.sample_record(payment, cost, participation, slippage, 0) + test_datum = self.sample_record(payment, cost, participation, slippage, 0, 0) self.assertFalse(test_datum.is_overdraft()) self.assertEqual( test_datum.as_payouts(), @@ -439,7 +483,7 @@ def test_reward_datum_cost_exceeds_payment_non_degenerate(self): cost = max(sum(x) for x in triplets) + 1 for payment, participation, slippage in triplets: - test_datum = self.sample_record(payment, cost, participation, slippage, 0) + test_datum = self.sample_record(payment, cost, participation, slippage, 0, 0) self.assertFalse(test_datum.is_overdraft()) self.assertLess(test_datum.total_outgoing_eth(), cost) self.assertEqual( @@ -462,12 +506,12 @@ def test_reward_datum_overdraft(self): for payment, participation, slippage in triplets: for cost in [0, 1, 100]: # Doesn't matter their costs, they are in overdraft state! - rec = self.sample_record(payment, cost, participation, slippage, 0) + rec = self.sample_record(payment, cost, participation, slippage, 0, 0) self.assertTrue(rec.is_overdraft()) - def test_reward_datum_1_1_1_1(self): + def test_reward_datum_1_1_1_1_0_0(self): payment, cost, participation, slippage = 1, 1, 1, 1 - test_datum = self.sample_record(payment, cost, participation, slippage, 0) + test_datum = self.sample_record(payment, cost, participation, slippage, 0, 0) self.assertFalse(test_datum.is_overdraft()) self.assertEqual( @@ -491,7 +535,7 @@ def test_reward_datum_1_1_1_1(self): def test_payout_negative_payments(self): payment, cost, participation, slippage = -1, 1, 1, 1 - test_datum = self.sample_record(payment, cost, participation, slippage, 0) + test_datum = self.sample_record(payment, cost, participation, slippage, 0, 0) self.assertEqual( test_datum.as_payouts(), [