Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions internal/migrations/033-order-book-settlement.sql
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}

Expand All @@ -110,14 +111,15 @@ 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; }
if $val_pid IS NOT NULL {
$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;
}
}

Expand Down
46 changes: 29 additions & 17 deletions internal/migrations/034-order-book-rewards.sql
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down
38 changes: 25 additions & 13 deletions tests/streams/order_book/fee_distribution_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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)
Expand Down