From 98dc72d666f8a2cc06269f17c496003e4ab71913 Mon Sep 17 00:00:00 2001 From: Michael Buntarman Date: Tue, 10 Mar 2026 03:24:57 +0700 Subject: [PATCH 1/2] chore: distribute data provider rewards --- .../migrations/033-order-book-settlement.sql | 6 ++- .../migrations/034-order-book-rewards.sql | 46 ++++++++++++------- 2 files changed, 33 insertions(+), 19 deletions(-) diff --git a/internal/migrations/033-order-book-settlement.sql b/internal/migrations/033-order-book-settlement.sql index 151a4630..6ebfee48 100644 --- a/internal/migrations/033-order-book-settlement.sql +++ b/internal/migrations/033-order-book-settlement.sql @@ -92,13 +92,14 @@ CREATE OR REPLACE ACTION distribute_fees( else if $bridge = 'sepolia_bridge' { sepolia_bridge.unlock($dp_wallet_hex, $infra_share); } else if $bridge = 'ethereum_bridge' { ethereum_bridge.unlock($dp_wallet_hex, $infra_share); } + $actual_dp_fees := $infra_share; + $dp_pid INT; for $p in SELECT id FROM ob_participants WHERE wallet_address = $dp_addr { $dp_pid := $p.id; } if $dp_pid IS NOT NULL { $next_id_dp INT; for $row in SELECT COALESCE(MAX(id), 0::INT) + 1 as val FROM ob_net_impacts { $next_id_dp := $row.val; } ob_record_net_impact($next_id_dp, $query_id, $dp_pid, $winning_outcome, 0::INT8, $infra_share, FALSE); - $actual_dp_fees := $infra_share; } } @@ -110,6 +111,8 @@ CREATE OR REPLACE ACTION distribute_fees( else if $bridge = 'sepolia_bridge' { sepolia_bridge.unlock($val_wallet, $infra_share); } else if $bridge = 'ethereum_bridge' { ethereum_bridge.unlock($val_wallet, $infra_share); } + $actual_validator_fees := $infra_share; + $val_pid INT; $leader_bytes BYTEA := tn_utils.get_leader_bytes(); for $p in SELECT id FROM ob_participants WHERE wallet_address = $leader_bytes { $val_pid := $p.id; } @@ -117,7 +120,6 @@ CREATE OR REPLACE ACTION distribute_fees( $next_id_val INT; for $row in SELECT COALESCE(MAX(id), 0::INT) + 1 as val FROM ob_net_impacts { $next_id_val := $row.val; } ob_record_net_impact($next_id_val, $query_id, $val_pid, $winning_outcome, 0::INT8, $infra_share, FALSE); - $actual_validator_fees := $infra_share; } } diff --git a/internal/migrations/034-order-book-rewards.sql b/internal/migrations/034-order-book-rewards.sql index 375c796e..3d53f575 100644 --- a/internal/migrations/034-order-book-rewards.sql +++ b/internal/migrations/034-order-book-rewards.sql @@ -114,31 +114,43 @@ CREATE OR REPLACE ACTION sample_lp_rewards( ERROR('Market is already settled'); } - -- Calculate midpoint - $best_bid INT; - $best_ask INT; + -- Calculate midpoint considering both YES and NO outcomes + $best_bid INT := 0; + $best_ask INT := 100; - for $row in SELECT price FROM ob_positions - WHERE query_id = $query_id AND outcome = TRUE AND price < 0 - ORDER BY price ASC LIMIT 1 - { - $best_bid := $row.price; + -- YES Buys (price < 0) -> bid = ABS(price) + for $row in SELECT price FROM ob_positions WHERE query_id = $query_id AND outcome = TRUE AND price < 0 ORDER BY price ASC LIMIT 1 { + $p INT := $row.price; + if $p < 0 { $p := -$p; } + if $p > $best_bid { $best_bid := $p; } } - for $row in SELECT price FROM ob_positions - WHERE query_id = $query_id AND outcome = TRUE AND price > 0 - ORDER BY price ASC LIMIT 1 - { - $best_ask := $row.price; + -- NO Sells (price > 0) -> bid = 100 - price + for $row in SELECT price FROM ob_positions WHERE query_id = $query_id AND outcome = FALSE AND price > 0 ORDER BY price ASC LIMIT 1 { + $p INT := 100 - $row.price; + if $p > $best_bid { $best_bid := $p; } + } + + -- YES Sells (price > 0) -> ask = price + for $row in SELECT price FROM ob_positions WHERE query_id = $query_id AND outcome = TRUE AND price > 0 ORDER BY price ASC LIMIT 1 { + if $row.price < $best_ask { $best_ask := $row.price; } + } + + -- NO Buys (price < 0) -> ask = 100 - ABS(price) + for $row in SELECT price FROM ob_positions WHERE query_id = $query_id AND outcome = FALSE AND price < 0 ORDER BY price ASC LIMIT 1 { + $p INT := $row.price; + if $p < 0 { $p := -$p; } + $p := 100 - $p; + if $p < $best_ask { $best_ask := $p; } } - -- If no two-sided liquidity, no rewards - if $best_bid IS NULL OR $best_ask IS NULL { + -- If no valid bids or asks were found (i.e. still 0 or 100), no two-sided liquidity + if $best_bid = 0 OR $best_ask = 100 { RETURN; } - -- Midpoint is (BestAsk + BestBidMagnitude) / 2 - $x_mid INT := ($best_ask + ABS($best_bid)) / 2; + -- Midpoint is (best_ask + best_bid) / 2 + $x_mid INT := ($best_ask + $best_bid) / 2; -- Dynamic spread $x_spread_base INT := ABS($x_mid - (100 - $x_mid)); From 845c4fd34ffd733ad03086c6b7e598b75fe8afa3 Mon Sep 17 00:00:00 2001 From: Michael Buntarman Date: Tue, 10 Mar 2026 04:16:41 +0700 Subject: [PATCH 2/2] chore: patch CI --- .../order_book/fee_distribution_test.go | 38 ++++++++++++------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/tests/streams/order_book/fee_distribution_test.go b/tests/streams/order_book/fee_distribution_test.go index 5b8d26b6..59da1fc3 100644 --- a/tests/streams/order_book/fee_distribution_test.go +++ b/tests/streams/order_book/fee_distribution_test.go @@ -190,15 +190,24 @@ func testDistribution1Block2LPs(t *testing.T) func(context.Context, *kwilTesting infraShare := new(big.Int).Div(new(big.Int).Mul(totalFees, big.NewInt(125)), big.NewInt(1000)) lpShareTotal := new(big.Int).Sub(totalFees, new(big.Int).Mul(infraShare, big.NewInt(2))) - // User1 is DP, so they get 12.5% infraShare + their LP share - // User1 LP percentage is 64% of lpShareTotal - // User2 LP percentage is 36% of lpShareTotal - expectedLP1 := new(big.Int).Div(new(big.Int).Mul(lpShareTotal, big.NewInt(64)), big.NewInt(100)) - expectedLP2 := new(big.Int).Div(new(big.Int).Mul(lpShareTotal, big.NewInt(36)), big.NewInt(100)) + // Dynamically calculate expected LP shares based on sampled rewards + // rewards map is [participant_id: percentage] + expectedLP1 := big.NewInt(0) + expectedLP2 := big.NewInt(0) + + if p1Reward, ok := rewards[1]; ok { + // Convert float64 percentage to big.Int with precision + // reward_percent is already 0-100 + p1Wei := new(big.Int).Mul(lpShareTotal, big.NewInt(int64(p1Reward*100))) + expectedLP1 = new(big.Int).Div(p1Wei, big.NewInt(10000)) + } + if p2Reward, ok := rewards[2]; ok { + p2Wei := new(big.Int).Mul(lpShareTotal, big.NewInt(int64(p2Reward*100))) + expectedLP2 = new(big.Int).Div(p2Wei, big.NewInt(10000)) + } // User1 gets expectedLP1 + infraShare (as DP) // They might also get another infraShare if they are the leader (@leader_sender) - // In kwil-db testing, @leader_sender usually defaults to the first validator expectedDist1 := new(big.Int).Add(expectedLP1, infraShare) expectedDist2 := expectedLP2 @@ -376,16 +385,19 @@ func testDistribution3Blocks2LPs(t *testing.T) func(context.Context, *kwilTestin infraShare := new(big.Int).Div(new(big.Int).Mul(totalFees, big.NewInt(125)), big.NewInt(1000)) lpShareTotal := new(big.Int).Sub(totalFees, new(big.Int).Mul(infraShare, big.NewInt(2))) - // Total percentages: User1 = 192%, User2 = 108% - // User1 LP: (lpShareTotal * 192) / (100 * 3) - // User2 LP: (lpShareTotal * 108) / (100 * 3) + // Calculate average LP share across all blocks + totalP1Reward := rewards1000[1] + rewards2000[1] + rewards3000[1] + totalP2Reward := rewards1000[2] + rewards2000[2] + rewards3000[2] + + // User1 LP: (lpShareTotal * totalP1Reward) / (100 * 3) + // User2 LP: (lpShareTotal * totalP2Reward) / (100 * 3) expectedLP1 := new(big.Int).Div( - new(big.Int).Mul(lpShareTotal, big.NewInt(192)), - big.NewInt(300), + new(big.Int).Mul(lpShareTotal, big.NewInt(int64(totalP1Reward*100))), + big.NewInt(30000), ) expectedLP2 := new(big.Int).Div( - new(big.Int).Mul(lpShareTotal, big.NewInt(108)), - big.NewInt(300), + new(big.Int).Mul(lpShareTotal, big.NewInt(int64(totalP2Reward*100))), + big.NewInt(30000), ) // User1 is DP, so gets expectedLP1 + infraShare (+ Leader share if applicable)