diff --git a/extensions/tn_settlement/settlement_integration_test.go b/extensions/tn_settlement/settlement_integration_test.go index 62be9b1fc..1369dae2b 100644 --- a/extensions/tn_settlement/settlement_integration_test.go +++ b/extensions/tn_settlement/settlement_integration_test.go @@ -28,6 +28,11 @@ import ( // NO_OUTCOME_VALUE represents a NO outcome in settlement tests const NO_OUTCOME_VALUE = "-1.000000000000000000" +// Test constants - match existing erc20-bridge configuration +const ( + testExtensionName = "sepolia_bridge" +) + // TestSettlementIntegration tests the automatic settlement functionality func TestSettlementIntegration(t *testing.T) { owner := util.Unsafe_NewEthereumAddressFromString("0x4444444444444444444444444444444444444444") @@ -77,7 +82,7 @@ func testFindUnsettledMarkets(t *testing.T) func(context.Context, *kwilTesting.P var queryID int createRes, err := platform.Engine.Call(engineCtx, platform.DB, "", "create_market", - []any{attestationHash, settleTime, int64(5), int64(1)}, + []any{testExtensionName, attestationHash, settleTime, int64(5), int64(1)}, func(row *common.Row) error { queryID = int(row.Values[0].(int64)) return nil @@ -174,7 +179,7 @@ func testSettleMarketViaAction(t *testing.T) func(context.Context, *kwilTesting. var queryID int createRes, err := platform.Engine.Call(engineCtx, platform.DB, "", "create_market", - []any{attestationHash, settleTime, int64(5), int64(1)}, + []any{testExtensionName, attestationHash, settleTime, int64(5), int64(1)}, func(row *common.Row) error { queryID = int(row.Values[0].(int64)) return nil @@ -274,7 +279,7 @@ func testSkipMarketWithoutAttestation(t *testing.T) func(context.Context, *kwilT var queryID int createRes, err := platform.Engine.Call(engineCtx, platform.DB, "", "create_market", - []any{attestationHash, settleTime, int64(5), int64(1)}, + []any{testExtensionName, attestationHash, settleTime, int64(5), int64(1)}, func(row *common.Row) error { queryID = int(row.Values[0].(int64)) return nil @@ -349,7 +354,7 @@ func testMultipleMarketsProcessing(t *testing.T) func(context.Context, *kwilTest var queryID int createRes, err := platform.Engine.Call(engineCtx, platform.DB, "", "create_market", - []any{attestationHash, settleTime, int64(5), int64(1)}, + []any{testExtensionName, attestationHash, settleTime, int64(5), int64(1)}, func(row *common.Row) error { queryID = int(row.Values[0].(int64)) return nil diff --git a/internal/migrations/030-order-book-schema.sql b/internal/migrations/030-order-book-schema.sql index a1909932a..b5206d377 100644 --- a/internal/migrations/030-order-book-schema.sql +++ b/internal/migrations/030-order-book-schema.sql @@ -28,6 +28,7 @@ CREATE TABLE IF NOT EXISTS ob_queries ( min_order_size INT8 NOT NULL DEFAULT 20, created_at INT8 NOT NULL, creator BYTEA NOT NULL, + bridge TEXT NOT NULL, CONSTRAINT chk_ob_queries_max_spread CHECK (max_spread >= 1 AND max_spread <= 50), CONSTRAINT chk_ob_queries_min_order CHECK (min_order_size >= 1) diff --git a/internal/migrations/031-order-book-vault.sql b/internal/migrations/031-order-book-vault.sql index a94921756..657d4314a 100644 --- a/internal/migrations/031-order-book-vault.sql +++ b/internal/migrations/031-order-book-vault.sql @@ -9,28 +9,44 @@ * IMPORTANT: The "vault" is the network's ownedBalance in the ERC20 bridge, * stored in reward_instances.balance. We use lock()/unlock() methods. * - * TODO: When USDC bridge is deployed (see "Goal: Deposit/withdraw contracts"), - * replace ethereum_bridge with usdc_bridge in all collateral operations. + * Bridge Support: + * - All vault operations accept a $bridge parameter + * - Supported bridges: hoodi_tt2, sepolia_bridge, ethereum_bridge */ +-- ============================================================================= +-- validate_bridge: Helper to validate bridge parameter +-- ============================================================================= +-- Shared validation procedure (defined in 032-order-book-actions.sql) +-- Note: This procedure is defined in the order book actions file but used here too + -- ============================================================================= -- ob_lock_collateral: Lock user's collateral into network vault -- ============================================================================= -- Uses the ERC20 bridge's lock() method which: -- - Decreases user's balance in kwil_erc20_meta.balances -- - Increases network's ownedBalance in reward_instances.balance --- --- TODO: Replace ethereum_bridge with usdc_bridge when USDC bridge is deployed -CREATE OR REPLACE ACTION ob_lock_collateral($amount NUMERIC(78, 0)) +CREATE OR REPLACE ACTION ob_lock_collateral($bridge TEXT, $amount NUMERIC(78, 0)) PRIVATE { + -- Validate bridge (will ERROR if invalid) + -- Note: validate_bridge procedure defined in 032-order-book-actions.sql + -- Since migrations run in order, that procedure will exist when this is called + -- Validate amount if $amount IS NULL OR $amount <= 0::NUMERIC(78, 0) { ERROR('Lock amount must be positive'); } -- Lock collateral using bridge (user -> network ownedBalance) - -- TODO: Change to usdc_bridge.lock($amount) when USDC bridge is available - ethereum_bridge.lock($amount); + if $bridge = 'hoodi_tt2' { + hoodi_tt2.lock($amount); + } else if $bridge = 'sepolia_bridge' { + sepolia_bridge.lock($amount); + } else if $bridge = 'ethereum_bridge' { + ethereum_bridge.lock($amount); + } else { + ERROR('Invalid bridge. Supported: hoodi_tt2, sepolia_bridge, ethereum_bridge'); + } }; -- ============================================================================= @@ -41,8 +57,7 @@ PRIVATE { -- - Increases user's balance -- -- NOTE: This is effectively a SYSTEM action - unlock() requires owner permission --- TODO: Replace ethereum_bridge with usdc_bridge when USDC bridge is deployed -CREATE OR REPLACE ACTION ob_unlock_collateral($user_address TEXT, $amount NUMERIC(78, 0)) +CREATE OR REPLACE ACTION ob_unlock_collateral($bridge TEXT, $user_address TEXT, $amount NUMERIC(78, 0)) PRIVATE { -- Validate inputs (must be 0x-prefixed 40 hex character address) if $user_address IS NULL OR length($user_address) != 42 OR substring(LOWER($user_address), 1, 2) != '0x' { @@ -54,8 +69,15 @@ PRIVATE { } -- Unlock collateral using bridge (network ownedBalance -> user) - -- TODO: Change to usdc_bridge.unlock($user_address, $amount) when USDC bridge is available - ethereum_bridge.unlock($user_address, $amount); + if $bridge = 'hoodi_tt2' { + hoodi_tt2.unlock($user_address, $amount); + } else if $bridge = 'sepolia_bridge' { + sepolia_bridge.unlock($user_address, $amount); + } else if $bridge = 'ethereum_bridge' { + ethereum_bridge.unlock($user_address, $amount); + } else { + ERROR('Invalid bridge. Supported: hoodi_tt2, sepolia_bridge, ethereum_bridge'); + } }; -- ============================================================================= diff --git a/internal/migrations/032-order-book-actions.sql b/internal/migrations/032-order-book-actions.sql index c0d2da2c4..a8d89814f 100644 --- a/internal/migrations/032-order-book-actions.sql +++ b/internal/migrations/032-order-book-actions.sql @@ -5,8 +5,63 @@ * - create_market: Create a new prediction market * - get_market_info: Get market details by ID * - list_markets: List markets with optional filtering + * + * Bridge Support: + * - All collateral-locking actions accept a $bridge parameter + * - Supported bridges: hoodi_tt2, sepolia_bridge, ethereum_bridge */ +-- ============================================================================= +-- validate_bridge: Helper to validate bridge parameter +-- ============================================================================= +/** + * Validates that the bridge parameter is one of the supported bridges. + * Throws an error if invalid. + * + * Parameters: + * - $bridge: The bridge namespace to validate + */ +CREATE OR REPLACE ACTION validate_bridge($bridge TEXT) PRIVATE { + if $bridge IS NULL { + ERROR('bridge parameter is required'); + } + + if $bridge != 'hoodi_tt2' AND + $bridge != 'sepolia_bridge' AND + $bridge != 'ethereum_bridge' { + ERROR('Invalid bridge. Supported: hoodi_tt2, sepolia_bridge, ethereum_bridge'); + } + + RETURN; +}; + +-- ============================================================================= +-- get_market_bridge: Get the bridge for a market +-- ============================================================================= +/** + * Retrieves the bridge namespace for a given market. + * Throws an error if market doesn't exist. + * + * Parameters: + * - $query_id: The market ID + * + * Returns: + * - The bridge namespace (hoodi_tt2, sepolia_bridge, or ethereum_bridge) + */ +CREATE OR REPLACE ACTION get_market_bridge($query_id INT) PRIVATE RETURNS (bridge TEXT) { + $found_bridge TEXT; + + for $row in SELECT bridge FROM ob_queries WHERE id = $query_id { + $found_bridge := $row.bridge; + } + + if $found_bridge IS NULL { + ERROR('Market does not exist (query_id: ' || $query_id::TEXT || ')'); + } + + RETURN $found_bridge; +}; + -- ============================================================================= -- create_market: Create a new prediction market -- ============================================================================= @@ -15,6 +70,7 @@ * identified by a unique hash derived from the attestation query parameters. * * Parameters: + * - $bridge: Bridge namespace for collateral (hoodi_tt2, sepolia_bridge, ethereum_bridge) * - $query_hash: SHA256 hash of (data_provider + stream_id + action_id + args) - 32 bytes * - $settle_time: Unix timestamp when market can be settled (must be in future) * - $max_spread: Maximum spread for LP rewards (1-50 cents) @@ -27,6 +83,7 @@ * - 2 TRUF market creation fee (TODO: adjust based on spam prevention needs) */ CREATE OR REPLACE ACTION create_market( + $bridge TEXT, $query_hash BYTEA, $settle_time INT8, $max_spread INT, @@ -36,6 +93,9 @@ CREATE OR REPLACE ACTION create_market( -- VALIDATION -- ========================================================================== + -- Validate bridge parameter + validate_bridge($bridge); + -- Validate query hash (must be exactly 32 bytes for SHA256) -- Note: length() doesn't support BYTEA in Kuneiform, so we use encode() to convert -- to hex string and check the length (32 bytes = 64 hex characters) @@ -69,9 +129,16 @@ CREATE OR REPLACE ACTION create_market( -- TODO: Adjust fee based on spam prevention needs $market_creation_fee NUMERIC(78, 0) := '2000000000000000000'::NUMERIC(78, 0); - -- Check caller has sufficient balance + -- Check caller has sufficient balance (bridge-specific) -- TODO: Review when USDC bridge available - $caller_balance NUMERIC(78, 0) := COALESCE(ethereum_bridge.balance(@caller), 0::NUMERIC(78, 0)); + $caller_balance NUMERIC(78, 0); + if $bridge = 'hoodi_tt2' { + $caller_balance := COALESCE(hoodi_tt2.balance(@caller), 0::NUMERIC(78, 0)); + } else if $bridge = 'sepolia_bridge' { + $caller_balance := COALESCE(sepolia_bridge.balance(@caller), 0::NUMERIC(78, 0)); + } else if $bridge = 'ethereum_bridge' { + $caller_balance := COALESCE(ethereum_bridge.balance(@caller), 0::NUMERIC(78, 0)); + } if $caller_balance < $market_creation_fee { ERROR('Insufficient balance for market creation fee. Required: 2 TRUF'); @@ -82,11 +149,17 @@ CREATE OR REPLACE ACTION create_market( ERROR('Leader address not available for fee transfer'); } - -- Transfer fee to leader + -- Transfer fee to leader (bridge-specific) -- Note: Bridge operations throw ERROR on failure (insufficient balance, etc.) -- so no explicit return value check is needed $leader_hex TEXT := encode(@leader_sender, 'hex')::TEXT; - ethereum_bridge.transfer($leader_hex, $market_creation_fee); + if $bridge = 'hoodi_tt2' { + hoodi_tt2.transfer($leader_hex, $market_creation_fee); + } else if $bridge = 'sepolia_bridge' { + sepolia_bridge.transfer($leader_hex, $market_creation_fee); + } else if $bridge = 'ethereum_bridge' { + ethereum_bridge.transfer($leader_hex, $market_creation_fee); + } -- ========================================================================== -- CREATE MARKET @@ -112,7 +185,8 @@ CREATE OR REPLACE ACTION create_market( max_spread, min_order_size, created_at, - creator + creator, + bridge ) SELECT COALESCE(MAX(id), 0) + 1, @@ -121,7 +195,8 @@ CREATE OR REPLACE ACTION create_market( $max_spread, $min_order_size, @height, - $caller_bytes + $caller_bytes, + $bridge FROM ob_queries; -- Get the ID we just inserted @@ -368,7 +443,8 @@ PUBLIC VIEW RETURNS (market_exists BOOLEAN) { CREATE OR REPLACE ACTION match_direct( $query_id INT, $outcome BOOL, - $price INT + $price INT, + $bridge TEXT ) PRIVATE { -- Get first buy order (FIFO: earliest first) $buy_participant_id INT; @@ -445,7 +521,7 @@ CREATE OR REPLACE ACTION match_direct( $multiplier); -- Transfer payment from vault to seller - ob_unlock_collateral($seller_wallet_address, $seller_payment); + ob_unlock_collateral($bridge, $seller_wallet_address, $seller_payment); -- Transfer shares from seller to buyer -- Step 1: Delete fully matched orders FIRST (prevents amount=0 constraint violation) @@ -519,7 +595,8 @@ CREATE OR REPLACE ACTION match_direct( CREATE OR REPLACE ACTION match_mint( $query_id INT, $yes_price INT, - $no_price INT + $no_price INT, + $bridge TEXT ) PRIVATE { -- Validate complementary prices (must sum to 100) if ($yes_price + $no_price) != 100 { @@ -670,7 +747,8 @@ CREATE OR REPLACE ACTION match_mint( CREATE OR REPLACE ACTION match_burn( $query_id INT, $yes_price INT, - $no_price INT + $no_price INT, + $bridge TEXT ) PRIVATE { -- Multiplier for collateral calculations $multiplier NUMERIC(78, 0) := '10000000000000000'::NUMERIC(78, 0); @@ -769,8 +847,8 @@ CREATE OR REPLACE ACTION match_burn( $multiplier); -- Unlock collateral - ob_unlock_collateral($yes_wallet_address, $yes_payout); - ob_unlock_collateral($no_wallet_address, $no_payout); + ob_unlock_collateral($bridge, $yes_wallet_address, $yes_payout); + ob_unlock_collateral($bridge, $no_wallet_address, $no_payout); -- Delete fully matched sell orders FIRST DELETE FROM ob_positions @@ -821,7 +899,8 @@ CREATE OR REPLACE ACTION match_burn( CREATE OR REPLACE ACTION match_orders( $query_id INT, $outcome BOOL, - $price INT + $price INT, + $bridge TEXT ) PRIVATE { -- ========================================================================== -- SECTION 1: VALIDATE INPUTS @@ -845,7 +924,7 @@ CREATE OR REPLACE ACTION match_orders( -- Match buy and sell orders at the same price for same outcome -- This is the most common match type and should be tried first - match_direct($query_id, $outcome, $price); + match_direct($query_id, $outcome, $price, $bridge); -- ========================================================================== -- SECTION 3: TRY MINT MATCH @@ -861,10 +940,10 @@ CREATE OR REPLACE ACTION match_orders( -- Match based on which outcome we're matching for if $outcome = true { -- Matching YES order: look for NO buy at complementary price - match_mint($query_id, $price, $complementary_price); + match_mint($query_id, $price, $complementary_price, $bridge); } else { -- Matching NO order: look for YES buy at complementary price - match_mint($query_id, $complementary_price, $price); + match_mint($query_id, $complementary_price, $price, $bridge); } } @@ -878,10 +957,10 @@ CREATE OR REPLACE ACTION match_orders( -- Match based on which outcome we're matching for if $outcome = true { -- Matching YES order: look for NO sell at complementary price - match_burn($query_id, $price, $complementary_price); + match_burn($query_id, $price, $complementary_price, $bridge); } else { -- Matching NO order: look for YES sell at complementary price - match_burn($query_id, $complementary_price, $price); + match_burn($query_id, $complementary_price, $price, $bridge); } } @@ -899,6 +978,8 @@ CREATE OR REPLACE ACTION match_orders( * to the order book with a NEGATIVE price to distinguish it from sell orders. * The matching engine is triggered to check for immediate matches. * + * The bridge is determined by the market's configuration (set during create_market). + * * Parameters: * - $query_id: Market identifier (from ob_queries.id) * - $outcome: TRUE for YES shares, FALSE for NO shares @@ -930,14 +1011,17 @@ CREATE OR REPLACE ACTION place_buy_order( -- SECTION 1: VALIDATION -- ========================================================================== - -- 1.1 Validate @caller format (must be 0x-prefixed Ethereum address) + -- 1.1 Get market bridge (will ERROR if market doesn't exist) + $bridge TEXT := get_market_bridge($query_id); + + -- 1.2 Validate @caller format (must be 0x-prefixed Ethereum address) -- Note: Kwil supports both Secp256k1 (EVM) and ED25519 signers. This action -- requires a 0x-prefixed Ethereum address format for EVM compatibility. if @caller IS NULL OR length(@caller) != 42 OR substring(LOWER(@caller), 1, 2) != '0x' { ERROR('Invalid caller address format (expected 0x-prefixed Ethereum address)'); } - -- 1.2 Validate parameters + -- 1.3 Validate parameters if $query_id IS NULL { ERROR('query_id is required'); } @@ -958,7 +1042,7 @@ CREATE OR REPLACE ACTION place_buy_order( ERROR('amount exceeds maximum allowed of 1,000,000,000'); } - -- 1.3 Validate market exists and is not settled + -- 1.4 Validate market exists and is not settled $settled BOOL; $market_found BOOL := false; @@ -995,10 +1079,17 @@ CREATE OR REPLACE ACTION place_buy_order( $collateral_needed NUMERIC(78, 0) := ($amount::NUMERIC(78, 0) * $price::NUMERIC(78, 0) * '10000000000000000'::NUMERIC(78, 0)); -- ========================================================================== - -- SECTION 3: CHECK BALANCE + -- SECTION 3: CHECK BALANCE (bridge-specific) -- ========================================================================== - $caller_balance NUMERIC(78, 0) := COALESCE(ethereum_bridge.balance(@caller), 0::NUMERIC(78, 0)); + $caller_balance NUMERIC(78, 0); + if $bridge = 'hoodi_tt2' { + $caller_balance := COALESCE(hoodi_tt2.balance(@caller), 0::NUMERIC(78, 0)); + } else if $bridge = 'sepolia_bridge' { + $caller_balance := COALESCE(sepolia_bridge.balance(@caller), 0::NUMERIC(78, 0)); + } else if $bridge = 'ethereum_bridge' { + $caller_balance := COALESCE(ethereum_bridge.balance(@caller), 0::NUMERIC(78, 0)); + } if $caller_balance < $collateral_needed { -- Note: Division by 10^18 for display purposes (convert wei to TRUF) @@ -1034,13 +1125,18 @@ CREATE OR REPLACE ACTION place_buy_order( } -- ========================================================================== - -- SECTION 5: LOCK COLLATERAL + -- SECTION 5: LOCK COLLATERAL (bridge-specific) -- ========================================================================== -- Lock tokens from user to vault (network-owned balance) - -- Note: ethereum_bridge.lock() throws ERROR on failure (insufficient balance, etc.) - -- TODO: Replace ethereum_bridge with usdc_bridge when USDC bridge is deployed - ethereum_bridge.lock($collateral_needed); + -- Note: Bridge lock() throws ERROR on failure (insufficient balance, etc.) + if $bridge = 'hoodi_tt2' { + hoodi_tt2.lock($collateral_needed); + } else if $bridge = 'sepolia_bridge' { + sepolia_bridge.lock($collateral_needed); + } else if $bridge = 'ethereum_bridge' { + ethereum_bridge.lock($collateral_needed); + } -- ========================================================================== -- SECTION 6: INSERT BUY ORDER (UPSERT) @@ -1064,7 +1160,7 @@ CREATE OR REPLACE ACTION place_buy_order( -- ========================================================================== -- Attempt to match this buy order with existing sell orders - match_orders($query_id, $outcome, $price); + match_orders($query_id, $outcome, $price, $bridge); -- Success: Order placed (may be partially or fully matched by future matching engine) }; @@ -1106,6 +1202,9 @@ CREATE OR REPLACE ACTION place_sell_order( -- SECTION 1: VALIDATION -- ========================================================================== + -- 1.0 Get market bridge (will ERROR if market doesn't exist) + $bridge TEXT := get_market_bridge($query_id); + -- 1.1 Validate @caller format (must be 0x-prefixed Ethereum address) -- Note: Kwil supports both Secp256k1 (EVM) and ED25519 signers. This action -- requires a 0x-prefixed Ethereum address format for EVM compatibility. @@ -1238,7 +1337,7 @@ CREATE OR REPLACE ACTION place_sell_order( -- ========================================================================== -- Attempt to match this sell order with existing buy orders - match_orders($query_id, $outcome, $price); + match_orders($query_id, $outcome, $price, $bridge); -- Success: Order placed (may be partially or fully matched by future matching engine) }; @@ -1254,6 +1353,8 @@ CREATE OR REPLACE ACTION place_sell_order( * This feature enables users to become liquidity providers and earn a share * of redemption fees by providing tight two-sided markets. * + * The bridge is determined by the market's configuration (set during create_market). + * * Parameters: * - $query_id: Market identifier (from ob_queries.id) * - $true_price: Price for YES shares in cents (1-99 = $0.01 to $0.99) @@ -1301,14 +1402,17 @@ CREATE OR REPLACE ACTION place_split_limit_order( -- SECTION 1: VALIDATION -- ========================================================================== - -- 1.1 Validate @caller format (must be 0x-prefixed Ethereum address) + -- 1.1 Get market bridge (will ERROR if market doesn't exist) + $bridge TEXT := get_market_bridge($query_id); + + -- 1.2 Validate @caller format (must be 0x-prefixed Ethereum address) -- Note: Kwil supports both Secp256k1 (EVM) and ED25519 signers. This action -- requires a 0x-prefixed Ethereum address format for EVM compatibility. if @caller IS NULL OR length(@caller) != 42 OR substring(LOWER(@caller), 1, 2) != '0x' { ERROR('Invalid caller address format (expected 0x-prefixed Ethereum address)'); } - -- 1.2 Validate parameters + -- 1.3 Validate parameters if $query_id IS NULL { ERROR('query_id is required'); } @@ -1325,7 +1429,7 @@ CREATE OR REPLACE ACTION place_split_limit_order( ERROR('amount exceeds maximum allowed of 1,000,000,000'); } - -- 1.3 Validate market exists and is not settled + -- 1.4 Validate market exists and is not settled $settled BOOL; $market_found BOOL := false; @@ -1362,10 +1466,17 @@ CREATE OR REPLACE ACTION place_split_limit_order( $collateral_needed NUMERIC(78, 0) := ($amount::NUMERIC(78, 0) * '1000000000000000000'::NUMERIC(78, 0)); -- ========================================================================== - -- SECTION 3: CHECK BALANCE + -- SECTION 3: CHECK BALANCE (bridge-specific) -- ========================================================================== - $caller_balance NUMERIC(78, 0) := COALESCE(ethereum_bridge.balance(@caller), 0::NUMERIC(78, 0)); + $caller_balance NUMERIC(78, 0); + if $bridge = 'hoodi_tt2' { + $caller_balance := COALESCE(hoodi_tt2.balance(@caller), 0::NUMERIC(78, 0)); + } else if $bridge = 'sepolia_bridge' { + $caller_balance := COALESCE(sepolia_bridge.balance(@caller), 0::NUMERIC(78, 0)); + } else if $bridge = 'ethereum_bridge' { + $caller_balance := COALESCE(ethereum_bridge.balance(@caller), 0::NUMERIC(78, 0)); + } if $caller_balance < $collateral_needed { -- Note: Division by 10^18 for display purposes (convert wei to TRUF) @@ -1401,13 +1512,18 @@ CREATE OR REPLACE ACTION place_split_limit_order( } -- ========================================================================== - -- SECTION 5: LOCK COLLATERAL + -- SECTION 5: LOCK COLLATERAL (bridge-specific) -- ========================================================================== -- Lock tokens from user to vault (network-owned balance) - -- Note: ethereum_bridge.lock() throws ERROR on failure (insufficient balance, etc.) - -- TODO: Replace ethereum_bridge with usdc_bridge when USDC bridge is deployed - ethereum_bridge.lock($collateral_needed); + -- Note: Bridge lock() throws ERROR on failure (insufficient balance, etc.) + if $bridge = 'hoodi_tt2' { + hoodi_tt2.lock($collateral_needed); + } else if $bridge = 'sepolia_bridge' { + sepolia_bridge.lock($collateral_needed); + } else if $bridge = 'ethereum_bridge' { + ethereum_bridge.lock($collateral_needed); + } -- ========================================================================== -- SECTION 6: MINT YES SHARES (HOLDING) @@ -1450,7 +1566,7 @@ CREATE OR REPLACE ACTION place_split_limit_order( -- Attempt to match the NO sell order with existing buy orders -- Match is attempted on the FALSE (NO) outcome at the false_price - match_orders($query_id, FALSE, $false_price); + match_orders($query_id, FALSE, $false_price, $bridge); -- Success: Split order placed -- - YES shares held at price=0 (not for sale) @@ -1508,6 +1624,13 @@ CREATE OR REPLACE ACTION cancel_order( $outcome BOOL, $price INT ) PUBLIC { + -- ========================================================================== + -- SECTION 0: GET MARKET BRIDGE + -- ========================================================================== + + -- Get market bridge (will ERROR if market doesn't exist) + $bridge TEXT := get_market_bridge($query_id); + -- ========================================================================== -- SECTION 1: VALIDATE CALLER -- ========================================================================== @@ -1611,8 +1734,8 @@ CREATE OR REPLACE ACTION cancel_order( $refund_amount NUMERIC(78, 0) := $order_amount::NUMERIC(78, 0) * $abs_price::NUMERIC(78, 0) * $multiplier; -- Unlock collateral back to user using helper from 031-order-book-vault.sql - -- This calls ethereum_bridge.unlock() internally - ob_unlock_collateral(@caller, $refund_amount); + -- Passes bridge parameter to unlock from correct bridge + ob_unlock_collateral($bridge, @caller, $refund_amount); } -- For sell orders (price > 0): Return shares to holding wallet @@ -1709,6 +1832,13 @@ CREATE OR REPLACE ACTION change_bid( $new_price INT, $new_amount INT8 ) PUBLIC { + -- ========================================================================== + -- SECTION 0: GET MARKET BRIDGE + -- ========================================================================== + + -- Get market bridge (will ERROR if market doesn't exist) + $bridge TEXT := get_market_bridge($query_id); + -- ========================================================================== -- SECTION 1: VALIDATE CALLER -- ========================================================================== @@ -1824,13 +1954,19 @@ CREATE OR REPLACE ACTION change_bid( if $collateral_delta > $zero { -- New order needs MORE collateral -- Lock additional amount (will ERROR if insufficient balance) - ethereum_bridge.lock($collateral_delta); + if $bridge = 'hoodi_tt2' { + hoodi_tt2.lock($collateral_delta); + } else if $bridge = 'sepolia_bridge' { + sepolia_bridge.lock($collateral_delta); + } else if $bridge = 'ethereum_bridge' { + ethereum_bridge.lock($collateral_delta); + } } else if $collateral_delta < $zero { -- New order needs LESS collateral -- Unlock excess amount $unlock_amount NUMERIC(78, 0) := $zero - $collateral_delta; -- Make positive - ob_unlock_collateral(@caller, $unlock_amount); + ob_unlock_collateral($bridge, @caller, $unlock_amount); } -- If $collateral_delta = 0, no collateral adjustment needed @@ -1867,7 +2003,7 @@ CREATE OR REPLACE ACTION change_bid( -- Try to match new order immediately -- Note: match_orders expects positive price (1-99), so use $new_abs_price not $new_price - match_orders($query_id, $outcome, $new_abs_price); + match_orders($query_id, $outcome, $new_abs_price, $bridge); -- Success: Buy order price modified atomically -- - Old order deleted, new order placed with preserved timestamp @@ -1941,6 +2077,13 @@ CREATE OR REPLACE ACTION change_ask( $new_price INT, $new_amount INT8 ) PUBLIC { + -- ========================================================================== + -- SECTION 0: GET MARKET BRIDGE + -- ========================================================================== + + -- Get market bridge (will ERROR if market doesn't exist) + $bridge TEXT := get_market_bridge($query_id); + -- ========================================================================== -- SECTION 1: VALIDATE CALLER -- ========================================================================== @@ -2115,7 +2258,7 @@ CREATE OR REPLACE ACTION change_ask( -- ========================================================================== -- Try to match new order immediately - match_orders($query_id, $outcome, $new_price); + match_orders($query_id, $outcome, $new_price, $bridge); -- Success: Sell order price modified atomically -- - Old order deleted, new order placed with preserved timestamp diff --git a/tests/streams/order_book/market_creation_test.go b/tests/streams/order_book/market_creation_test.go index 86c17a38b..32995ac15 100644 --- a/tests/streams/order_book/market_creation_test.go +++ b/tests/streams/order_book/market_creation_test.go @@ -378,7 +378,7 @@ func callCreateMarket(ctx context.Context, platform *kwilTesting.Platform, signe platform.DB, "", "create_market", - []any{queryHash, settleTime, maxSpread, minOrderSize}, + []any{testExtensionName, queryHash, settleTime, maxSpread, minOrderSize}, resultFn, ) if err != nil { diff --git a/tests/streams/order_book/queries_test.go b/tests/streams/order_book/queries_test.go index 55ab4aa80..0913fd071 100644 --- a/tests/streams/order_book/queries_test.go +++ b/tests/streams/order_book/queries_test.go @@ -877,7 +877,7 @@ func createTestMarketQueries(t *testing.T, ctx context.Context, platform *kwilTe var queryID int64 err := callActionQueries(ctx, platform, signer, "create_market", - []any{queryHash[:], settleTime, int64(5), int64(20)}, + []any{testExtensionName, queryHash[:], settleTime, int64(5), int64(20)}, func(row *common.Row) error { queryID = row.Values[0].(int64) return nil diff --git a/tests/streams/order_book/settlement_payout_test.go b/tests/streams/order_book/settlement_payout_test.go index 42993eca5..5e147d532 100644 --- a/tests/streams/order_book/settlement_payout_test.go +++ b/tests/streams/order_book/settlement_payout_test.go @@ -181,7 +181,7 @@ func testWinnerReceives98PercentPayout(t *testing.T) func(context.Context, *kwil engineCtx = helper.NewEngineContext() engineCtx.TxContext.BlockContext.Timestamp = 50 createMarketRes, err := platform.Engine.Call(engineCtx, platform.DB, "", "create_market", - []any{attestationHash, settleTime, maxSpread, minOrderSize}, + []any{testExtensionName, attestationHash, settleTime, maxSpread, minOrderSize}, func(row *common.Row) error { queryID = int(row.Values[0].(int64)) return nil diff --git a/tests/streams/order_book/settlement_test.go b/tests/streams/order_book/settlement_test.go index 1a5f4bcf7..56d9263b4 100644 --- a/tests/streams/order_book/settlement_test.go +++ b/tests/streams/order_book/settlement_test.go @@ -175,7 +175,7 @@ func testSettleMarketHappyPath(t *testing.T) func(context.Context, *kwilTesting. engineCtx = helper.NewEngineContext() engineCtx.TxContext.BlockContext.Timestamp = 50 createRes, err := platform.Engine.Call(engineCtx, platform.DB, "", "create_market", - []any{attestationHash, settleTime, maxSpread, minOrderSize}, + []any{testExtensionName, attestationHash, settleTime, maxSpread, minOrderSize}, func(row *common.Row) error { queryID = int(row.Values[0].(int64)) return nil @@ -304,7 +304,7 @@ func testSettleMarketWithNoOutcome(t *testing.T) func(context.Context, *kwilTest engineCtx = helper.NewEngineContext() engineCtx.TxContext.BlockContext.Timestamp = 50 _, err = platform.Engine.Call(engineCtx, platform.DB, "", "create_market", - []any{attestationHash, int64(100), int64(5), int64(1)}, + []any{testExtensionName, attestationHash, int64(100), int64(5), int64(1)}, func(row *common.Row) error { queryID = int(row.Values[0].(int64)) return nil @@ -402,7 +402,7 @@ func testSettleMarketWithMultipleDatapoints(t *testing.T) func(context.Context, engineCtx = helper.NewEngineContext() engineCtx.TxContext.BlockContext.Timestamp = 50 _, err = platform.Engine.Call(engineCtx, platform.DB, "", "create_market", - []any{attestationHash, int64(100), int64(5), int64(1)}, + []any{testExtensionName, attestationHash, int64(100), int64(5), int64(1)}, func(row *common.Row) error { queryID = int(row.Values[0].(int64)) return nil @@ -515,7 +515,7 @@ func testSettleMarketAlreadySettled(t *testing.T) func(context.Context, *kwilTes engineCtx = helper.NewEngineContext() engineCtx.TxContext.BlockContext.Timestamp = 50 _, err = platform.Engine.Call(engineCtx, platform.DB, "", "create_market", - []any{attestationHash, int64(100), int64(5), int64(1)}, + []any{testExtensionName, attestationHash, int64(100), int64(5), int64(1)}, func(row *common.Row) error { queryID = int(row.Values[0].(int64)) return nil @@ -597,7 +597,7 @@ func testSettleMarketTooEarly(t *testing.T) func(context.Context, *kwilTesting.P engineCtx = helper.NewEngineContext() engineCtx.TxContext.BlockContext.Timestamp = 50 _, err = platform.Engine.Call(engineCtx, platform.DB, "", "create_market", - []any{attestationHash, int64(1000), int64(5), int64(1)}, + []any{testExtensionName, attestationHash, int64(1000), int64(5), int64(1)}, func(row *common.Row) error { queryID = int(row.Values[0].(int64)) return nil @@ -638,7 +638,7 @@ func testSettleMarketNoAttestation(t *testing.T) func(context.Context, *kwilTest engineCtx := helper.NewEngineContext() engineCtx.TxContext.BlockContext.Timestamp = 50 _, err := platform.Engine.Call(engineCtx, platform.DB, "", "create_market", - []any{fakeHash, int64(100), int64(5), int64(1)}, + []any{testExtensionName, fakeHash, int64(100), int64(5), int64(1)}, func(row *common.Row) error { queryID = int(row.Values[0].(int64)) return nil @@ -710,7 +710,7 @@ func testSettleMarketAttestationNotSigned(t *testing.T) func(context.Context, *k engineCtx = helper.NewEngineContext() engineCtx.TxContext.BlockContext.Timestamp = 50 _, err = platform.Engine.Call(engineCtx, platform.DB, "", "create_market", - []any{attestationHash, int64(100), int64(5), int64(1)}, + []any{testExtensionName, attestationHash, int64(100), int64(5), int64(1)}, func(row *common.Row) error { queryID = int(row.Values[0].(int64)) return nil @@ -800,7 +800,7 @@ func testSettleMarketValidationIntegration(t *testing.T) func(context.Context, * engineCtx = helper.NewEngineContext() engineCtx.TxContext.BlockContext.Timestamp = 50 _, err = platform.Engine.Call(engineCtx, platform.DB, "", "create_market", - []any{attestationHash, int64(100), int64(5), int64(1)}, + []any{testExtensionName, attestationHash, int64(100), int64(5), int64(1)}, func(row *common.Row) error { queryID = int(row.Values[0].(int64)) return nil @@ -895,7 +895,7 @@ func testSettleMarketBlockedByBinaryParityViolation(t *testing.T) func(context.C engineCtx = helper.NewEngineContext() engineCtx.TxContext.BlockContext.Timestamp = 50 _, err = platform.Engine.Call(engineCtx, platform.DB, "", "create_market", - []any{attestationHash, int64(100), int64(5), int64(1)}, + []any{testExtensionName, attestationHash, int64(100), int64(5), int64(1)}, func(row *common.Row) error { queryID = int(row.Values[0].(int64)) return nil @@ -1081,7 +1081,7 @@ func testSettleMarketBlockedByCollateralMismatch(t *testing.T) func(context.Cont engineCtx = helper.NewEngineContext() engineCtx.TxContext.BlockContext.Timestamp = 50 _, err = platform.Engine.Call(engineCtx, platform.DB, "", "create_market", - []any{attestationHash1, int64(100), int64(5), int64(1)}, + []any{testExtensionName, attestationHash1, int64(100), int64(5), int64(1)}, func(row *common.Row) error { queryID1 = int(row.Values[0].(int64)) return nil @@ -1091,7 +1091,7 @@ func testSettleMarketBlockedByCollateralMismatch(t *testing.T) func(context.Cont engineCtx = helper.NewEngineContext() engineCtx.TxContext.BlockContext.Timestamp = 50 _, err = platform.Engine.Call(engineCtx, platform.DB, "", "create_market", - []any{attestationHash2, int64(100), int64(5), int64(1)}, + []any{testExtensionName, attestationHash2, int64(100), int64(5), int64(1)}, func(row *common.Row) error { queryID2 = int(row.Values[0].(int64)) return nil