diff --git a/queries/orderbook/barn_batch_rewards.sql b/queries/orderbook/barn_batch_rewards.sql index a7407368..61be7a3a 100644 --- a/queries/orderbook/barn_batch_rewards.sql +++ b/queries/orderbook/barn_batch_rewards.sql @@ -86,7 +86,7 @@ order_protocol_fee AS ( -- impossible anyways. This query will return a division by -- zero error in that case. LEAST( - fp.surplus_max_volume_factor * os.sell_amount * os.buy_amount / (os.sell_amount - os.observed_fee), + fp.surplus_max_volume_factor / (1 - fp.surplus_max_volume_factor) * os.buy_amount, -- at most charge a fraction of volume fp.surplus_factor / (1 - fp.surplus_factor) * surplus -- charge a fraction of surplus ) @@ -96,12 +96,14 @@ order_protocol_fee AS ( fp.surplus_factor / (1 - fp.surplus_factor) * surplus -- charge a fraction of surplus ) END - WHEN fp.kind = 'volume' THEN fp.volume_factor / (1 + fp.volume_factor) * os.sell_amount + WHEN fp.kind = 'volume' THEN CASE + WHEN os.kind = 'sell' THEN + fp.volume_factor / (1 - fp.volume_factor) * os.buy_amount + WHEN os.kind = 'buy' THEN + fp.volume_factor / (1 + fp.volume_factor) * os.sell_amount + END END AS protocol_fee, - CASE - WHEN fp.kind = 'surplus' THEN os.surplus_token - WHEN fp.kind = 'volume' THEN os.sell_token - END AS protocol_fee_token + os.surplus_token AS protocol_fee_token FROM order_surplus os JOIN fee_policies fp -- contains protocol fee policy @@ -160,17 +162,17 @@ reward_data AS ( case when block_number is not null and block_number > block_deadline then 0 - else coalesce(execution_cost, 0) + else coalesce(execution_cost, 0) -- if block_number is null, execution cost is 0 end as execution_cost, case when block_number is not null and block_number > block_deadline then 0 - else coalesce(surplus, 0) + else coalesce(surplus, 0) -- if block_number is null, surplus is 0 end as surplus, case when block_number is not null and block_number > block_deadline then 0 - else coalesce(fee, 0) + else coalesce(fee, 0) -- if block_number is null, fee is 0 end as fee, -- scores winning_score, @@ -201,14 +203,14 @@ reward_per_auction as ( surplus, protocol_fee, -- the protocol fee fee - network_fee_correction as network_fee, -- the network fee - surplus + protocol_fee + fee - network_fee_correction - reference_score as uncapped_payment_eth, + surplus + protocol_fee - reference_score as uncapped_payment, -- Capped Reward = CLAMP_[-E, E + exec_cost](uncapped_reward_eth) LEAST( GREATEST( - {{EPSILON_LOWER}}, - surplus + protocol_fee + fee - network_fee_correction - reference_score + surplus + protocol_fee - reference_score ), - {{EPSILON_UPPER}} + execution_cost + {{EPSILON_UPPER}} ) as capped_payment, winning_score, reference_score, @@ -235,17 +237,9 @@ participation_counts as ( primary_rewards as ( SELECT rpt.solver, - SUM(capped_payment) as payment_wei, - SUM(execution_cost) as exececution_cost_wei - FROM - reward_per_auction rpt - GROUP BY - solver -), -protocol_fees as ( - SELECT - solver, - SUM(protocol_fee) as protocol_fee_wei + SUM(capped_payment) as payment, + SUM(protocol_fee) as protocol_fee, + SUM(network_fee) as network_fee FROM reward_per_auction rpt GROUP BY @@ -254,18 +248,17 @@ protocol_fees as ( aggregate_results as ( SELECT concat('0x', encode(pc.solver, 'hex')) as solver, - coalesce(payment_wei, 0) as payment_eth, - coalesce(exececution_cost_wei, 0) as execution_cost_eth, + coalesce(payment, 0) as primary_reward_eth, num_participating_batches, - coalesce(protocol_fee_wei, 0) as protocol_fee_eth + coalesce(protocol_fee, 0) as protocol_fee_eth, + coalesce(network_fee, 0) as network_fee_eth FROM participation_counts pc LEFT OUTER JOIN primary_rewards pr ON pr.solver = pc.solver - LEFT OUTER JOIN protocol_fees pf ON pf.solver = pc.solver ) -- select * from aggregate_results order by - solver \ No newline at end of file + solver diff --git a/queries/orderbook/prod_batch_rewards.sql b/queries/orderbook/prod_batch_rewards.sql index 621f701d..61be7a3a 100644 --- a/queries/orderbook/prod_batch_rewards.sql +++ b/queries/orderbook/prod_batch_rewards.sql @@ -162,17 +162,17 @@ reward_data AS ( case when block_number is not null and block_number > block_deadline then 0 - else coalesce(execution_cost, 0) + else coalesce(execution_cost, 0) -- if block_number is null, execution cost is 0 end as execution_cost, case when block_number is not null and block_number > block_deadline then 0 - else coalesce(surplus, 0) + else coalesce(surplus, 0) -- if block_number is null, surplus is 0 end as surplus, case when block_number is not null and block_number > block_deadline then 0 - else coalesce(fee, 0) + else coalesce(fee, 0) -- if block_number is null, fee is 0 end as fee, -- scores winning_score, @@ -203,14 +203,14 @@ reward_per_auction as ( surplus, protocol_fee, -- the protocol fee fee - network_fee_correction as network_fee, -- the network fee - surplus + protocol_fee + fee - network_fee_correction - reference_score as uncapped_payment_eth, + surplus + protocol_fee - reference_score as uncapped_payment, -- Capped Reward = CLAMP_[-E, E + exec_cost](uncapped_reward_eth) LEAST( GREATEST( - {{EPSILON_LOWER}}, - surplus + protocol_fee + fee - network_fee_correction - reference_score + surplus + protocol_fee - reference_score ), - {{EPSILON_UPPER}} + execution_cost + {{EPSILON_UPPER}} ) as capped_payment, winning_score, reference_score, @@ -237,17 +237,9 @@ participation_counts as ( primary_rewards as ( SELECT rpt.solver, - SUM(capped_payment) as payment_wei, - SUM(execution_cost) as exececution_cost_wei - FROM - reward_per_auction rpt - GROUP BY - solver -), -protocol_fees as ( - SELECT - solver, - SUM(protocol_fee) as protocol_fee_wei + SUM(capped_payment) as payment, + SUM(protocol_fee) as protocol_fee, + SUM(network_fee) as network_fee FROM reward_per_auction rpt GROUP BY @@ -256,14 +248,13 @@ protocol_fees as ( aggregate_results as ( SELECT concat('0x', encode(pc.solver, 'hex')) as solver, - coalesce(payment_wei, 0) as payment_eth, - coalesce(exececution_cost_wei, 0) as execution_cost_eth, + coalesce(payment, 0) as primary_reward_eth, num_participating_batches, - coalesce(protocol_fee_wei, 0) as protocol_fee_eth + coalesce(protocol_fee, 0) as protocol_fee_eth, + coalesce(network_fee, 0) as network_fee_eth FROM participation_counts pc LEFT OUTER JOIN primary_rewards pr ON pr.solver = pc.solver - LEFT OUTER JOIN protocol_fees pf ON pf.solver = pc.solver ) -- select * diff --git a/src/fetch/payouts.py b/src/fetch/payouts.py index 0921f2a9..ff24de3d 100644 --- a/src/fetch/payouts.py +++ b/src/fetch/payouts.py @@ -32,13 +32,13 @@ PAYMENT_COLUMNS = { "solver", - "payment_eth", - "execution_cost_eth", + "primary_reward_eth", + "primary_reward_cow", "secondary_reward_eth", - "reward_cow", "secondary_reward_cow", "quote_reward_cow", "protocol_fee_eth", + "network_fee_eth", } SLIPPAGE_COLUMNS = { "solver", @@ -49,10 +49,8 @@ COMPLETE_COLUMNS = PAYMENT_COLUMNS.union(SLIPPAGE_COLUMNS).union(REWARD_TARGET_COLUMNS) NUMERICAL_COLUMNS = [ - "payment_eth", - "execution_cost_eth", - "reward_eth", - "reward_cow", + "primary_reward_eth", + "primary_reward_cow", "secondary_reward_cow", "secondary_reward_eth", "quote_reward_cow", @@ -79,24 +77,22 @@ def __init__( # pylint: disable=too-many-arguments solver: Address, solver_name: str, reward_target: Address, - exec_cost: int, - payment_eth: int, + primary_reward_eth: int, secondary_reward_eth: int, slippage_eth: int, primary_reward_cow: int, secondary_reward_cow: int, quote_reward_cow: 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_cow" self.solver = solver self.solver_name = solver_name self.reward_target = reward_target - self.exec_cost = exec_cost - self.payment_eth = payment_eth self.slippage_eth = slippage_eth + self.primary_reward_eth = primary_reward_eth self.primary_reward_cow = primary_reward_cow self.secondary_reward_eth = secondary_reward_eth self.secondary_reward_cow = secondary_reward_cow @@ -120,10 +116,9 @@ def from_series(cls, frame: Series) -> RewardAndPenaltyDatum: solver=Address(solver), solver_name=frame["solver_name"], reward_target=Address(reward_target), - payment_eth=int(frame["payment_eth"]), slippage_eth=slippage, - primary_reward_cow=int(frame["reward_cow"]), - exec_cost=int(frame["execution_cost_eth"]), + primary_reward_eth=int(frame["primary_reward_eth"]), + primary_reward_cow=int(frame["primary_reward_cow"]), secondary_reward_eth=int(frame["secondary_reward_eth"]), secondary_reward_cow=int(frame["secondary_reward_cow"]), quote_reward_cow=int(frame["quote_reward_cow"]), @@ -131,7 +126,7 @@ def from_series(cls, frame: Series) -> RewardAndPenaltyDatum: 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.primary_reward_eth + self.secondary_reward_eth + self.slippage_eth def total_cow_reward(self) -> int: """Total outgoing COW token reward""" @@ -139,7 +134,7 @@ def total_cow_reward(self) -> int: def total_eth_reward(self) -> int: """Total outgoing ETH reward""" - return self.payment_eth - self.exec_cost + self.secondary_reward_eth + return self.primary_reward_eth + self.secondary_reward_eth def is_overdraft(self) -> bool: """ @@ -168,7 +163,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.slippage_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 = ( @@ -270,12 +265,11 @@ def extend_payment_df(pdf: DataFrame, converter: TokenConversion) -> DataFrame: This is evaluated in both ETH and COW (for different use cases). """ # Note that this can be negative! - pdf["reward_eth"] = pdf["payment_eth"] - pdf["execution_cost_eth"] - pdf["reward_cow"] = pdf["reward_eth"].apply(converter.eth_to_token) + pdf["primary_reward_cow"] = pdf["primary_reward_eth"].apply(converter.eth_to_token) secondary_allocation = max( min( - PERIOD_BUDGET_COW - pdf["reward_cow"].sum(), + PERIOD_BUDGET_COW - pdf["primary_reward_cow"].sum(), converter.eth_to_token(CONSISTENCY_REWARD_CAP_ETH), ), 0, @@ -383,6 +377,12 @@ def construct_payout_dataframe( merged_df = payment_df.merge(slippage_df, on=join_column, how="left").merge( reward_target_df, on=join_column, how="left" ) + + # 4. Add slippage from fees to slippage + merged_df["eth_slippage_wei"] = ( + merged_df["eth_slippage_wei"].fillna(0) + merged_df["network_fee_eth"] + ) + return merged_df @@ -417,7 +417,7 @@ def construct_payouts( # Sort by solver before breaking this data frame into Transfer objects. complete_payout_df = complete_payout_df.sort_values("solver") - performance_reward = complete_payout_df["reward_cow"].sum() + performance_reward = complete_payout_df["primary_reward_cow"].sum() participation_reward = complete_payout_df["secondary_reward_cow"].sum() quote_reward = complete_payout_df["quote_reward_cow"].sum() protocol_fee = complete_payout_df["protocol_fee_eth"].sum() diff --git a/tests/queries/batch_rewards_test_db.sql b/tests/queries/batch_rewards_test_db.sql index c61c745f..20f0937b 100644 --- a/tests/queries/batch_rewards_test_db.sql +++ b/tests/queries/batch_rewards_test_db.sql @@ -179,12 +179,12 @@ VALUES (1, '\x5222222222222222222222222222222222222222'::bytea), (56, '\x02'::bytea); INSERT INTO settlement_scores (auction_id, winning_score, reference_score, winner, block_deadline, simulation_block) -VALUES (1, 5000000000000000000, 4000000000000000000, '\x5111111111111111111111111111111111111111'::bytea, 10, 0), - (2, 6000000000000000000, 3000000000000000000, '\x5222222222222222222222222222222222222222'::bytea, 11, 1), +VALUES (1, 6000000000000000000, 4000000000000000000, '\x5111111111111111111111111111111111111111'::bytea, 10, 0), + (2, 12000000000000000000, 3000000000000000000, '\x5222222222222222222222222222222222222222'::bytea, 11, 1), (3, 21000000000000000000, 3000000000000000000, '\x5111111111111111111111111111111111111111'::bytea, 12, 2), (5, 5000000000000000000, 3000000000000000000, '\x5111111111111111111111111111111111111111'::bytea, 14, 4), -- jump in auction id (6, 10000000000000000000, 9000000000000000000, '\x5111111111111111111111111111111111111111'::bytea, 15, 5), -- settled too late - (7, 5000000000000000000, 0, '\x5111111111111111111111111111111111111111'::bytea, 30, 6), -- no competition + (7, 6000000000000000000, 0, '\x5111111111111111111111111111111111111111'::bytea, 30, 6), -- no competition (8, 5000000000000000000, 0, '\x5111111111111111111111111111111111111111'::bytea, 35, 7), -- no competition, failed transaction (9, 5000000000000000000, 1000000000000000000, '\x5111111111111111111111111111111111111111'::bytea, 36, 8), -- score larger than quality (10, 5000000000000000000, 4000000000000000000, '\x5333333333333333333333333333333333333333'::bytea, 37, 9), -- participant with net negative payment @@ -235,7 +235,7 @@ VALUES ('\x01'::bytea, '\x01'::bytea, '\x02'::bytea, 95000000, 94000000000000000 INSERT INTO trades (block_number, log_index, order_uid, sell_amount, buy_amount, fee_amount) VALUES (51, 0, '\x01'::bytea, 100000000, 95000000000000000000, 5000000), -(52, 0, '\x02'::bytea, 106000000, 100000000000000000000, 5000000), +(52, 0, '\x02'::bytea, 105000000, 100000000000000000000, 5000000), (53, 0, '\x03'::bytea, 100000000, 101000000000000000000, 0), (54, 0, '\x04'::bytea, 99000000, 100000000000000000000, 0), (55, 0, '\x05'::bytea, 100000000, 95000000000000000000, 0), diff --git a/tests/queries/test_batch_rewards.py b/tests/queries/test_batch_rewards.py index a55f01c3..5d6edfb8 100644 --- a/tests/queries/test_batch_rewards.py +++ b/tests/queries/test_batch_rewards.py @@ -28,22 +28,14 @@ def test_get_batch_rewards(self): "0x5333333333333333333333333333333333333333", "0x5444444444444444444444444444444444444444", ], - "payment_eth": [ - 9535064849462312.0, - 10.5e15, - 6600000000000000.00000, - 12450000000000000.00000, + "primary_reward_eth": [ + 2071357035553330.0, # 3 * 1e18 * 5e14 / 1e18 (surplus) + 571357035553330 (protocol fee) + 3519801980198020.0, + 6000000000000000.00000, + 12000000000000000.00000, -10000000000000000.00000, 0.00000, ], - "execution_cost_eth": [ - 7500000000000000.0, - 7500000000000000.0, - 800000000000000.00000, - 450000000000000.00000, - 0.00000, - 0.00000, - ], "num_participating_batches": [ 3, 3, @@ -60,6 +52,14 @@ def test_get_batch_rewards(self): 0.0, 0.0, ], + "network_fee_eth": [ + 7463707813908982.0, # almost 2500000000000000 + 3000000000000000 + 2500000000000000 - 5.748876684972541e14 + 6980198019801980.0, # 2500000000000000 + 4000000000000000 + 2500000000000000 - 2.0198019801980198e15 + 1050000000000000.0, + 400000000000000.0, + 0.0, + 0.0, + ], } ) self.assertIsNone(pandas.testing.assert_frame_equal(expected, batch_rewards)) diff --git a/tests/unit/test_payouts.py b/tests/unit/test_payouts.py index 3651685a..4673f910 100644 --- a/tests/unit/test_payouts.py +++ b/tests/unit/test_payouts.py @@ -50,18 +50,12 @@ def setUp(self) -> None: ) ) - self.eth_payments = [ + self.primary_reward_eth = [ 600000000000000.00000, - 10450000000000000.00000, + 12000000000000000.00000, -10000000000000000.00000, 0.00000, ] - self.execution_costs = [ - 800000000000000.00000, - 450000000000000.00000, - 0.00000, - 0.00000, - ] self.batch_participation = [ 7, 2, @@ -74,40 +68,45 @@ def setUp(self) -> None: 0.0, 0.0, ] + self.network_fee_eth = [ + 2000000000000000.0, + 4000000000000000.0, + 0.0, + 0.0, + ] # Mocking TokenConversion! self.mock_converter = TokenConversion( eth_to_token=lambda t: int(t * 1000), token_to_eth=lambda t: t // 1000 ) def test_extend_payment_df(self): - base_data_dict: dict = { + base_data_dict = { "solver": self.solvers, "num_quotes": self.num_quotes, - "payment_eth": self.eth_payments, - "execution_cost_eth": self.execution_costs, + "primary_reward_eth": self.primary_reward_eth, "num_participating_batches": self.batch_participation, "protocol_fee_eth": self.protocol_fee_eth, + "network_fee_eth": self.network_fee_eth, } base_payout_df = DataFrame(base_data_dict) result = extend_payment_df(base_payout_df, converter=self.mock_converter) expected_data_dict = { "solver": self.solvers, "num_quotes": self.num_quotes, - "payment_eth": self.eth_payments, - "execution_cost_eth": self.execution_costs, - "num_participating_batches": self.batch_participation, - "protocol_fee_eth": self.protocol_fee_eth, - "reward_eth": [ - -200000000000000.00000, - 10000000000000000.00000, + "primary_reward_eth": [ + 600000000000000.00000, + 12000000000000000.00000, -10000000000000000.00000, 0.00000, ], - "reward_cow": [ - -200000000000000000, - 10000000000000000000, - -10000000000000000000, - 0, + "num_participating_batches": self.batch_participation, + "protocol_fee_eth": self.protocol_fee_eth, + "network_fee_eth": self.network_fee_eth, + "primary_reward_cow": [ + 600000000000000000.0, + 12000000000000000000.0, + -10000000000000000000.0, + 0.0, ], "secondary_reward_cow": [ 1909090909090909000000.00000, @@ -149,11 +148,11 @@ def test_validate_df_columns(self): { "solver": [], "payment_eth": [], - "execution_cost_eth": [], "num_participating_batches": [], "protocol_fee_eth": [], - "reward_eth": [], - "reward_cow": [], + "network_fee_eth": [], + "primary_reward_eth": [], + "primary_reward_cow": [], "secondary_reward_cow": [], "secondary_reward_eth": [], "quote_reward_cow": [], @@ -198,10 +197,10 @@ def test_construct_payouts(self): { "solver": self.solvers, "num_quotes": self.num_quotes, - "payment_eth": self.eth_payments, - "execution_cost_eth": self.execution_costs, + "primary_reward_eth": self.primary_reward_eth, "num_participating_batches": self.batch_participation, "protocol_fee_eth": self.protocol_fee_eth, + "network_fee_eth": self.network_fee_eth, } ), converter=self.mock_converter, @@ -227,8 +226,7 @@ def test_construct_payouts(self): { "solver": self.solvers, "num_quotes": self.num_quotes, - "payment_eth": [600000000000000.0, 1.045e16, -1e16, 0.0], - "execution_cost_eth": [800000000000000.0, 450000000000000.0, 0.0, 0.0], + "primary_reward_eth": [600000000000000.0, 1.2e16, -1e16, 0.0], "num_participating_batches": [7, 2, 7, 6], "protocol_fee_eth": [ 1000000000000000.0, @@ -236,12 +234,17 @@ def test_construct_payouts(self): 0.0, 0.0, ], - "reward_eth": [-200000000000000.0, 1e16, -1e16, 0.0], - "reward_cow": [ - -200000000000000000, - 10000000000000000000, - -10000000000000000000, - 0, + "network_fee_eth": [ + 2000000000000000.0, + 4000000000000000.0, + 0.0, + 0.0, + ], + "primary_reward_cow": [ + 600000000000000000.0, + 12000000000000000000.0, + -10000000000000000000.0, + 0.0, ], "secondary_reward_cow": [ 1909090909090909000000.00000, @@ -262,7 +265,7 @@ def test_construct_payouts(self): 12000000000000000000.00000, ], "solver_name": ["S_1", "S_2", "S_3", None], - "eth_slippage_wei": [1.0, 0.0, -1.0, None], + "eth_slippage_wei": [2000000000000001.0, 4000000000000000.0, -1.0, 0.0], "reward_target": [ "0x0000000000000000000000000000000000000005", "0x0000000000000000000000000000000000000006", @@ -272,7 +275,7 @@ def test_construct_payouts(self): } ) - self.assertIsNone(pandas.testing.assert_frame_equal(result, expected)) + self.assertIsNone(pandas.testing.assert_frame_equal(expected, result)) def test_prepare_transfers(self): # Need Example of every possible scenario @@ -280,15 +283,14 @@ def test_prepare_transfers(self): { "solver": self.solvers, "num_quotes": self.num_quotes, - "payment_eth": [600000000000000.0, 1.045e16, -1e16, 0.0], - "execution_cost_eth": [800000000000000.0, 450000000000000.0, 0.0, 0.0], + "primary_reward_eth": [600000000000000.0, 1.2e16, -1e16, 0.0], "protocol_fee_eth": self.protocol_fee_eth, - "reward_eth": [-200000000000000.0, 1e16, -1e16, 0.0], - "reward_cow": [ - -200000000000000000, - 10000000000000000000, - -10000000000000000000, - 0, + "network_fee_eth": [100.0, 200.0, 300.0, 0.0], + "primary_reward_cow": [ + 600000000000000000.0, + 12000000000000000000.0, + -10000000000000000000.0, + 0.0, ], "secondary_reward_cow": [ 6.363636363636364e16, @@ -321,22 +323,21 @@ def test_prepare_transfers(self): period = AccountingPeriod("1985-03-10", 1) payout_transfers = prepare_transfers(full_payout_data, period) self.assertEqual( - payout_transfers.transfers, [ Transfer( token=None, recipient=Address(self.solvers[0]), - amount_wei=663636363636364, + amount_wei=1, ), Transfer( - token=None, - recipient=Address(self.solvers[1]), - amount_wei=450000000000000, + token=Token(COW_TOKEN_ADDRESS), + recipient=Address(self.reward_targets[0]), + amount_wei=663636363636363640, ), Transfer( token=Token(COW_TOKEN_ADDRESS), recipient=Address(self.reward_targets[1]), - amount_wei=10018181818181818180, + amount_wei=12018181818181818180, ), Transfer( token=Token(COW_TOKEN_ADDRESS), @@ -359,6 +360,7 @@ def test_prepare_transfers(self): amount_wei=3000000000000000, ), ], + payout_transfers.transfers, ) self.assertEqual( @@ -384,9 +386,8 @@ def setUp(self) -> None: def sample_record( self, - payment: int, - cost: int, - participation: int, + primary_reward: int, + secondary_reward: int, slippage: int, num_quotes: int, ): @@ -395,126 +396,143 @@ def sample_record( solver=self.solver, solver_name=self.solver_name, reward_target=self.reward_target, - payment_eth=payment, - exec_cost=cost, - primary_reward_cow=(payment - cost) * self.conversion_rate, - secondary_reward_eth=participation, - secondary_reward_cow=participation * self.conversion_rate, + primary_reward_eth=primary_reward, + primary_reward_cow=primary_reward * self.conversion_rate, + secondary_reward_eth=secondary_reward, + secondary_reward_cow=secondary_reward * self.conversion_rate, slippage_eth=slippage, quote_reward_cow=QUOTE_REWARD_COW * num_quotes, ) def test_invalid_input(self): + """Test that negative and secondary and quote rewards throw an error.""" + + # invalid secondary reward with self.assertRaises(AssertionError): - self.sample_record(0, -1, 0, 0, 0) + self.sample_record(0, -1, 0, 0) + # invalid quote reward with self.assertRaises(AssertionError): - self.sample_record(0, 0, -1, 0, 0) + self.sample_record(0, 0, 0, -1) def test_reward_datum_0_0_0_0(self): - test_datum = self.sample_record(0, 0, 0, 0, 0) + """Without data there is no payout and no overdraft.""" + test_datum = self.sample_record(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): - cost = 1 - test_datum = self.sample_record(1, cost, 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_pm1_0_0_0(self): + """Primary reward only.""" - def test_reward_datum_3_2_0_minus1(self): - payment, cost, participation, slippage = 3, 2, 0, -1 - test_datum = self.sample_record(payment, cost, participation, slippage, 0) + # positive reward is paid in COW + primary_reward = 1 + test_datum = self.sample_record(primary_reward, 0, 0, 0) self.assertFalse(test_datum.is_overdraft()) self.assertEqual( test_datum.as_payouts(), [ - Transfer( - token=None, - recipient=self.solver, - amount_wei=cost + slippage, - ), Transfer( token=self.cow_token, recipient=self.reward_target, - amount_wei=(payment - cost) * self.conversion_rate, - ), + amount_wei=primary_reward * 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) + # negative reward gives overdraft + primary_reward = -1 + test_datum = self.sample_record(primary_reward, 0, 0, 0) + self.assertTrue(test_datum.is_overdraft()) + self.assertEqual(test_datum.as_payouts(), []) + + def test_reward_datum_0_0_pm1_0(self): + """Slippag only.""" + + # positive slippage is paid in ETH + slippage = 1 + test_datum = self.sample_record(0, 0, slippage, 0) self.assertFalse(test_datum.is_overdraft()) self.assertEqual( test_datum.as_payouts(), - [], + [Transfer(token=None, recipient=self.solver, amount_wei=slippage)], ) - def test_reward_datum_cost_exceeds_payment_non_degenerate(self): - # Payment + Slippage combined do not exceed costs so only that is returned - - triplets = [(1, 0, 1), (2, 0, -1), (1, 1, 1)] - 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) - self.assertFalse(test_datum.is_overdraft()) - self.assertLess(test_datum.total_outgoing_eth(), cost) - self.assertEqual( - test_datum.as_payouts(), - [ - Transfer( - token=None, - recipient=self.solver, - amount_wei=test_datum.total_outgoing_eth(), - ) - ], - ) - - def test_reward_datum_overdraft(self): - # Any time when payment + participation + slippage < 0 - triplets = [ - (-1, 0, 0), - (0, 0, -1), - ] - 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) - self.assertTrue(rec.is_overdraft()) - - def test_reward_datum_1_1_1_1(self): - payment, cost, participation, slippage = 1, 1, 1, 1 - test_datum = self.sample_record(payment, cost, participation, slippage, 0) + # negative slippage gives overdraft + slippage = -1 + test_datum = self.sample_record(0, 0, slippage, 0) + self.assertTrue(test_datum.is_overdraft()) + self.assertEqual(test_datum.as_payouts(), []) + def test_reward_datum_0_0_0_1(self): + """Quote rewards only.""" + num_quotes = 1 + test_datum = self.sample_record(0, 0, 0, num_quotes) self.assertFalse(test_datum.is_overdraft()) self.assertEqual( - test_datum.total_cow_reward(), participation * self.conversion_rate + test_datum.as_payouts(), + [ + Transfer( + token=self.cow_token, + recipient=self.reward_target, + amount_wei=6000000000000000000 * num_quotes, + ) + ], ) + + def test_reward_datum_4_2_1_0(self): + """COW payment for rewards, ETH payment for slippage.""" + primary_reward, secondary_reward, slippage, num_quotes = 4, 2, 1, 0 + test_datum = self.sample_record(primary_reward, secondary_reward, slippage, 0) + self.assertFalse(test_datum.is_overdraft()) self.assertEqual( test_datum.as_payouts(), [ Transfer( token=None, recipient=self.solver, - amount_wei=cost + slippage, + amount_wei=slippage, ), Transfer( token=self.cow_token, recipient=self.reward_target, - amount_wei=test_datum.total_cow_reward(), + amount_wei=(primary_reward + secondary_reward) + * self.conversion_rate, ), ], ) - def test_payout_negative_payments(self): - payment, cost, participation, slippage = -1, 1, 1, 1 - test_datum = self.sample_record(payment, cost, participation, slippage, 0) + def test_reward_datum_slippage_reduces_reward(self): + """Negative slippage reduces COW reward.""" + primary_reward, secondary_reward, slippage, num_quotes = 4, 2, -1, 0 + test_datum = self.sample_record(primary_reward, secondary_reward, slippage, 0) + self.assertFalse(test_datum.is_overdraft()) + self.assertEqual( + test_datum.as_payouts(), + [ + Transfer( + token=self.cow_token, + recipient=self.reward_target, + amount_wei=(primary_reward + secondary_reward + slippage) + * self.conversion_rate, + ), + ], + ) + + def test_reward_datum_slippage_exceeds_reward(self): + """Negative slippage leads to overtraft.""" + primary_reward, participation, slippage = 1, 2, -4 + test_datum = self.sample_record(primary_reward, participation, slippage, 0) + self.assertTrue(test_datum.is_overdraft()) + self.assertEqual(test_datum.as_payouts(), []) + + def test_reward_datum_reward_reduces_slippage(self): + """Negative reward reduces ETH slippage payment.""" + primary_reward, secondary_reward, slippage = -2, 1, 3 + test_datum = self.sample_record(primary_reward, secondary_reward, slippage, 0) + self.assertEqual( + test_datum.total_outgoing_eth(), + primary_reward + secondary_reward + slippage, + ) self.assertEqual( test_datum.as_payouts(), [