From 0c501ceacbb92c9a559430ec6fe810d69ed15195 Mon Sep 17 00:00:00 2001 From: Felix Henneke Date: Fri, 29 Mar 2024 14:31:28 +0100 Subject: [PATCH] Add price improvement protocol fee (#353) This PR implements a protocol fee on price improvement as introduced in ... A new case was added to the batch rewards SQL query for computing a price improvement fee. Price improvement is computed from a quote (depending on order kind). If price improvement is positive and smaller than surplus, the protocol fee is a fraction of price improvement. Otherwise it is zero or a fraction of surplus, respectively. Four cases are tested - normal sell order - normal buy order - sell order with bad quote, fee capped at fraction of volume - sell order with negative price improvement, thus zero profotol fee This PR should be merged and tagged before the end of this accounting period. The change to the batch rewards query should be mirrored on dune-sync. --- queries/orderbook/barn_batch_rewards.sql | 28 ++++++++ queries/orderbook/prod_batch_rewards.sql | 28 ++++++++ tests/queries/batch_rewards_test_db.sql | 90 ++++++++++++++++++++---- tests/queries/test_batch_rewards.py | 5 ++ 4 files changed, 137 insertions(+), 14 deletions(-) diff --git a/queries/orderbook/barn_batch_rewards.sql b/queries/orderbook/barn_batch_rewards.sql index 61be7a3a..760a1d3e 100644 --- a/queries/orderbook/barn_batch_rewards.sql +++ b/queries/orderbook/barn_batch_rewards.sql @@ -48,6 +48,10 @@ order_surplus AS ( 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 t.buy_amount - t.sell_amount * (oq.buy_amount - oq.buy_amount / oq.sell_amount * oq.gas_amount * oq.gas_price / oq.sell_token_price) / oq.sell_amount + WHEN o.kind = 'buy' THEN t.buy_amount * (oq.sell_amount + oq.gas_amount * oq.gas_price / oq.sell_token_price) / oq.buy_amount - t.sell_amount + END AS price_improvement, CASE WHEN o.kind = 'sell' THEN o.buy_token WHEN o.kind = 'buy' THEN o.sell_token @@ -63,6 +67,8 @@ order_surplus AS ( JOIN order_execution oe -- contains surplus fee ON t.order_uid = oe.order_uid AND s.auction_id = oe.auction_id + LEFT OUTER JOIN order_quotes oq -- contains quote amounts + ON o.uid = oq.order_uid WHERE ss.block_deadline >= {{start_block}} AND ss.block_deadline <= {{end_block}} @@ -96,6 +102,28 @@ order_protocol_fee AS ( fp.surplus_factor / (1 - fp.surplus_factor) * surplus -- charge a fraction of surplus ) END + WHEN fp.kind = 'priceimprovement' THEN CASE + WHEN os.kind = 'sell' THEN + LEAST( + -- at most charge a fraction of volume + fp.price_improvement_max_volume_factor / (1 - fp.price_improvement_max_volume_factor) * os.buy_amount, + -- charge a fraction of price improvement, at most 0 + GREATEST( + fp.price_improvement_factor / (1 - fp.price_improvement_factor) * price_improvement + , + 0 + ) + ) + WHEN os.kind = 'buy' THEN LEAST( + -- at most charge a fraction of volume + fp.price_improvement_max_volume_factor / (1 + fp.price_improvement_max_volume_factor) * os.sell_amount, + -- charge a fraction of price improvement + GREATEST( + fp.price_improvement_factor / (1 - fp.price_improvement_factor) * price_improvement, + 0 + ) + ) + END WHEN fp.kind = 'volume' THEN CASE WHEN os.kind = 'sell' THEN fp.volume_factor / (1 - fp.volume_factor) * os.buy_amount diff --git a/queries/orderbook/prod_batch_rewards.sql b/queries/orderbook/prod_batch_rewards.sql index 61be7a3a..760a1d3e 100644 --- a/queries/orderbook/prod_batch_rewards.sql +++ b/queries/orderbook/prod_batch_rewards.sql @@ -48,6 +48,10 @@ order_surplus AS ( 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 t.buy_amount - t.sell_amount * (oq.buy_amount - oq.buy_amount / oq.sell_amount * oq.gas_amount * oq.gas_price / oq.sell_token_price) / oq.sell_amount + WHEN o.kind = 'buy' THEN t.buy_amount * (oq.sell_amount + oq.gas_amount * oq.gas_price / oq.sell_token_price) / oq.buy_amount - t.sell_amount + END AS price_improvement, CASE WHEN o.kind = 'sell' THEN o.buy_token WHEN o.kind = 'buy' THEN o.sell_token @@ -63,6 +67,8 @@ order_surplus AS ( JOIN order_execution oe -- contains surplus fee ON t.order_uid = oe.order_uid AND s.auction_id = oe.auction_id + LEFT OUTER JOIN order_quotes oq -- contains quote amounts + ON o.uid = oq.order_uid WHERE ss.block_deadline >= {{start_block}} AND ss.block_deadline <= {{end_block}} @@ -96,6 +102,28 @@ order_protocol_fee AS ( fp.surplus_factor / (1 - fp.surplus_factor) * surplus -- charge a fraction of surplus ) END + WHEN fp.kind = 'priceimprovement' THEN CASE + WHEN os.kind = 'sell' THEN + LEAST( + -- at most charge a fraction of volume + fp.price_improvement_max_volume_factor / (1 - fp.price_improvement_max_volume_factor) * os.buy_amount, + -- charge a fraction of price improvement, at most 0 + GREATEST( + fp.price_improvement_factor / (1 - fp.price_improvement_factor) * price_improvement + , + 0 + ) + ) + WHEN os.kind = 'buy' THEN LEAST( + -- at most charge a fraction of volume + fp.price_improvement_max_volume_factor / (1 + fp.price_improvement_max_volume_factor) * os.sell_amount, + -- charge a fraction of price improvement + GREATEST( + fp.price_improvement_factor / (1 - fp.price_improvement_factor) * price_improvement, + 0 + ) + ) + END WHEN fp.kind = 'volume' THEN CASE WHEN os.kind = 'sell' THEN fp.volume_factor / (1 - fp.volume_factor) * os.buy_amount diff --git a/tests/queries/batch_rewards_test_db.sql b/tests/queries/batch_rewards_test_db.sql index 20f0937b..93a1bb4c 100644 --- a/tests/queries/batch_rewards_test_db.sql +++ b/tests/queries/batch_rewards_test_db.sql @@ -6,6 +6,7 @@ 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 order_quotes; DROP TABLE IF EXISTS trades; DROP TABLE IF EXISTS order_execution; DROP TABLE IF EXISTS fee_policies; @@ -81,6 +82,17 @@ CREATE TABLE orders ( class OrderClass NOT NULL ); +CREATE TABLE IF NOT EXISTS order_quotes +( + order_uid bytea PRIMARY KEY, + gas_amount numeric(78, 0) NOT NULL, + gas_price numeric(78, 0) NOT NULL, + sell_token_price float NOT NULL, + sell_amount numeric(78, 0) NOT NULL, + buy_amount numeric(78, 0) NOT NULL, + solver bytea NOT NULL +); + CREATE TABLE IF NOT EXISTS trades ( block_number bigint NOT NULL, @@ -104,7 +116,7 @@ CREATE TABLE IF NOT EXISTS order_execution PRIMARY KEY (order_uid, auction_id) ); -CREATE TYPE PolicyKind AS ENUM ('surplus', 'volume'); +CREATE TYPE PolicyKind AS ENUM ('surplus', 'volume', 'priceimprovement'); CREATE TABLE fee_policies ( auction_id bigint NOT NULL, @@ -119,6 +131,8 @@ CREATE TABLE fee_policies ( surplus_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, + price_improvement_factor double precision, + price_improvement_max_volume_factor double precision, PRIMARY KEY (auction_id, order_uid, application_order) ); @@ -129,6 +143,7 @@ TRUNCATE settlement_scores; TRUNCATE settlement_observations; TRUNCATE auction_prices; TRUNCATE orders; +TRUNCATE order_quotes; TRUNCATE trades; TRUNCATE fee_policies; @@ -146,7 +161,11 @@ VALUES (1, 10, '\x5111111111111111111111111111111111111111'::bytea, '\x7111'::by (53, 10, '\x01'::bytea, '\x03'::bytea, '\x01'::bytea, 2, 53), (54, 10, '\x02'::bytea, '\x04'::bytea, '\x02'::bytea, 2, 54), (55, 10, '\x01'::bytea, '\x05'::bytea, '\x01'::bytea, 3, 55), - (56, 10, '\x02'::bytea, '\x06'::bytea, '\x02'::bytea, 3, 56); + (56, 10, '\x02'::bytea, '\x06'::bytea, '\x02'::bytea, 3, 56), + (57, 10, '\x03'::bytea, '\x07'::bytea, '\x03'::bytea, 0, 57), + (58, 10, '\x03'::bytea, '\x08'::bytea, '\x03'::bytea, 1, 58), + (59, 10, '\x03'::bytea, '\x09'::bytea, '\x03'::bytea, 2, 59), + (60, 10, '\x03'::bytea, '\x0a'::bytea, '\x03'::bytea, 3, 60); INSERT INTO auction_participants (auction_id, participant) VALUES (1, '\x5222222222222222222222222222222222222222'::bytea), @@ -176,7 +195,11 @@ VALUES (1, '\x5222222222222222222222222222222222222222'::bytea), (53, '\x01'::bytea), (54, '\x02'::bytea), (55, '\x01'::bytea), - (56, '\x02'::bytea); + (56, '\x02'::bytea), + (57, '\x03'::bytea), + (58, '\x03'::bytea), + (59, '\x03'::bytea), + (60, '\x03'::bytea); INSERT INTO settlement_scores (auction_id, winning_score, reference_score, winner, block_deadline, simulation_block) VALUES (1, 6000000000000000000, 4000000000000000000, '\x5111111111111111111111111111111111111111'::bytea, 10, 0), @@ -193,7 +216,11 @@ VALUES (1, 6000000000000000000, 4000000000000000000, '\x511111111111111111111111 (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 + (56, 500000000000000, 0, '\x02'::bytea, 65, 55), -- score probably wrong, does not take protocol fee into account + (57, 1000000000000000, 0, '\x03'::bytea, 66, 56), + (58, 1500000000000000, 0, '\x03'::bytea, 67, 57), + (59, 500000000000000, 0, '\x03'::bytea, 68, 58), + (60, 500000000000000, 0, '\x03'::bytea, 69, 59); INSERT INTO settlement_observations (block_number, log_index, gas_used, effective_gas_price, surplus, fee) VALUES (1, 10, 100000, 2000000000, 6000000000000000000, 200000000000000), @@ -209,7 +236,11 @@ VALUES (1, 10, 100000, 2000000000, 6000000000000000000, 200000000000000), (53, 10, 100000, 25000000000, 500000000000000, 3000000000000000), (54, 10, 100000, 25000000000, 500000000000000, 4000000000000000), (55, 10, 100000, 25000000000, 500000000000000, 2500000000000000), - (56, 10, 100000, 25000000000, 500000000000000, 2500000000000000); + (56, 10, 100000, 25000000000, 500000000000000, 2500000000000000), + (57, 10, 100000, 25000000000, 750000000000000, 2500000000000000), + (58, 10, 100000, 25000000000, 1000000000000000, 2500000000000000), + (59, 10, 100000, 25000000000, 500000000000000, 2500000000000000), + (60, 10, 100000, 25000000000, 250000000000000, 2500000000000000); INSERT INTO auction_prices (auction_id, token, price) VALUES (51, '\x01', 500000000000000000000000000), @@ -223,7 +254,15 @@ VALUES (51, '\x01', 500000000000000000000000000), (55, '\x01', 500000000000000000000000000), (55, '\x02', 500000000000000), (56, '\x01', 500000000000000000000000000), -(56, '\x02', 500000000000000); +(56, '\x02', 500000000000000), +(57, '\x01', 500000000000000000000000000), +(57, '\x02', 500000000000000), +(58, '\x01', 500000000000000000000000000), +(58, '\x02', 500000000000000), +(59, '\x01', 500000000000000000000000000), +(59, '\x02', 500000000000000), +(60, '\x01', 500000000000000000000000000), +(60, '\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 @@ -231,7 +270,17 @@ VALUES ('\x01'::bytea, '\x01'::bytea, '\x02'::bytea, 95000000, 94000000000000000 ('\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 +('\x06'::bytea, '\x01'::bytea, '\x02'::bytea, 106000000, 100000000000000000000, 0, 'buy', 'f', 0, 'limit'), -- in market buy limit order +('\x07'::bytea, '\x01'::bytea, '\x02'::bytea, 100000000, 94000000000000000000, 0, 'sell', 'f', 0, 'limit'), -- in market sell limit order +('\x08'::bytea, '\x01'::bytea, '\x02'::bytea, 106000000, 100000000000000000000, 0, 'buy', 'f', 0, 'limit'), -- in market buy limit order +('\x09'::bytea, '\x01'::bytea, '\x02'::bytea, 100000000, 94000000000000000000, 0, 'sell', 'f', 0, 'limit'), -- in market sell limit order +('\x0a'::bytea, '\x01'::bytea, '\x02'::bytea, 100000000, 94000000000000000000, 0, 'sell', 'f', 0, 'limit'); -- in market sell limit order + +INSERT INTO order_quotes (order_uid, gas_amount, gas_price, sell_token_price, sell_amount, buy_amount, solver) +VALUES ('\x07'::bytea, 100000, 25000000000, 500000000., 100000000, 100000000000000000000, '\x01'::bytea), +('\x08'::bytea, 100000, 25000000000, 500000000., 100000000, 100000000000000000000, '\x02'::bytea), +('\x09'::bytea, 100000, 25000000000, 500000000., 100000000, 90000000000000000000, '\x02'::bytea), +('\x0a'::bytea, 100000, 25000000000, 500000000., 100000000, 100000000000000000000, '\x02'::bytea); INSERT INTO trades (block_number, log_index, order_uid, sell_amount, buy_amount, fee_amount) VALUES (51, 0, '\x01'::bytea, 100000000, 95000000000000000000, 5000000), @@ -239,16 +288,29 @@ VALUES (51, 0, '\x01'::bytea, 100000000, 95000000000000000000, 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); +(56, 0, '\x06'::bytea, 105000000, 100000000000000000000, 0), +(57, 0, '\x07'::bytea, 100000000, 95500000000000000000, 0), +(58, 0, '\x08'::bytea, 104000000, 100000000000000000000, 0), +(59, 0, '\x09'::bytea, 100000000, 95000000000000000000, 0), +(60, 0, '\x0a'::bytea, 100000000, 94500000000000000000, 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); +('\x06'::bytea, 56, 0, 6000000, NULL), +('\x07'::bytea, 57, 0, 6000000, NULL), +('\x08'::bytea, 58, 0, 6000000, NULL), +('\x09'::bytea, 59, 0, 6000000, NULL), +('\x0a'::bytea, 60, 0, 6000000, NULL); + +INSERT INTO fee_policies (auction_id, order_uid, application_order, kind, surplus_factor, surplus_max_volume_factor, volume_factor, price_improvement_factor, price_improvement_max_volume_factor) +VALUES (53, '\x03'::bytea, 3, 'surplus', 0.5, 0.02, NULL, NULL, NULL), +(54, '\x04'::bytea, 4, 'surplus', 0.75, 0.1, NULL, NULL, NULL), +(55, '\x05'::bytea, 5, 'volume', NULL, NULL, 0.0015, NULL, NULL), +(56, '\x06'::bytea, 6, 'surplus', 0.9, 0.01, NULL, NULL, NULL), +(57, '\x07'::bytea, 7, 'priceimprovement', NULL, NULL, NULL, 0.5, 0.01), +(58, '\x08'::bytea, 8, 'priceimprovement', NULL, NULL, NULL, 0.5, 0.01), +(59, '\x09'::bytea, 9, 'priceimprovement', NULL, NULL, NULL, 0.5, 0.01), +(60, '\x0a'::bytea, 9, 'priceimprovement', NULL, NULL, NULL, 0.5, 0.01); -INSERT INTO fee_policies (auction_id, order_uid, application_order, kind, surplus_factor, surplus_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 5d6edfb8..951170d1 100644 --- a/tests/queries/test_batch_rewards.py +++ b/tests/queries/test_batch_rewards.py @@ -23,6 +23,7 @@ def test_get_batch_rewards(self): "solver": [ "0x01", "0x02", + "0x03", "0x5111111111111111111111111111111111111111", "0x5222222222222222222222222222222222222222", "0x5333333333333333333333333333333333333333", @@ -31,6 +32,7 @@ def test_get_batch_rewards(self): "primary_reward_eth": [ 2071357035553330.0, # 3 * 1e18 * 5e14 / 1e18 (surplus) + 571357035553330 (protocol fee) 3519801980198020.0, + 3729797979797980.0, # 1.5e18 * 5e14 / 1e18 + 2e6 * 5e26 / 1e18 + 1e18 * 5e14 / 1e18 + 0.5e18 * 5e14 / 1e18 + 1229797979797980.0 (protocol) 6000000000000000.00000, 12000000000000000.00000, -10000000000000000.00000, @@ -39,6 +41,7 @@ def test_get_batch_rewards(self): "num_participating_batches": [ 3, 3, + 4, 7, 2, 7, @@ -47,6 +50,7 @@ def test_get_batch_rewards(self): "protocol_fee_eth": [ 571357035553330.0, # 0.5 / (1 - 0.5) * 1e18 * 5e14 / 1e18 + 0.0015 / (1 - 0.0015) * 95e18 * 5e14 / 1e18 2.0198019801980198e15, # 0.75 / (1 - 0.75) * 1e6 * 5e26 / 1e18 + 0.01 / (1 + 0.01) * 105e6 * 5e26 / 1e18 + 1229797979797980.0, # 0.5 / (1 - 0.5) * 0.5e18 * 5e14 / 1e18 + 0.5 / (1 - 0.5) * 1e6 * 5e26 / 1e18 + 0.01 / (1 - 0.01) * 95e18 * 5e14 / 1e18 0.0, 0.0, 0.0, @@ -55,6 +59,7 @@ def test_get_batch_rewards(self): "network_fee_eth": [ 7463707813908982.0, # almost 2500000000000000 + 3000000000000000 + 2500000000000000 - 5.748876684972541e14 6980198019801980.0, # 2500000000000000 + 4000000000000000 + 2500000000000000 - 2.0198019801980198e15 + 8779179226823198.0, 1050000000000000.0, 400000000000000.0, 0.0,