From 2bda2b1ea0780f8cb66a7be34c41112f0019c9df Mon Sep 17 00:00:00 2001 From: Felix Henneke Date: Mon, 5 Feb 2024 11:51:42 +0200 Subject: [PATCH] Add protocol fee (#333) This PR adds changes to solver rewards due to the introduction of protocol fees. The implementation in this PR is intended to make solvers oblivious to protocol fees. This means that the quality of a solution as defined in CIP-20 in our accounting is needs to be equal to what the solver computed without knowledge of protocol fees. CIP-20 specifies that `quality = surplus + fee` in ETH. With protocol fees, the surplus is reduced by some amount, the protocol fee. This means that the quality is now something like `quality = surplus + protocol_fee + fee` in ETH. Here, `surplus` refers to the surplus users receive, `protocol_fee` is the fee the protocol charges, and `fee` is a fee set by solvers or the protocol for covering network costs. All token amounts are converted to ETH using native prices of the auction. One problem is, that fees set by solvers for covering network costs are not directly observable for the autopilot. For those orders, the autopilot only computes a total fee in the sell token by comparing what a user did send to the protocol to what they would have sent to receive the same amount of buy tokens if they had traded at uniform clearing prices. Let us call this term `total_fee` and it is in the sell token. Let us further use the term `total_surplus` for the surplus a solver perceives who is oblivious to protocol fees. The inconsistency corrected in this PR comes from the fact that the solver perceives a quality of `total_surplus * surplus_native_price + fee * sell_native_price`. This is different from `surplus * surplus_native_price + total_fee * sell_native_price`. Instead, the reward script needs to compute `surplus * surplus_native_price + protocol_fee * protocol_fee_native_price + fee * sell_native_price`. The fee for network costs needs to be reconstructed from observations. The driver currently computes the total fee in the sell token as `total_fee = fee + protocol_fee * protocol_fee_clearing_price / sell_clearing_price`. Thus the network fee is `total_fee - protocol_fee * protocol_fee_clearing_price / sell_clearing_price`. Since the ratio of clearing prices is equal to the ratio of traded amount _if there were no fee at all,_ `protocol_fee_clearing_price / sell_clearing_price = (amount_sent - total_fee) / amount_received`, the correction to the total fee to compute the actual fee for network costs becomes `network_fee_correction = (amount_sent - total_fee) / amount_received * protocol_fee`. The implementation in this PR depends on the current implementation of protocol fees in the driver. With drivers ran by solvers, this is not possible anymore. By then we should be ready to rank by `surplus + protocol_fee`. This value is easy to compute for the driver and no correction of network fees is required anymore. --- queries/orderbook/batch_rewards.sql | 152 ++++++++++++++++++++-- src/fetch/payouts.py | 16 ++- tests/queries/batch_rewards_test_db.sql | 161 +++++++++++++++++++++++- tests/queries/test_batch_rewards.py | 16 +++ tests/unit/test_payouts.py | 23 ++++ 5 files changed, 351 insertions(+), 17 deletions(-) diff --git a/queries/orderbook/batch_rewards.sql b/queries/orderbook/batch_rewards.sql index 31e6dc25..664c0f01 100644 --- a/queries/orderbook/batch_rewards.sql +++ b/queries/orderbook/batch_rewards.sql @@ -28,13 +28,130 @@ WITH observed_settlements AS (SELECT WHERE block_deadline >= {{start_block}} AND block_deadline <= {{end_block}} GROUP BY ss.auction_id), +-- protocol fees: +order_surplus AS ( + SELECT + ss.winner as solver, + at.auction_id, + s.tx_hash, + t.order_uid, + o.sell_token, + o.buy_token, + t.sell_amount, -- the total amount the user sends + t.buy_amount, -- the total amount the user receives + oe.surplus_fee as observed_fee, -- the total discrepancy between what the user sends and what they would have send if they traded at clearing price + o.kind, + CASE + WHEN o.kind = 'sell' + THEN t.buy_amount - t.sell_amount * o.buy_amount / (o.sell_amount + o.fee_amount) + WHEN o.kind = 'buy' + THEN t.buy_amount * (o.sell_amount + o.fee_amount) / o.buy_amount - t.sell_amount + END AS surplus, + CASE + WHEN o.kind = 'sell' + THEN o.buy_token + WHEN o.kind = 'buy' + THEN o.sell_token + END AS surplus_token + FROM settlements s -- links block_number and log_index to tx_from and tx_nonce + JOIN auction_transaction at -- links auction_id to tx_from and tx_nonce + ON s.tx_from = at.tx_from AND s.tx_nonce = at.tx_nonce + JOIN settlement_scores ss -- contains block_deadline + ON at.auction_id = ss.auction_id + JOIN trades t -- contains traded amounts + ON s.block_number = t.block_number -- log_index cannot be checked, does not work correctly with multiple auctions on the same block + JOIN orders o -- contains tokens and limit amounts + ON t.order_uid = o.uid + JOIN order_execution oe -- contains surplus fee + ON t.order_uid = oe.order_uid AND at.auction_id = oe.auction_id + WHERE ss.block_deadline >= {{start_block}} + AND ss.block_deadline <= {{end_block}} +) +,order_protocol_fee AS ( + SELECT + os.auction_id, + os.solver, + os.tx_hash, + os.sell_amount, + os.buy_amount, + os.sell_token, + os.observed_fee, + os.surplus, + os.surplus_token, + CASE + WHEN fp.kind = 'surplus' + THEN + CASE + WHEN os.kind = 'sell' + THEN + -- We assume that the case surplus_factor != 1 always. In + -- that case reconstructing the protocol fee would be + -- impossible anyways. This query will return a division by + -- zero error in that case. + LEAST( + fp.max_volume_factor * os.sell_amount * os.buy_amount / (os.sell_amount - os.observed_fee), -- at most charge a fraction of volume + fp.surplus_factor / (1 - fp.surplus_factor) * surplus -- charge a fraction of surplus + ) + WHEN os.kind = 'buy' + THEN + LEAST( + fp.max_volume_factor / (1 + fp.max_volume_factor) * os.sell_amount, -- at most charge a fraction of volume + 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 + 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 + FROM order_surplus os + JOIN fee_policies fp -- contains protocol fee policy + ON os.auction_id = fp.auction_id AND os.order_uid = fp.order_uid +) +,order_protocol_fee_prices AS ( + SELECT + opf.solver, + opf.tx_hash, + opf.surplus, + opf.protocol_fee, + CASE + WHEN opf.sell_token != opf.protocol_fee_token + THEN (opf.sell_amount - opf.observed_fee) / opf.buy_amount * opf.protocol_fee + ELSE opf.protocol_fee + END AS network_fee_correction, + opf.sell_token as network_fee_token, + ap_surplus.price / pow(10, 18) as surplus_token_price, + ap_protocol.price / pow(10, 18) as protocol_fee_token_price, + ap_sell.price / pow(10, 18) as network_fee_token_price + FROM order_protocol_fee opf + JOIN auction_prices ap_sell -- contains price: sell token + ON opf.auction_id = ap_sell.auction_id AND opf.sell_token = ap_sell.token + JOIN auction_prices ap_surplus -- contains price: surplus token + ON opf.auction_id = ap_surplus.auction_id AND opf.surplus_token = ap_surplus.token + JOIN auction_prices ap_protocol -- contains price: protocol fee token + ON opf.auction_id = ap_protocol.auction_id AND opf.protocol_fee_token = ap_protocol.token +), +batch_protocol_fees AS ( + SELECT + solver, + tx_hash, + -- sum(surplus * surplus_token_price) as surplus, + sum(protocol_fee * protocol_fee_token_price) as protocol_fee, + sum(network_fee_correction * network_fee_token_price) as network_fee_correction + FROM order_protocol_fee_prices + group by solver, tx_hash +), reward_data AS (SELECT -- observations - tx_hash, + os.tx_hash, ss.auction_id, -- TODO - Assuming that `solver == winner` when both not null -- We will need to monitor that `solver == winner`! - coalesce(solver, winner) as solver, + coalesce(os.solver, winner) as solver, block_number as settlement_block, block_deadline, case @@ -50,7 +167,10 @@ WITH observed_settlements AS (SELECT winning_score, reference_score, -- auction_participation - participating_solvers + participating_solvers, + -- protocol_fees + coalesce(cast(protocol_fee as numeric(78, 0)), 0) as protocol_fee, + coalesce(cast(network_fee_correction as numeric(78, 0)), 0) as network_fee_correction FROM settlement_scores ss -- If there are reported scores, -- there will always be a record of auction participants @@ -58,7 +178,9 @@ WITH observed_settlements AS (SELECT ON ss.auction_id = ap.auction_id -- outer joins made in order to capture non-existent settlements. LEFT OUTER JOIN observed_settlements os - ON os.auction_id = ss.auction_id), + ON os.auction_id = ss.auction_id + LEFT OUTER JOIN batch_protocol_fees bpf + ON bpf.tx_hash = os.tx_hash), reward_per_auction as (SELECT tx_hash, auction_id, settlement_block, @@ -66,10 +188,11 @@ WITH observed_settlements AS (SELECT solver, execution_cost, surplus, - fee, - surplus + fee - reference_score as uncapped_reward_eth, - -- Uncapped Reward = CLAMP_[-E, E + exec_cost](uncapped_reward_eth) - LEAST(GREATEST(-{{EPSILON}}, surplus + fee - reference_score), + protocol_fee, + fee - network_fee_correction as network_fee, + surplus + protocol_fee + fee - network_fee_correction - reference_score as uncapped_reward_eth, + -- capped Reward = CLAMP_[-E, E + exec_cost](uncapped_reward_eth) + LEAST(GREATEST(-{{EPSILON}}, surplus + coalesce(protocol_fee - network_fee_correction, 0) + fee - reference_score), {{EPSILON}} + execution_cost) as capped_payment, winning_score, reference_score, @@ -85,14 +208,21 @@ WITH observed_settlements AS (SELECT 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 + FROM reward_per_auction rpt + GROUP BY solver), 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, - num_participating_batches + num_participating_batches, + coalesce(protocol_fee_wei, 0) as protocol_fee_eth FROM participation_counts pc LEFT OUTER JOIN primary_rewards pr - ON pr.solver = pc.solver) - + ON pr.solver = pc.solver + LEFT OUTER JOIN protocol_fees pf + ON pf.solver = pc.solver) +-- select * from aggregate_results order by solver; diff --git a/src/fetch/payouts.py b/src/fetch/payouts.py index 45998382..4724f0ee 100644 --- a/src/fetch/payouts.py +++ b/src/fetch/payouts.py @@ -25,6 +25,8 @@ PERIOD_BUDGET_COW = 306646 * 10**18 QUOTE_REWARD = 9 * 10**18 +PROTOCOL_FEE_SAFE = Address("0xB64963f95215FDe6510657e719bd832BB8bb941B") + PAYMENT_COLUMNS = { "solver", "payment_eth", @@ -33,6 +35,7 @@ "reward_cow", "secondary_reward_cow", "quote_reward_cow", + "protocol_fee_eth", } SLIPPAGE_COLUMNS = { "solver", @@ -50,6 +53,7 @@ "secondary_reward_cow", "secondary_reward_eth", "quote_reward_cow", + "protocol_fee_eth", ] @@ -308,6 +312,14 @@ def prepare_transfers(payout_df: DataFrame, period: AccountingPeriod) -> PeriodP overdrafts.append(overdraft) transfers += payout_datum.as_payouts() + transfers.append( + Transfer( + token=None, + recipient=PROTOCOL_FEE_SAFE, + amount_wei=int(payout_df.protocol_fee_eth.sum()), + ) + ) + return PeriodPayouts(overdrafts, transfers) @@ -396,10 +408,12 @@ def construct_payouts( performance_reward = complete_payout_df["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() dune.log_saver.print( f"Performance Reward: {performance_reward / 10 ** 18:.4f}\n" f"Participation Reward: {participation_reward / 10 ** 18:.4f}\n" - f"Quote Reward: {quote_reward / 10 ** 18:.4f}\n", + f"Quote Reward: {quote_reward / 10 ** 18:.4f}\n" + f"Protocol Fees: {protocol_fee / 10 ** 18:.4f}\n", category=Category.TOTALS, ) payouts = prepare_transfers(complete_payout_df, dune.period) diff --git a/tests/queries/batch_rewards_test_db.sql b/tests/queries/batch_rewards_test_db.sql index c984e1d4..05f92e5c 100644 --- a/tests/queries/batch_rewards_test_db.sql +++ b/tests/queries/batch_rewards_test_db.sql @@ -3,6 +3,14 @@ DROP TABLE IF EXISTS auction_transaction; DROP TABLE IF EXISTS auction_participants; DROP TABLE IF EXISTS settlement_scores; DROP TABLE IF EXISTS settlement_observations; +DROP TABLE IF EXISTS auction_prices; +DROP TABLE IF EXISTS orders; +DROP TYPE IF EXISTS OrderKind; +DROP TYPE IF EXISTS OrderClass; +DROP TABLE IF EXISTS trades; +DROP TABLE IF EXISTS order_execution; +DROP TABLE IF EXISTS fee_policies; +DROP TYPE IF EXISTS PolicyKind; CREATE TABLE IF NOT EXISTS settlements ( @@ -55,12 +63,83 @@ CREATE TABLE IF NOT EXISTS settlement_observations PRIMARY KEY (block_number, log_index) ); +CREATE TABLE IF NOT EXISTS auction_prices +( + auction_id bigint NOT NULL, + token bytea NOT NULL, + price numeric(78, 0) NOT NULL, + + PRIMARY KEY (auction_id, token) +); + +-- orders table +CREATE TYPE OrderKind AS ENUM ('buy', 'sell'); +CREATE TYPE OrderClass AS ENUM ('market', 'limit'); + +CREATE TABLE orders ( + uid bytea PRIMARY KEY, + sell_token bytea NOT NULL, + buy_token bytea NOT NULL, + sell_amount numeric(78,0) NOT NULL, + buy_amount numeric(78,0) NOT NULL, + fee_amount numeric(78,0) NOT NULL, + kind OrderKind NOT NULL, + partially_fillable boolean NOT NULL, + full_fee_amount numeric(78,0) NOT NULL, + class OrderClass NOT NULL +); + +CREATE TABLE IF NOT EXISTS trades +( + block_number bigint NOT NULL, + log_index bigint NOT NULL, + order_uid bytea NOT NULL, + sell_amount numeric(78, 0) NOT NULL, + buy_amount numeric(78, 0) NOT NULL, + fee_amount numeric(78, 0) NOT NULL, + + PRIMARY KEY (block_number, log_index) +); + +CREATE TABLE IF NOT EXISTS order_execution +( + order_uid bytea NOT NULL, + auction_id bigint NOT NULL, + reward double precision NOT NULL, + surplus_fee numeric(78, 0) NOT NULL, + solver_fee numeric(78, 0), + + PRIMARY KEY (order_uid, auction_id) +); + +CREATE TYPE PolicyKind AS ENUM ('surplus', 'volume'); + +CREATE TABLE fee_policies ( + auction_id bigint NOT NULL, + order_uid bytea NOT NULL, + -- The order in which the fee policies are inserted and applied. + application_order SERIAL NOT NULL, + -- The type of the fee policy. + kind PolicyKind NOT NULL, + -- The fee should be taken as a percentage of the price improvement. The value is between 0 and 1. + surplus_factor double precision, + -- Cap the fee at a certain percentage of the order volume. The value is between 0 and 1. + max_volume_factor double precision, + -- The fee should be taken as a percentage of the order volume. The value is between 0 and 1. + volume_factor double precision, + PRIMARY KEY (auction_id, order_uid, application_order) +); + TRUNCATE settlements; TRUNCATE auction_transaction; TRUNCATE auction_participants; TRUNCATE settlement_scores; TRUNCATE settlement_observations; +TRUNCATE auction_prices; +TRUNCATE orders; +TRUNCATE trades; +TRUNCATE fee_policies; INSERT INTO settlements (block_number, log_index, solver, tx_hash, tx_from, tx_nonce) @@ -70,7 +149,13 @@ VALUES (1, 10, '\x5111111111111111111111111111111111111111'::bytea, '\x7111'::by -- would the following entry be in the data base? (submitted too late) -- YES (20, 10, '\x5111111111111111111111111111111111111111'::bytea, '\x7444'::bytea, '\x5111111111111111111111111111111111111111'::bytea, 3), (25, 10, '\x5111111111111111111111111111111111111111'::bytea, '\x7555'::bytea, '\x5111111111111111111111111111111111111111'::bytea, 4), - (26, 10, '\x5111111111111111111111111111111111111111'::bytea, '\x7666'::bytea, '\x5111111111111111111111111111111111111111'::bytea, 6); + (26, 10, '\x5111111111111111111111111111111111111111'::bytea, '\x7666'::bytea, '\x5111111111111111111111111111111111111111'::bytea, 6), + (51, 10, '\x01'::bytea, '\x01'::bytea, '\x01'::bytea, 1), + (52, 10, '\x02'::bytea, '\x02'::bytea, '\x02'::bytea, 1), + (53, 10, '\x01'::bytea, '\x03'::bytea, '\x01'::bytea, 2), + (54, 10, '\x02'::bytea, '\x04'::bytea, '\x02'::bytea, 2), + (55, 10, '\x01'::bytea, '\x05'::bytea, '\x01'::bytea, 3), + (56, 10, '\x02'::bytea, '\x06'::bytea, '\x02'::bytea, 3); INSERT INTO auction_transaction (auction_id, tx_from, tx_nonce) VALUES (1, '\x5111111111111111111111111111111111111111'::bytea, 1), @@ -80,7 +165,13 @@ VALUES (1, '\x5111111111111111111111111111111111111111'::bytea, 1), (7, '\x5111111111111111111111111111111111111111'::bytea, 4), (8, '\x5111111111111111111111111111111111111111'::bytea, 5), -- would that entry be in the data base? (failed transaction) (9, '\x5111111111111111111111111111111111111111'::bytea, 6), - (10, '\x5333333333333333333333333333333333333333'::bytea, 1); -- would that entry be in the data base? (failed transaction) + (10, '\x5333333333333333333333333333333333333333'::bytea, 1), -- would that entry be in the data base? (failed transaction) + (51, '\x01'::bytea, 1), + (52, '\x02'::bytea, 1), + (53, '\x01'::bytea, 2), + (54, '\x02'::bytea, 2), + (55, '\x01'::bytea, 3), + (56, '\x02'::bytea, 3); INSERT INTO auction_participants (auction_id, participant) VALUES (1, '\x5222222222222222222222222222222222222222'::bytea), @@ -104,7 +195,13 @@ VALUES (1, '\x5222222222222222222222222222222222222222'::bytea), (9, '\x5333333333333333333333333333333333333333'::bytea), (9, '\x5111111111111111111111111111111111111111'::bytea), (10, '\x5444444444444444444444444444444444444444'::bytea), - (10, '\x5333333333333333333333333333333333333333'::bytea); + (10, '\x5333333333333333333333333333333333333333'::bytea), + (51, '\x01'::bytea), + (52, '\x02'::bytea), + (53, '\x01'::bytea), + (54, '\x02'::bytea), + (55, '\x01'::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), @@ -115,7 +212,13 @@ VALUES (1, 5000000000000000000, 4000000000000000000, '\x511111111111111111111111 (7, 5000000000000000000, 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 + (10, 5000000000000000000, 4000000000000000000, '\x5333333333333333333333333333333333333333'::bytea, 37, 9), -- participant with net negative payment + (51, 500000000000000, 0, '\x01'::bytea, 60, 50), + (52, 500000000000000, 0, '\x02'::bytea, 61, 51), + (53, 1000000000000000, 0, '\x01'::bytea, 62, 52), + (54, 2000000000000000, 0, '\x02'::bytea, 62, 52), + (55, 500000000000000, 0, '\x01'::bytea, 64, 54), + (56, 500000000000000, 0, '\x02'::bytea, 65, 55); -- score probably wrong, does not take protocol fee into account INSERT INTO settlement_observations (block_number, log_index, gas_used, effective_gas_price, surplus, fee) VALUES (1, 10, 100000, 2000000000, 6000000000000000000, 200000000000000), @@ -125,4 +228,52 @@ VALUES (1, 10, 100000, 2000000000, 6000000000000000000, 200000000000000), -- I would prefer to use the real numbers. What is backend gonna do. (20, 10, 100000, 2000000000, 0, 0), -- would that entry be in the data base? (submitted too late) (25, 10, 100000, 2000000000, 6000000000000000000, 200000000000000), - (26, 10, 100000, 2000000000, 0, 400000000000000); + (26, 10, 100000, 2000000000, 0, 400000000000000), + (51, 10, 100000, 25000000000, 500000000000000, 2500000000000000), + (52, 10, 100000, 25000000000, 500000000000000, 2500000000000000), + (53, 10, 100000, 25000000000, 500000000000000, 3000000000000000), + (54, 10, 100000, 25000000000, 500000000000000, 4000000000000000), + (55, 10, 100000, 25000000000, 500000000000000, 2500000000000000), + (56, 10, 100000, 25000000000, 500000000000000, 2500000000000000); + +INSERT INTO auction_prices (auction_id, token, price) +VALUES (51, '\x01', 500000000000000000000000000), +(51, '\x02', 500000000000000), +(52, '\x01', 500000000000000000000000000), +(52, '\x02', 500000000000000), +(53, '\x01', 500000000000000000000000000), +(53, '\x02', 500000000000000), +(54, '\x01', 500000000000000000000000000), +(54, '\x02', 500000000000000), +(55, '\x01', 500000000000000000000000000), +(55, '\x02', 500000000000000), +(56, '\x01', 500000000000000000000000000), +(56, '\x02', 500000000000000); + +INSERT INTO orders (uid, sell_token, buy_token, sell_amount, buy_amount, fee_amount, kind, partially_fillable, full_fee_amount, class) +VALUES ('\x01'::bytea, '\x01'::bytea, '\x02'::bytea, 95000000, 94000000000000000000, 5000000, 'sell', 'f', 5000000, 'market'), -- sell market order +('\x02'::bytea, '\x01'::bytea, '\x02'::bytea, 101000000, 100000000000000000000, 5000000, 'buy', 'f', 5000000, 'market'), -- buy market order +('\x03'::bytea, '\x01'::bytea, '\x02'::bytea, 100000000, 100000000000000000000, 0, 'sell', 't', 0, 'limit'), -- partially fillable sell limit order +('\x04'::bytea, '\x01'::bytea, '\x02'::bytea, 100000000, 100000000000000000000, 0, 'buy', 't', 0, 'limit'), -- partially fillable buy limit order +('\x05'::bytea, '\x01'::bytea, '\x02'::bytea, 100000000, 94000000000000000000, 0, 'sell', 'f', 0, 'limit'), -- in market sell limit order +('\x06'::bytea, '\x01'::bytea, '\x02'::bytea, 106000000, 100000000000000000000, 0, 'buy', 'f', 0, 'limit'); -- in market buy limit order + +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), +(53, 0, '\x03'::bytea, 100000000, 101000000000000000000, 0), +(54, 0, '\x04'::bytea, 99000000, 100000000000000000000, 0), +(55, 0, '\x05'::bytea, 100000000, 95000000000000000000, 0), +(56, 0, '\x06'::bytea, 105000000, 100000000000000000000, 0); + +INSERT INTO order_execution (order_uid, auction_id, reward, surplus_fee, solver_fee) +VALUES ('\x03'::bytea, 53, 0, 5931372, NULL), +('\x04'::bytea, 54, 0, 6000000, NULL), +('\x05'::bytea, 55, 0, 6000000, NULL), +('\x06'::bytea, 56, 0, 6000000, NULL); + +INSERT INTO fee_policies (auction_id, order_uid, application_order, kind, surplus_factor, max_volume_factor, volume_factor) +VALUES (53, '\x03'::bytea, 3, 'surplus', 0.5, 0.02, NULL), +(54, '\x04'::bytea, 4, 'surplus', 0.75, 0.1, NULL), +(55, '\x05'::bytea, 5, 'volume', NULL, NULL, 0.0015), +(56, '\x06'::bytea, 6, 'surplus', 0.9, 0.01, NULL); diff --git a/tests/queries/test_batch_rewards.py b/tests/queries/test_batch_rewards.py index fc6f3fb2..f0605711 100644 --- a/tests/queries/test_batch_rewards.py +++ b/tests/queries/test_batch_rewards.py @@ -21,29 +21,45 @@ def test_get_batch_rewards(self): expected = DataFrame( { "solver": [ + "0x01", + "0x02", "0x5111111111111111111111111111111111111111", "0x5222222222222222222222222222222222222222", "0x5333333333333333333333333333333333333333", "0x5444444444444444444444444444444444444444", ], "payment_eth": [ + 9.534313722772278e15, + 10.5e15, 600000000000000.00000, 10450000000000000.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, 7, 2, 7, 6, ], + "protocol_fee_eth": [ + 5.748876684972541e14, # 0.5 / (1 - 0.5) * 1e18 * 5e14 / 1e18 + 0.0015 / (1 + 0.0015) * 1e8 * 5e26 / 1e18 + 2.0198019801980198e15, # 0.75 / (1 - 0.75) * 1e6 * 5e26 / 1e18 + 0.01 / (1 + 0.01) * 105e6 * 5e26 / 1e18 + 0.0, + 0.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 fc4ff8e5..682187f8 100644 --- a/tests/unit/test_payouts.py +++ b/tests/unit/test_payouts.py @@ -14,6 +14,7 @@ prepare_transfers, RewardAndPenaltyDatum, QUOTE_REWARD, + PROTOCOL_FEE_SAFE, ) from src.models.accounting_period import AccountingPeriod from src.models.overdraft import Overdraft @@ -67,6 +68,12 @@ def setUp(self) -> None: 7, 6, ] + self.protocol_fee_eth = [ + 1000000000000000.0, + 2000000000000000.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 @@ -79,6 +86,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_fee_eth, } base_payout_df = DataFrame(base_data_dict) result = extend_payment_df(base_payout_df, converter=self.mock_converter) @@ -88,6 +96,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_fee_eth, "reward_eth": [ -200000000000000.00000, 10000000000000000.00000, @@ -142,6 +151,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 +201,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_fee_eth, } ), converter=self.mock_converter, @@ -219,6 +230,12 @@ 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": [ + 1000000000000000.0, + 2000000000000000.0, + 0.0, + 0.0, + ], "reward_eth": [-200000000000000.0, 1e16, -1e16, 0.0], "reward_cow": [ -200000000000000000, @@ -265,6 +282,7 @@ def test_prepare_transfers(self): "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], + "protocol_fee_eth": self.protocol_fee_eth, "reward_eth": [-200000000000000.0, 1e16, -1e16, 0.0], "reward_cow": [ -200000000000000000, @@ -335,6 +353,11 @@ def test_prepare_transfers(self): recipient=Address(self.reward_targets[3]), amount_wei=54545454545454544, ), + Transfer( + token=None, + recipient=PROTOCOL_FEE_SAFE, + amount_wei=3000000000000000, + ), ], )