diff --git a/extensions/tn_utils/precompiles.go b/extensions/tn_utils/precompiles.go index 487ffadc5..91d1fa83d 100644 --- a/extensions/tn_utils/precompiles.go +++ b/extensions/tn_utils/precompiles.go @@ -2,11 +2,14 @@ package tn_utils import ( "bytes" + "crypto/sha256" "encoding/binary" "fmt" "math" "math/big" + gethAbi "github.com/ethereum/go-ethereum/accounts/abi" + gethCommon "github.com/ethereum/go-ethereum/common" "github.com/trufnetwork/kwil-db/common" "github.com/trufnetwork/kwil-db/core/types" "github.com/trufnetwork/kwil-db/extensions/precompiles" @@ -29,6 +32,7 @@ func buildPrecompile() precompiles.Precompile { canonicalToDataPointsABIMethod(), forceLastArgFalseMethod(), parseAttestationBooleanMethod(), + computeAttestationHashMethod(), }, } } @@ -573,3 +577,148 @@ func parseAttestationBooleanHandler(ctx *common.EngineContext, app *common.App, return resultFn([]any{outcome}) } + +// computeAttestationHashMethod computes the attestation-format hash from ABI-encoded query components. +// This ensures market hashes match attestation hashes, enabling automatic settlement. +func computeAttestationHashMethod() precompiles.Method { + return precompiles.Method{ + Name: "compute_attestation_hash", + AccessModifiers: []precompiles.Modifier{precompiles.VIEW, precompiles.PUBLIC}, + Parameters: []precompiles.PrecompileValue{ + precompiles.NewPrecompileValue("query_components", types.ByteaType, false), + }, + Returns: &precompiles.MethodReturn{ + IsTable: true, + Fields: []precompiles.PrecompileValue{ + precompiles.NewPrecompileValue("hash", types.ByteaType, false), + }, + }, + Handler: computeAttestationHashHandler, + } +} + +// computeAttestationHashHandler decodes ABI-encoded query components and computes +// the attestation hash using the same format as request_attestation. +func computeAttestationHashHandler(ctx *common.EngineContext, app *common.App, inputs []any, resultFn func([]any) error) error { + queryComponents, err := toByteSliceAllowNil(inputs[0]) + if err != nil { + return err + } + + if len(queryComponents) == 0 { + return fmt.Errorf("query_components cannot be empty") + } + + // Define ABI type for query components: (address, bytes32, string, bytes) + addressType, err := gethAbi.NewType("address", "", nil) + if err != nil { + return fmt.Errorf("failed to create address type: %w", err) + } + bytes32Type, err := gethAbi.NewType("bytes32", "", nil) + if err != nil { + return fmt.Errorf("failed to create bytes32 type: %w", err) + } + stringType, err := gethAbi.NewType("string", "", nil) + if err != nil { + return fmt.Errorf("failed to create string type: %w", err) + } + bytesType, err := gethAbi.NewType("bytes", "", nil) + if err != nil { + return fmt.Errorf("failed to create bytes type: %w", err) + } + + args := gethAbi.Arguments{ + {Type: addressType, Name: "data_provider"}, + {Type: bytes32Type, Name: "stream_id"}, + {Type: stringType, Name: "action_id"}, + {Type: bytesType, Name: "args"}, + } + + // Decode ABI + decoded, err := args.Unpack(queryComponents) + if err != nil { + return fmt.Errorf("failed to decode query_components (expected ABI-encoded (address,bytes32,string,bytes)): %w", err) + } + + if len(decoded) != 4 { + return fmt.Errorf("expected 4 components, got %d", len(decoded)) + } + + // Extract components + dataProvider, ok := decoded[0].(gethCommon.Address) + if !ok { + return fmt.Errorf("data_provider must be address, got %T", decoded[0]) + } + + streamID, ok := decoded[1].([32]byte) + if !ok { + return fmt.Errorf("stream_id must be bytes32, got %T", decoded[1]) + } + + actionIDStr, ok := decoded[2].(string) + if !ok { + return fmt.Errorf("action_id must be string, got %T", decoded[2]) + } + + argsBytes, ok := decoded[3].([]byte) + if !ok { + return fmt.Errorf("args must be bytes, got %T", decoded[3]) + } + + // Map action_id string to uint16 (must match attestation_actions table) + actionIDNum, err := getActionIDNumber(actionIDStr) + if err != nil { + return fmt.Errorf("invalid action_id: %w", err) + } + + // Build hash input using attestation format + // Format: version(1) + algo(1) + length_prefix(data_provider) + length_prefix(stream_id) + action_id(2) + length_prefix(args) + buffer := new(bytes.Buffer) + + // Version (1 byte) - always 0x01 + buffer.WriteByte(1) + + // Algorithm (1 byte) - always 0x00 + buffer.WriteByte(0) + + // Length-prefixed data_provider (20 bytes) + dataProviderBytes := dataProvider.Bytes() + buffer.Write(lengthPrefixBytes(dataProviderBytes)) + + // Length-prefixed stream_id (32 bytes) + buffer.Write(lengthPrefixBytes(streamID[:])) + + // Action ID as uint16 big-endian (2 bytes) + var actionIDBytes [2]byte + binary.BigEndian.PutUint16(actionIDBytes[:], actionIDNum) + buffer.Write(actionIDBytes[:]) + + // Length-prefixed args + buffer.Write(lengthPrefixBytes(argsBytes)) + + // Compute SHA256 hash + hash := sha256.Sum256(buffer.Bytes()) + + return resultFn([]any{hash[:]}) +} + +// getActionIDNumber maps action name to numeric ID (must match attestation_actions table) +func getActionIDNumber(actionName string) (uint16, error) { + actionMap := map[string]uint16{ + "get_record": 1, + "get_index": 2, + "get_change_over_time": 3, + "get_last_record": 4, + "get_first_record": 5, + // Future binary actions will be added here: + // "price_above_threshold": 6, + // "price_below_threshold": 7, + // "value_in_range": 8, + } + + id, ok := actionMap[actionName] + if !ok { + return 0, fmt.Errorf("unknown action: %s (must be one of: get_record, get_index, get_change_over_time, get_last_record, get_first_record)", actionName) + } + return id, nil +} diff --git a/extensions/tn_utils/precompiles_test.go b/extensions/tn_utils/precompiles_test.go new file mode 100644 index 000000000..01ef031ba --- /dev/null +++ b/extensions/tn_utils/precompiles_test.go @@ -0,0 +1,280 @@ +//go:build kwiltest + +package tn_utils + +import ( + "bytes" + "crypto/sha256" + "encoding/binary" + "testing" + + gethAbi "github.com/ethereum/go-ethereum/accounts/abi" + gethCommon "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" +) + +// TestComputeAttestationHash tests the compute_attestation_hash precompile +func TestComputeAttestationHash(t *testing.T) { + t.Run("ValidComponents_ProducesCorrectHash", func(t *testing.T) { + // Encode valid query components + dataProvider := "0x1111111111111111111111111111111111111111" + streamID := "stbtcusd00000000000000000000000000" // Exactly 32 chars + actionID := "get_record" + args := []byte{0x00, 0x00, 0x00, 0x20} // Empty ABI-encoded bytes + + queryComponents, err := encodeQueryComponents(dataProvider, streamID, actionID, args) + require.NoError(t, err) + + // Call the handler + var resultHash []byte + err = computeAttestationHashHandler(nil, nil, []any{queryComponents}, func(result []any) error { + resultHash = result[0].([]byte) + return nil + }) + + require.NoError(t, err) + require.Len(t, resultHash, 32, "hash should be 32 bytes") + }) + + t.Run("SameInputs_ProduceSameHash", func(t *testing.T) { + // Test determinism + dataProvider := "0x2222222222222222222222222222222222222222" + streamID := "stteststream0000000000000000000000" // Exactly 32 chars + actionID := "get_record" + args := []byte{0x01, 0x02, 0x03} + + queryComponents, err := encodeQueryComponents(dataProvider, streamID, actionID, args) + require.NoError(t, err) + + // Compute hash twice + var hash1, hash2 []byte + err = computeAttestationHashHandler(nil, nil, []any{queryComponents}, func(result []any) error { + hash1 = result[0].([]byte) + return nil + }) + require.NoError(t, err) + + err = computeAttestationHashHandler(nil, nil, []any{queryComponents}, func(result []any) error { + hash2 = result[0].([]byte) + return nil + }) + require.NoError(t, err) + + require.Equal(t, hash1, hash2, "same inputs should produce same hash") + }) + + t.Run("DifferentInputs_ProduceDifferentHashes", func(t *testing.T) { + // Test that different components produce different hashes + dataProvider := "0x3333333333333333333333333333333333333333" + streamID1 := "ststream100000000000000000000000000" // Exactly 32 chars + streamID2 := "ststream200000000000000000000000000" // Exactly 32 chars + actionID := "get_record" + args := []byte{0x00} + + components1, err := encodeQueryComponents(dataProvider, streamID1, actionID, args) + require.NoError(t, err) + + components2, err := encodeQueryComponents(dataProvider, streamID2, actionID, args) + require.NoError(t, err) + + var hash1, hash2 []byte + err = computeAttestationHashHandler(nil, nil, []any{components1}, func(result []any) error { + hash1 = result[0].([]byte) + return nil + }) + require.NoError(t, err) + + err = computeAttestationHashHandler(nil, nil, []any{components2}, func(result []any) error { + hash2 = result[0].([]byte) + return nil + }) + require.NoError(t, err) + + require.NotEqual(t, hash1, hash2, "different stream IDs should produce different hashes") + }) + + t.Run("EmptyQueryComponents_ReturnsError", func(t *testing.T) { + err := computeAttestationHashHandler(nil, nil, []any{[]byte{}}, func(result []any) error { + return nil + }) + + require.Error(t, err) + require.Contains(t, err.Error(), "query_components cannot be empty") + }) + + t.Run("InvalidABIEncoding_ReturnsError", func(t *testing.T) { + // Pass invalid ABI data + invalidABI := []byte{0xFF, 0xFF, 0xFF, 0xFF} + + err := computeAttestationHashHandler(nil, nil, []any{invalidABI}, func(result []any) error { + return nil + }) + + require.Error(t, err) + require.Contains(t, err.Error(), "failed to decode query_components") + }) + + t.Run("UnknownActionID_ReturnsError", func(t *testing.T) { + // Encode with invalid action_id + dataProvider := "0x4444444444444444444444444444444444444444" + streamID := "sttest00000000000000000000000000000" // Exactly 32 chars + invalidActionID := "unknown_action" + args := []byte{0x00} + + queryComponents, err := encodeQueryComponents(dataProvider, streamID, invalidActionID, args) + require.NoError(t, err) + + err = computeAttestationHashHandler(nil, nil, []any{queryComponents}, func(result []any) error { + return nil + }) + + require.Error(t, err) + require.Contains(t, err.Error(), "unknown action") + }) + + t.Run("KnownGoodTestVector", func(t *testing.T) { + // Use a known good test vector to ensure hash computation matches expected format + dataProvider := "0x1111111111111111111111111111111111111111" + streamID := "stbtcusd00000000000000000000000000" // Exactly 32 chars + actionID := "get_record" + args := []byte{0x00, 0x00, 0x00, 0x20} // Empty ABI-encoded bytes + + queryComponents, err := encodeQueryComponents(dataProvider, streamID, actionID, args) + require.NoError(t, err) + + // Compute hash + var computedHash []byte + err = computeAttestationHashHandler(nil, nil, []any{queryComponents}, func(result []any) error { + computedHash = result[0].([]byte) + return nil + }) + require.NoError(t, err) + + // Manually compute expected hash using attestation format + expectedHash := computeExpectedAttestationHash(t, dataProvider, streamID, actionID, args) + + require.Equal(t, expectedHash, computedHash, "computed hash should match expected attestation format") + }) + + t.Run("AllSupportedActions_ProduceValidHashes", func(t *testing.T) { + // Test all supported action IDs + supportedActions := []string{ + "get_record", + "get_index", + "get_change_over_time", + "get_last_record", + "get_first_record", + } + + dataProvider := "0x5555555555555555555555555555555555555555" + streamID := "sttest00000000000000000000000000000" // Exactly 32 chars + args := []byte{0x00} + + for _, actionID := range supportedActions { + t.Run(actionID, func(t *testing.T) { + queryComponents, err := encodeQueryComponents(dataProvider, streamID, actionID, args) + require.NoError(t, err) + + var hash []byte + err = computeAttestationHashHandler(nil, nil, []any{queryComponents}, func(result []any) error { + hash = result[0].([]byte) + return nil + }) + + require.NoError(t, err, "action %s should succeed", actionID) + require.Len(t, hash, 32, "hash should be 32 bytes for action %s", actionID) + }) + } + }) +} + +// Helper function to encode query components using ABI +func encodeQueryComponents(dataProvider, streamID, actionID string, args []byte) ([]byte, error) { + addressType, err := gethAbi.NewType("address", "", nil) + if err != nil { + return nil, err + } + bytes32Type, err := gethAbi.NewType("bytes32", "", nil) + if err != nil { + return nil, err + } + stringType, err := gethAbi.NewType("string", "", nil) + if err != nil { + return nil, err + } + bytesType, err := gethAbi.NewType("bytes", "", nil) + if err != nil { + return nil, err + } + + abiArgs := gethAbi.Arguments{ + {Type: addressType, Name: "data_provider"}, + {Type: bytes32Type, Name: "stream_id"}, + {Type: stringType, Name: "action_id"}, + {Type: bytesType, Name: "args"}, + } + + // Convert data provider to address + dpAddr := gethCommon.HexToAddress(dataProvider) + + // Convert stream ID to bytes32 (pad with zeros on the right) + var streamIDBytes [32]byte + copy(streamIDBytes[:], []byte(streamID)) + + // Pack the ABI + encoded, err := abiArgs.Pack(dpAddr, streamIDBytes, actionID, args) + if err != nil { + return nil, err + } + + return encoded, nil +} + +// Helper function to compute expected hash using attestation format +// This replicates the logic in computeAttestationHashHandler for verification +func computeExpectedAttestationHash(t *testing.T, dataProvider, streamID, actionID string, args []byte) []byte { + // Parse data provider address + dpAddr := gethCommon.HexToAddress(dataProvider) + + // Convert stream ID to bytes32 + var streamIDBytes [32]byte + copy(streamIDBytes[:], []byte(streamID)) + + // Map action ID to number + actionMap := map[string]uint16{ + "get_record": 1, + "get_index": 2, + "get_change_over_time": 3, + "get_last_record": 4, + "get_first_record": 5, + } + actionIDNum, ok := actionMap[actionID] + require.True(t, ok, "unknown action_id: %s", actionID) + + // Build hash input + buffer := new(bytes.Buffer) + + // Version (1 byte) + buffer.WriteByte(1) + + // Algorithm (1 byte) + buffer.WriteByte(0) + + // Length-prefixed data_provider (20 bytes) + buffer.Write(lengthPrefixBytes(dpAddr.Bytes())) + + // Length-prefixed stream_id (32 bytes) + buffer.Write(lengthPrefixBytes(streamIDBytes[:])) + + // Action ID as uint16 big-endian (2 bytes) + var actionIDBytes [2]byte + binary.BigEndian.PutUint16(actionIDBytes[:], actionIDNum) + buffer.Write(actionIDBytes[:]) + + // Length-prefixed args + buffer.Write(lengthPrefixBytes(args)) + + // Compute SHA256 hash + hash := sha256.Sum256(buffer.Bytes()) + return hash[:] +} diff --git a/internal/migrations/030-order-book-schema.sql b/internal/migrations/030-order-book-schema.sql index b5206d377..8238c19d2 100644 --- a/internal/migrations/030-order-book-schema.sql +++ b/internal/migrations/030-order-book-schema.sql @@ -20,6 +20,7 @@ CREATE TABLE IF NOT EXISTS ob_queries ( id INT PRIMARY KEY, hash BYTEA NOT NULL UNIQUE, + query_components BYTEA NOT NULL, settle_time INT8 NOT NULL, settled BOOLEAN DEFAULT false NOT NULL, winning_outcome BOOLEAN, @@ -91,6 +92,10 @@ CREATE INDEX IF NOT EXISTS idx_ob_queries_settle_time CREATE INDEX IF NOT EXISTS idx_ob_queries_settled ON ob_queries(settled); +-- Query components index for potential future queries on components +CREATE INDEX IF NOT EXISTS idx_ob_queries_components + ON ob_queries(query_components); + -- ============================================================================= -- Transaction method registration -- ============================================================================= diff --git a/internal/migrations/032-order-book-actions.sql b/internal/migrations/032-order-book-actions.sql index 674b4f02b..9fca6b147 100644 --- a/internal/migrations/032-order-book-actions.sql +++ b/internal/migrations/032-order-book-actions.sql @@ -67,11 +67,11 @@ CREATE OR REPLACE ACTION get_market_bridge($query_id INT) PRIVATE RETURNS (bridg -- ============================================================================= /** * Creates a market with specified settlement parameters. The market is - * identified by a unique hash derived from the attestation query parameters. + * identified by a unique hash derived from the query components. * * 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 + * - $query_components: ABI-encoded (address data_provider, bytes32 stream_id, string action_id, bytes args) * - $settle_time: Unix timestamp when market can be settled (must be in future) * - $max_spread: Maximum spread for LP rewards (1-50 cents) * - $min_order_size: Minimum order size for LP rewards (must be positive) @@ -84,7 +84,7 @@ CREATE OR REPLACE ACTION get_market_bridge($query_id INT) PRIVATE RETURNS (bridg */ CREATE OR REPLACE ACTION create_market( $bridge TEXT, - $query_hash BYTEA, + $query_components BYTEA, $settle_time INT8, $max_spread INT, $min_order_size INT8 @@ -96,14 +96,30 @@ CREATE OR REPLACE ACTION create_market( -- 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) - if $query_hash IS NULL { - ERROR('query_hash is required'); + -- Validate query components (must be ABI-encoded) + if $query_components IS NULL OR length(encode($query_components, 'hex')) = 0 { + ERROR('query_components is required (ABI-encoded (address,bytes32,string,bytes))'); + } + + -- Compute hash from query components using attestation format + -- This ensures market hash matches attestation hash for automatic settlement + $query_hash BYTEA; + for $row in tn_utils.compute_attestation_hash($query_components) { + $query_hash := $row.hash; } - if length(encode($query_hash, 'hex')) != 64 { - ERROR('query_hash must be exactly 32 bytes (SHA256)'); + + -- Validate hash is exactly 32 bytes + if length(encode($query_hash, 'hex')) != 64 { -- 32 bytes = 64 hex chars + ERROR('Invalid query_components: computed hash must be 32 bytes'); + } + + -- Check for duplicate market (hash must be unique) + $existing_id INT; + for $row in SELECT id FROM ob_queries WHERE hash = $query_hash { + $existing_id := $row.id; + } + if $existing_id IS NOT NULL { + ERROR('Market already exists with this query hash (query_id: ' || $existing_id::TEXT || ')'); } -- Validate settlement time (must be in the future) @@ -126,22 +142,17 @@ CREATE OR REPLACE ACTION create_market( -- FEE COLLECTION -- ========================================================================== -- Fee: 2 TRUF (2 * 10^18 wei) - -- TODO: Adjust fee based on spam prevention needs + -- Market creation fee is ALWAYS paid in TRUF (hoodi_tt on testnet) + -- regardless of which bridge the market uses for collateral $market_creation_fee NUMERIC(78, 0) := '2000000000000000000'::NUMERIC(78, 0); - -- Check caller has sufficient balance (bridge-specific) - -- TODO: Review when USDC bridge available - $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)); - } + -- Check caller has sufficient TRUF balance + -- IMPORTANT: Fee is collected from hoodi_tt (TRUF), not from market's bridge + -- TODO: Replace hoodi_tt with actual TRUF bridge on mainnet when available! hoodi_tt is testnet only. + $caller_balance NUMERIC(78, 0) := COALESCE(hoodi_tt.balance(@caller), 0::NUMERIC(78, 0)); if $caller_balance < $market_creation_fee { - ERROR('Insufficient balance for market creation fee. Required: 2 TRUF'); + ERROR('Insufficient TRUF balance for market creation fee. Required: 2 TRUF (hoodi_tt balance)'); } -- Verify leader address is available for fee transfer @@ -149,17 +160,11 @@ CREATE OR REPLACE ACTION create_market( ERROR('Leader address not available for fee transfer'); } - -- Transfer fee to leader (bridge-specific) + -- Transfer fee to leader from TRUF bridge (hoodi_tt) -- 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; - 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); - } + hoodi_tt.transfer($leader_hex, $market_creation_fee); -- ========================================================================== -- CREATE MARKET @@ -181,6 +186,7 @@ CREATE OR REPLACE ACTION create_market( INSERT INTO ob_queries ( id, hash, + query_components, settle_time, max_spread, min_order_size, @@ -191,6 +197,7 @@ CREATE OR REPLACE ACTION create_market( SELECT COALESCE(MAX(id), 0) + 1, $query_hash, + $query_components, $settle_time, $max_spread, $min_order_size, @@ -232,6 +239,8 @@ CREATE OR REPLACE ACTION create_market( CREATE OR REPLACE ACTION get_market_info($query_id INT) PUBLIC VIEW RETURNS ( hash BYTEA, + query_components BYTEA, + bridge TEXT, settle_time INT8, settled BOOLEAN, winning_outcome BOOLEAN, @@ -246,12 +255,12 @@ PUBLIC VIEW RETURNS ( } for $market in - SELECT hash, settle_time, settled, winning_outcome, settled_at, + SELECT hash, query_components, bridge, settle_time, settled, winning_outcome, settled_at, max_spread, min_order_size, created_at, creator FROM ob_queries WHERE id = $query_id { - RETURN $market.hash, $market.settle_time, $market.settled, + RETURN $market.hash, $market.query_components, $market.bridge, $market.settle_time, $market.settled, $market.winning_outcome, $market.settled_at, $market.max_spread, $market.min_order_size, $market.created_at, $market.creator; } diff --git a/tests/streams/order_book/buy_order_test.go b/tests/streams/order_book/buy_order_test.go index 07815ba6c..ced61ad27 100644 --- a/tests/streams/order_book/buy_order_test.go +++ b/tests/streams/order_book/buy_order_test.go @@ -4,7 +4,7 @@ package order_book import ( "context" - "crypto/sha256" + "encoding/hex" "fmt" "math/big" "strings" @@ -14,10 +14,12 @@ import ( "github.com/stretchr/testify/require" "github.com/trufnetwork/kwil-db/common" coreauth "github.com/trufnetwork/kwil-db/core/crypto/auth" - erc20bridge "github.com/trufnetwork/kwil-db/node/exts/erc20-bridge/erc20" + "github.com/trufnetwork/kwil-db/core/types" kwilTesting "github.com/trufnetwork/kwil-db/testing" "github.com/trufnetwork/node/internal/migrations" testutils "github.com/trufnetwork/node/tests/streams/utils" + testerc20 "github.com/trufnetwork/node/tests/streams/utils/erc20" + erc20bridge "github.com/trufnetwork/kwil-db/node/exts/erc20-bridge/erc20" "github.com/trufnetwork/sdk-go/core/util" ) @@ -71,27 +73,162 @@ func testBuyOrderSuccessful(t *testing.T) func(ctx context.Context, platform *kw return func(ctx context.Context, platform *kwilTesting.Platform) error { userAddr := util.Unsafe_NewEthereumAddressFromString("0x1111111111111111111111111111111111111111") - // Setup: Initialize ERC20 extension singleton (needed for lock/unlock operations) - err := erc20bridge.ForTestingInitializeExtension(ctx, platform) - require.NoError(t, err, "failed to initialize ERC20 extension") - - // Setup: Give user 100 TRUF - err = giveBalance(ctx, platform, userAddr.Address(), "100000000000000000000") + // Setup: Give user 100 TRUF (injected to both TRUF and USDC bridges) + err := giveBalance(ctx, platform, userAddr.Address(), "100000000000000000000") require.NoError(t, err, "failed to give balance") + // CRITICAL: Load DB instances into singleton cache + // giveBalance() syncs instances to DATABASE via ForTestingForceSyncInstance + // But balance()/lock()/unlock() actions check the in-memory SINGLETON cache + // ForTestingInitializeExtension() loads DB instances into singleton + err = erc20bridge.ForTestingInitializeExtension(ctx, platform) + require.NoError(t, err, "failed to initialize ERC20 extension singleton") + + // DEBUG: Check balances AFTER initialization + trufBal, err := getBalance(ctx, platform, userAddr.Address()) + require.NoError(t, err) + usdcBal, err := getUSDCBalance(ctx, platform, userAddr.Address()) + require.NoError(t, err) + t.Logf("DEBUG: After init - TRUF: %s (expected 100e18), USDC: %s (expected 100e18)", trufBal.String(), usdcBal.String()) + + // DEBUG: Try to get balance directly from DB to see if it's there + balanceStr, err := testerc20.GetUserBalance(ctx, platform, testUSDCExtensionName, userAddr.Address()) + t.Logf("DEBUG: USDC balance from GetUserBalance: %s (err: %v)", balanceStr, err) + + // DEBUG: Check what extension instances exist + app := &common.App{DB: platform.DB, Engine: platform.Engine} + var instanceCount int + _ = app.Engine.ExecuteWithoutEngineCtx(ctx, app.DB, `{kwil_erc20_meta}SELECT COUNT(*) FROM reward_instances`, nil, func(r *common.Row) error { + instanceCount = int(r.Values[0].(int64)) + return nil + }) + t.Logf("DEBUG: Total ERC20 instances in DB: %d", instanceCount) + + // DEBUG: List all instances with sync status + _ = app.Engine.ExecuteWithoutEngineCtx(ctx, app.DB, `{kwil_erc20_meta} + SELECT escrow_address, synced, erc20_address, erc20_decimals + FROM reward_instances`, nil, func(r *common.Row) error { + escrow := r.Values[0].([]byte) + synced := r.Values[1].(bool) + var erc20Addr []byte + if r.Values[2] != nil { + erc20Addr = r.Values[2].([]byte) + } + var decimals *int64 + if r.Values[3] != nil { + d := r.Values[3].(int64) + decimals = &d + } + t.Logf("DEBUG: Instance escrow=0x%x synced=%v erc20=0x%x decimals=%v", + escrow, synced, erc20Addr, decimals) + return nil + }) + + // DEBUG: Check user balances table (correct table is 'balances', column is 'address') + var balanceCount int + _ = app.Engine.ExecuteWithoutEngineCtx(ctx, app.DB, `{kwil_erc20_meta}SELECT COUNT(*) FROM balances WHERE address = $addr`, + map[string]any{"$addr": []byte(userAddr.Bytes())}, func(r *common.Row) error { + balanceCount = int(r.Values[0].(int64)) + return nil + }) + t.Logf("DEBUG: User has %d balance records in balances table", balanceCount) + + // DEBUG: Show actual balance values for this user + _ = app.Engine.ExecuteWithoutEngineCtx(ctx, app.DB, `{kwil_erc20_meta} + SELECT b.balance, r.escrow_address, r.erc20_address + FROM balances b + JOIN reward_instances r ON b.reward_id = r.id + WHERE b.address = $addr`, + map[string]any{"$addr": []byte(userAddr.Bytes())}, func(r *common.Row) error { + balance := r.Values[0].(*types.Decimal).String() + escrow := "0x" + hex.EncodeToString(r.Values[1].([]byte)) + erc20 := "0x" + hex.EncodeToString(r.Values[2].([]byte)) + t.Logf("DEBUG: Balance=%s escrow=%s erc20=%s", balance, escrow, erc20) + return nil + }) + + // DEBUG: Check if ordered-sync namespace exists + var namespaceExists bool + err = app.Engine.ExecuteWithoutEngineCtx(ctx, app.DB, `SELECT 1`, nil, func(r *common.Row) error { + namespaceExists = true + return nil + }) + t.Logf("DEBUG: Can execute queries: %v (err: %v)", namespaceExists, err) + + // DEBUG: Check if ordered-sync pending_data were stored + var pendingCount int + err = app.Engine.ExecuteWithoutEngineCtx(ctx, app.DB, `{kwil_ordered_sync}SELECT COUNT(*) FROM pending_data`, nil, func(r *common.Row) error { + pendingCount = int(r.Values[0].(int64)) + return nil + }) + t.Logf("DEBUG: Total ordered-sync pending_data: %d (err: %v)", pendingCount, err) + + // DEBUG: Check topics table + var topicCount int + err = app.Engine.ExecuteWithoutEngineCtx(ctx, app.DB, `{kwil_ordered_sync}SELECT COUNT(*) FROM topics`, nil, func(r *common.Row) error { + topicCount = int(r.Values[0].(int64)) + return nil + }) + t.Logf("DEBUG: Total ordered-sync topics: %d (err: %v)", topicCount, err) + + // DEBUG: List pending_data points with topic info + _ = app.Engine.ExecuteWithoutEngineCtx(ctx, app.DB, `{kwil_ordered_sync} + SELECT p.point, p.previous_point, t.name, t.last_processed_point + FROM pending_data p + JOIN topics t ON p.topic_id = t.id + ORDER BY p.point`, nil, func(r *common.Row) error { + point := r.Values[0].(int64) + var prevPoint *int64 + if r.Values[1] != nil { + p := r.Values[1].(int64) + prevPoint = &p + } + topicName := r.Values[2].(string) + var lastProcessed *int64 + if r.Values[3] != nil { + lp := r.Values[3].(int64) + lastProcessed = &lp + } + var lpStr string + if lastProcessed != nil { + lpStr = fmt.Sprintf("%d", *lastProcessed) + } else { + lpStr = "nil" + } + var prevStr string + if prevPoint != nil { + prevStr = fmt.Sprintf("%d", *prevPoint) + } else { + prevStr = "nil" + } + t.Logf("DEBUG: pending point=%d prev=%s topic=%s last_processed=%s MATCH=%v", + point, prevStr, topicName, lpStr, (prevPoint == nil && lastProcessed == nil) || (prevPoint != nil && lastProcessed != nil && *prevPoint == *lastProcessed)) + return nil + }) + + // DEBUG: Check finalized status + _ = app.Engine.ExecuteWithoutEngineCtx(ctx, app.DB, `{kwil_ordered_sync}SELECT topic, point, finalized FROM logs ORDER BY point`, nil, func(r *common.Row) error { + topic := r.Values[0].(string) + point := r.Values[1].(int64) + finalized := r.Values[2].(bool) + t.Logf("DEBUG: Log topic=%s point=%d finalized=%v", topic, point, finalized) + return nil + }) + // Create a market - queryHash := sha256.Sum256([]byte("test_market_buy_1")) + queryComponents, err := encodeQueryComponentsForTests(userAddr.Address(), "sttest00000000000000000000000001", "get_record", []byte{0x01}) + require.NoError(t, err) settleTime := time.Now().Add(24 * time.Hour).Unix() var marketID int64 - err = callCreateMarket(ctx, platform, &userAddr, queryHash[:], settleTime, 5, 20, func(row *common.Row) error { + err = callCreateMarket(ctx, platform, &userAddr, queryComponents, settleTime, 5, 20, func(row *common.Row) error { marketID = row.Values[0].(int64) return nil }) require.NoError(t, err, "create_market should succeed") - // Get balance before order - balanceBefore, err := getBalance(ctx, platform, userAddr.Address()) + // Get USDC balance before order (collateral is locked from USDC bridge) + balanceBefore, err := getUSDCBalance(ctx, platform, userAddr.Address()) require.NoError(t, err) // Place buy order: 10 YES shares at $0.56 @@ -99,8 +236,8 @@ func testBuyOrderSuccessful(t *testing.T) func(ctx context.Context, platform *kw err = callPlaceBuyOrder(ctx, platform, &userAddr, int(marketID), true, 56, 10) require.NoError(t, err, "place_buy_order should succeed") - // Verify balance decreased by 5.6 TRUF - balanceAfter, err := getBalance(ctx, platform, userAddr.Address()) + // Verify USDC balance decreased by 5.6 TRUF + balanceAfter, err := getUSDCBalance(ctx, platform, userAddr.Address()) require.NoError(t, err) // Expected: balanceBefore - 5.6 (collateral locked for order) @@ -129,20 +266,21 @@ func testBuyOrderInsufficientBalance(t *testing.T) func(ctx context.Context, pla return func(ctx context.Context, platform *kwilTesting.Platform) error { userAddr := util.Unsafe_NewEthereumAddressFromString("0x2222222222222222222222222222222222222222") - // Setup: Initialize ERC20 extension - err := erc20bridge.ForTestingInitializeExtension(ctx, platform) + // Give user only 3 TRUF (enough for market creation but not order) + err := giveBalance(ctx, platform, userAddr.Address(), "3000000000000000000") require.NoError(t, err) - // Give user only 3 TRUF (enough for market creation but not order) - err = giveBalance(ctx, platform, userAddr.Address(), "3000000000000000000") + // Initialize ERC20 extension after balance injection + err = erc20bridge.ForTestingInitializeExtension(ctx, platform) require.NoError(t, err) // Create market - queryHash := sha256.Sum256([]byte("test_market_buy_2")) + queryComponents, err := encodeQueryComponentsForTests(userAddr.Address(), "sttest00000000000000000000000002", "get_record", []byte{0x01}) + require.NoError(t, err) settleTime := time.Now().Add(24 * time.Hour).Unix() var marketID int64 - err = callCreateMarket(ctx, platform, &userAddr, queryHash[:], settleTime, 5, 20, func(row *common.Row) error { + err = callCreateMarket(ctx, platform, &userAddr, queryComponents, settleTime, 5, 20, func(row *common.Row) error { marketID = row.Values[0].(int64) return nil }) @@ -162,10 +300,11 @@ func testBuyOrderMarketNotFound(t *testing.T) func(ctx context.Context, platform return func(ctx context.Context, platform *kwilTesting.Platform) error { userAddr := util.Unsafe_NewEthereumAddressFromString("0x3333333333333333333333333333333333333333") - err := erc20bridge.ForTestingInitializeExtension(ctx, platform) + err := giveBalance(ctx, platform, userAddr.Address(), "100000000000000000000") require.NoError(t, err) - err = giveBalance(ctx, platform, userAddr.Address(), "100000000000000000000") + // Initialize ERC20 extension after balance injection + err = erc20bridge.ForTestingInitializeExtension(ctx, platform) require.NoError(t, err) // Try to place order on non-existent market (ID 99999) @@ -182,18 +321,20 @@ func testBuyOrderInvalidPrice(t *testing.T) func(ctx context.Context, platform * return func(ctx context.Context, platform *kwilTesting.Platform) error { userAddr := util.Unsafe_NewEthereumAddressFromString("0x4444444444444444444444444444444444444444") - err := erc20bridge.ForTestingInitializeExtension(ctx, platform) + err := giveBalance(ctx, platform, userAddr.Address(), "100000000000000000000") require.NoError(t, err) - err = giveBalance(ctx, platform, userAddr.Address(), "100000000000000000000") + // Initialize ERC20 extension after balance injection + err = erc20bridge.ForTestingInitializeExtension(ctx, platform) require.NoError(t, err) // Create market - queryHash := sha256.Sum256([]byte("test_market_buy_3")) + queryComponents, err := encodeQueryComponentsForTests(userAddr.Address(), "sttest00000000000000000000000003", "get_record", []byte{0x01}) + require.NoError(t, err) settleTime := time.Now().Add(24 * time.Hour).Unix() var marketID int64 - err = callCreateMarket(ctx, platform, &userAddr, queryHash[:], settleTime, 5, 20, func(row *common.Row) error { + err = callCreateMarket(ctx, platform, &userAddr, queryComponents, settleTime, 5, 20, func(row *common.Row) error { marketID = row.Values[0].(int64) return nil }) @@ -218,18 +359,20 @@ func testBuyOrderInvalidAmount(t *testing.T) func(ctx context.Context, platform return func(ctx context.Context, platform *kwilTesting.Platform) error { userAddr := util.Unsafe_NewEthereumAddressFromString("0x5555555555555555555555555555555555555555") - err := erc20bridge.ForTestingInitializeExtension(ctx, platform) + err := giveBalance(ctx, platform, userAddr.Address(), "100000000000000000000") require.NoError(t, err) - err = giveBalance(ctx, platform, userAddr.Address(), "100000000000000000000") + // Initialize ERC20 extension after balance injection + err = erc20bridge.ForTestingInitializeExtension(ctx, platform) require.NoError(t, err) // Create market - queryHash := sha256.Sum256([]byte("test_market_buy_4")) + queryComponents, err := encodeQueryComponentsForTests(userAddr.Address(), "sttest00000000000000000000000004", "get_record", []byte{0x01}) + require.NoError(t, err) settleTime := time.Now().Add(24 * time.Hour).Unix() var marketID int64 - err = callCreateMarket(ctx, platform, &userAddr, queryHash[:], settleTime, 5, 20, func(row *common.Row) error { + err = callCreateMarket(ctx, platform, &userAddr, queryComponents, settleTime, 5, 20, func(row *common.Row) error { marketID = row.Values[0].(int64) return nil }) @@ -259,18 +402,20 @@ func testBuyOrderMultipleOrdersDifferentPrices(t *testing.T) func(ctx context.Co return func(ctx context.Context, platform *kwilTesting.Platform) error { userAddr := util.Unsafe_NewEthereumAddressFromString("0x6666666666666666666666666666666666666666") - err := erc20bridge.ForTestingInitializeExtension(ctx, platform) + err := giveBalance(ctx, platform, userAddr.Address(), "100000000000000000000") require.NoError(t, err) - err = giveBalance(ctx, platform, userAddr.Address(), "100000000000000000000") + // Initialize ERC20 extension after balance injection + err = erc20bridge.ForTestingInitializeExtension(ctx, platform) require.NoError(t, err) // Create market - queryHash := sha256.Sum256([]byte("test_market_buy_5")) + queryComponents, err := encodeQueryComponentsForTests(userAddr.Address(), "sttest00000000000000000000000005", "get_record", []byte{0x01}) + require.NoError(t, err) settleTime := time.Now().Add(24 * time.Hour).Unix() var marketID int64 - err = callCreateMarket(ctx, platform, &userAddr, queryHash[:], settleTime, 5, 20, func(row *common.Row) error { + err = callCreateMarket(ctx, platform, &userAddr, queryComponents, settleTime, 5, 20, func(row *common.Row) error { marketID = row.Values[0].(int64) return nil }) @@ -311,18 +456,20 @@ func testBuyOrderMultipleOrdersSamePrice(t *testing.T) func(ctx context.Context, return func(ctx context.Context, platform *kwilTesting.Platform) error { userAddr := util.Unsafe_NewEthereumAddressFromString("0x7777777777777777777777777777777777777777") - err := erc20bridge.ForTestingInitializeExtension(ctx, platform) + err := giveBalance(ctx, platform, userAddr.Address(), "100000000000000000000") require.NoError(t, err) - err = giveBalance(ctx, platform, userAddr.Address(), "100000000000000000000") + // Initialize ERC20 extension after balance injection + err = erc20bridge.ForTestingInitializeExtension(ctx, platform) require.NoError(t, err) // Create market - queryHash := sha256.Sum256([]byte("test_market_buy_6")) + queryComponents, err := encodeQueryComponentsForTests(userAddr.Address(), "sttest00000000000000000000000006", "get_record", []byte{0x01}) + require.NoError(t, err) settleTime := time.Now().Add(24 * time.Hour).Unix() var marketID int64 - err = callCreateMarket(ctx, platform, &userAddr, queryHash[:], settleTime, 5, 20, func(row *common.Row) error { + err = callCreateMarket(ctx, platform, &userAddr, queryComponents, settleTime, 5, 20, func(row *common.Row) error { marketID = row.Values[0].(int64) return nil }) @@ -354,26 +501,28 @@ func testBuyOrderBalanceChanges(t *testing.T) func(ctx context.Context, platform return func(ctx context.Context, platform *kwilTesting.Platform) error { userAddr := util.Unsafe_NewEthereumAddressFromString("0x8888888888888888888888888888888888888888") - err := erc20bridge.ForTestingInitializeExtension(ctx, platform) + initialBalance := toWei("50") + err := giveBalance(ctx, platform, userAddr.Address(), initialBalance.String()) require.NoError(t, err) - initialBalance := toWei("50") - err = giveBalance(ctx, platform, userAddr.Address(), initialBalance.String()) + // Initialize ERC20 extension after balance injection + err = erc20bridge.ForTestingInitializeExtension(ctx, platform) require.NoError(t, err) // Create market (costs 2 TRUF) - queryHash := sha256.Sum256([]byte("test_market_buy_7")) + queryComponents, err := encodeQueryComponentsForTests(userAddr.Address(), "sttest00000000000000000000000007", "get_record", []byte{0x01}) + require.NoError(t, err) settleTime := time.Now().Add(24 * time.Hour).Unix() var marketID int64 - err = callCreateMarket(ctx, platform, &userAddr, queryHash[:], settleTime, 5, 20, func(row *common.Row) error { + err = callCreateMarket(ctx, platform, &userAddr, queryComponents, settleTime, 5, 20, func(row *common.Row) error { marketID = row.Values[0].(int64) return nil }) require.NoError(t, err) - // Get balance after market creation - balanceAfterMarket, err := getBalance(ctx, platform, userAddr.Address()) + // Get USDC balance after market creation (collateral comes from USDC bridge) + balanceAfterMarket, err := getUSDCBalance(ctx, platform, userAddr.Address()) require.NoError(t, err) // Place order: 20 shares at $0.75 (requires 15 TRUF) @@ -381,8 +530,8 @@ func testBuyOrderBalanceChanges(t *testing.T) func(ctx context.Context, platform err = callPlaceBuyOrder(ctx, platform, &userAddr, int(marketID), false, 75, 20) require.NoError(t, err) - // Verify balance decreased by 15 TRUF - balanceFinal, err := getBalance(ctx, platform, userAddr.Address()) + // Verify USDC balance decreased by 15 TRUF + balanceFinal, err := getUSDCBalance(ctx, platform, userAddr.Address()) require.NoError(t, err) expectedFinal := new(big.Int).Sub(balanceAfterMarket, toWei("15")) @@ -470,3 +619,20 @@ func getPositions(ctx context.Context, platform *kwilTesting.Platform, marketID return positions, nil } + +// getUSDCBalance returns the user's USDC bridge balance (hoodi_tt2) +// Used for checking collateral locked in buy/sell orders +func getUSDCBalance(ctx context.Context, platform *kwilTesting.Platform, wallet string) (*big.Int, error) { + // Import testerc20 is needed + balanceStr, err := testerc20.GetUserBalance(ctx, platform, testUSDCExtensionName, wallet) + if err != nil { + return nil, err + } + + balance := new(big.Int) + if _, ok := balance.SetString(balanceStr, 10); !ok { + return nil, fmt.Errorf("invalid balance string: %s", balanceStr) + } + + return balance, nil +} diff --git a/tests/streams/order_book/cancel_order_test.go b/tests/streams/order_book/cancel_order_test.go index 077a5d5b7..7549b9795 100644 --- a/tests/streams/order_book/cancel_order_test.go +++ b/tests/streams/order_book/cancel_order_test.go @@ -4,7 +4,6 @@ package order_book import ( "context" - "crypto/sha256" "fmt" "math/big" "testing" @@ -54,11 +53,12 @@ func testCancelBuyOrderSuccess(t *testing.T) func(ctx context.Context, platform require.NoError(t, err, "failed to give balance") // Create market - queryHash := sha256.Sum256([]byte("test_market_cancel_buy_1")) + queryComponents, err := encodeQueryComponentsForTests(userAddr.Address(), "sttest00000000000000000000000027", "get_record", []byte{0x01}) + require.NoError(t, err) settleTime := time.Now().Add(24 * time.Hour).Unix() var marketID int64 - err = callCreateMarket(ctx, platform, &userAddr, queryHash[:], settleTime, 5, 20, func(row *common.Row) error { + err = callCreateMarket(ctx, platform, &userAddr, queryComponents, settleTime, 5, 20, func(row *common.Row) error { marketID = row.Values[0].(int64) return nil }) @@ -101,11 +101,12 @@ func testCancelSellOrderSuccess(t *testing.T) func(ctx context.Context, platform require.NoError(t, err) // Create market - queryHash := sha256.Sum256([]byte("test_market_cancel_sell_1")) + queryComponents, err := encodeQueryComponentsForTests(userAddr.Address(), "sttest00000000000000000000000028", "get_record", []byte{0x01}) + require.NoError(t, err) settleTime := time.Now().Add(24 * time.Hour).Unix() var marketID int64 - err = callCreateMarket(ctx, platform, &userAddr, queryHash[:], settleTime, 5, 20, func(row *common.Row) error { + err = callCreateMarket(ctx, platform, &userAddr, queryComponents, settleTime, 5, 20, func(row *common.Row) error { marketID = row.Values[0].(int64) return nil }) @@ -171,11 +172,12 @@ func testCancelBuyOrderMultiplePrices(t *testing.T) func(ctx context.Context, pl require.NoError(t, err) // Create market - queryHash := sha256.Sum256([]byte("test_market_cancel_multi")) + queryComponents, err := encodeQueryComponentsForTests(userAddr.Address(), "sttest00000000000000000000000029", "get_record", []byte{0x01}) + require.NoError(t, err) settleTime := time.Now().Add(24 * time.Hour).Unix() var marketID int64 - err = callCreateMarket(ctx, platform, &userAddr, queryHash[:], settleTime, 5, 20, func(row *common.Row) error { + err = callCreateMarket(ctx, platform, &userAddr, queryComponents, settleTime, 5, 20, func(row *common.Row) error { marketID = row.Values[0].(int64) return nil }) @@ -240,11 +242,12 @@ func testCancelOrderNotFound(t *testing.T) func(ctx context.Context, platform *k require.NoError(t, err) // Create market - queryHash := sha256.Sum256([]byte("test_market_cancel_not_found")) + queryComponents, err := encodeQueryComponentsForTests(userAddr.Address(), "sttest00000000000000000000000030", "get_record", []byte{0x01}) + require.NoError(t, err) settleTime := time.Now().Add(24 * time.Hour).Unix() var marketID int64 - err = callCreateMarket(ctx, platform, &userAddr, queryHash[:], settleTime, 5, 20, func(row *common.Row) error { + err = callCreateMarket(ctx, platform, &userAddr, queryComponents, settleTime, 5, 20, func(row *common.Row) error { marketID = row.Values[0].(int64) return nil }) @@ -276,11 +279,12 @@ func testCancelOrderMarketSettled(t *testing.T) func(ctx context.Context, platfo require.NoError(t, err) // Create market with settle time in the past - queryHash := sha256.Sum256([]byte("test_market_cancel_settled")) + queryComponents, err := encodeQueryComponentsForTests(userAddr.Address(), "sttest00000000000000000000000031", "get_record", []byte{0x01}) + require.NoError(t, err) settleTime := time.Now().Add(24 * time.Hour).Unix() var marketID int64 - err = callCreateMarket(ctx, platform, &userAddr, queryHash[:], settleTime, 5, 20, func(row *common.Row) error { + err = callCreateMarket(ctx, platform, &userAddr, queryComponents, settleTime, 5, 20, func(row *common.Row) error { marketID = row.Values[0].(int64) return nil }) @@ -334,11 +338,12 @@ func testCancelOrderInvalidPrice(t *testing.T) func(ctx context.Context, platfor require.NoError(t, err) // Create market - queryHash := sha256.Sum256([]byte("test_market_cancel_invalid_price")) + queryComponents, err := encodeQueryComponentsForTests(userAddr.Address(), "sttest00000000000000000000000032", "get_record", []byte{0x01}) + require.NoError(t, err) settleTime := time.Now().Add(24 * time.Hour).Unix() var marketID int64 - err = callCreateMarket(ctx, platform, &userAddr, queryHash[:], settleTime, 5, 20, func(row *common.Row) error { + err = callCreateMarket(ctx, platform, &userAddr, queryComponents, settleTime, 5, 20, func(row *common.Row) error { marketID = row.Values[0].(int64) return nil }) @@ -369,11 +374,12 @@ func testCancelOrderHoldings(t *testing.T) func(ctx context.Context, platform *k require.NoError(t, err) // Create market - queryHash := sha256.Sum256([]byte("test_market_cancel_holdings")) + queryComponents, err := encodeQueryComponentsForTests(userAddr.Address(), "sttest00000000000000000000000033", "get_record", []byte{0x01}) + require.NoError(t, err) settleTime := time.Now().Add(24 * time.Hour).Unix() var marketID int64 - err = callCreateMarket(ctx, platform, &userAddr, queryHash[:], settleTime, 5, 20, func(row *common.Row) error { + err = callCreateMarket(ctx, platform, &userAddr, queryComponents, settleTime, 5, 20, func(row *common.Row) error { marketID = row.Values[0].(int64) return nil }) @@ -401,11 +407,12 @@ func testCancelBuyOrderVerifyRefund(t *testing.T) func(ctx context.Context, plat require.NoError(t, err) // Create market - queryHash := sha256.Sum256([]byte("test_market_cancel_verify_refund")) + queryComponents, err := encodeQueryComponentsForTests(userAddr.Address(), "sttest00000000000000000000000034", "get_record", []byte{0x01}) + require.NoError(t, err) settleTime := time.Now().Add(24 * time.Hour).Unix() var marketID int64 - err = callCreateMarket(ctx, platform, &userAddr, queryHash[:], settleTime, 5, 20, func(row *common.Row) error { + err = callCreateMarket(ctx, platform, &userAddr, queryComponents, settleTime, 5, 20, func(row *common.Row) error { marketID = row.Values[0].(int64) return nil }) @@ -458,11 +465,12 @@ func testCancelSellOrderVerifyShares(t *testing.T) func(ctx context.Context, pla require.NoError(t, err) // Create market - queryHash := sha256.Sum256([]byte("test_market_cancel_verify_shares")) + queryComponents, err := encodeQueryComponentsForTests(userAddr.Address(), "sttest00000000000000000000000035", "get_record", []byte{0x01}) + require.NoError(t, err) settleTime := time.Now().Add(24 * time.Hour).Unix() var marketID int64 - err = callCreateMarket(ctx, platform, &userAddr, queryHash[:], settleTime, 5, 20, func(row *common.Row) error { + err = callCreateMarket(ctx, platform, &userAddr, queryComponents, settleTime, 5, 20, func(row *common.Row) error { marketID = row.Values[0].(int64) return nil }) diff --git a/tests/streams/order_book/change_order_test.go b/tests/streams/order_book/change_order_test.go index 0039ed864..b0dae04b1 100644 --- a/tests/streams/order_book/change_order_test.go +++ b/tests/streams/order_book/change_order_test.go @@ -4,7 +4,6 @@ package order_book import ( "context" - "crypto/sha256" "encoding/hex" "testing" "time" @@ -183,11 +182,12 @@ func testChangeBidSuccess(t *testing.T) func(ctx context.Context, platform *kwil require.NoError(t, err) // Create market - queryHash := sha256.Sum256([]byte("test_change_bid_success")) + queryComponents, err := encodeQueryComponentsForTests(userAddr.Address(), "stchangetest_change_bid_success0", "get_record", []byte{0x01}) + require.NoError(t, err) settleTime := time.Now().Add(24 * time.Hour).Unix() var marketID int64 - err = callCreateMarket(ctx, platform, &userAddr, queryHash[:], settleTime, 5, 20, func(row *common.Row) error { + err = callCreateMarket(ctx, platform, &userAddr, queryComponents, settleTime, 5, 20, func(row *common.Row) error { marketID = row.Values[0].(int64) return nil }) @@ -229,11 +229,12 @@ func testChangeBidHigherPrice(t *testing.T) func(ctx context.Context, platform * err = giveBalance(ctx, platform, userAddr.Address(), "300000000000000000000") require.NoError(t, err) - queryHash := sha256.Sum256([]byte("test_change_bid_higher")) + queryComponents, err := encodeQueryComponentsForTests(userAddr.Address(), "stchangetest_change_bid_higher00", "get_record", []byte{0x01}) + require.NoError(t, err) settleTime := time.Now().Add(24 * time.Hour).Unix() var marketID int64 - err = callCreateMarket(ctx, platform, &userAddr, queryHash[:], settleTime, 5, 20, func(row *common.Row) error { + err = callCreateMarket(ctx, platform, &userAddr, queryComponents, settleTime, 5, 20, func(row *common.Row) error { marketID = row.Values[0].(int64) return nil }) @@ -266,11 +267,12 @@ func testChangeBidInsufficientBalance(t *testing.T) func(ctx context.Context, pl err = giveBalance(ctx, platform, userAddr.Address(), "102000000000000000000") // 102 TRUF require.NoError(t, err) - queryHash := sha256.Sum256([]byte("test_change_bid_insufficient")) + queryComponents, err := encodeQueryComponentsForTests(userAddr.Address(), "stchangetest_change_bid_insuffic", "get_record", []byte{0x01}) + require.NoError(t, err) settleTime := time.Now().Add(24 * time.Hour).Unix() var marketID int64 - err = callCreateMarket(ctx, platform, &userAddr, queryHash[:], settleTime, 5, 20, func(row *common.Row) error { + err = callCreateMarket(ctx, platform, &userAddr, queryComponents, settleTime, 5, 20, func(row *common.Row) error { marketID = row.Values[0].(int64) return nil }) @@ -308,11 +310,12 @@ func testChangeBidTimestampPreservation(t *testing.T) func(ctx context.Context, err = giveBalance(ctx, platform, userAddr.Address(), "200000000000000000000") require.NoError(t, err) - queryHash := sha256.Sum256([]byte("test_change_bid_timestamp")) + queryComponents, err := encodeQueryComponentsForTests(userAddr.Address(), "stchangetest_change_bid_timestam", "get_record", []byte{0x01}) + require.NoError(t, err) settleTime := time.Now().Add(24 * time.Hour).Unix() var marketID int64 - err = callCreateMarket(ctx, platform, &userAddr, queryHash[:], settleTime, 5, 20, func(row *common.Row) error { + err = callCreateMarket(ctx, platform, &userAddr, queryComponents, settleTime, 5, 20, func(row *common.Row) error { marketID = row.Values[0].(int64) return nil }) @@ -351,11 +354,12 @@ func testChangeBidInvalidPrices(t *testing.T) func(ctx context.Context, platform err = giveBalance(ctx, platform, userAddr.Address(), "200000000000000000000") require.NoError(t, err) - queryHash := sha256.Sum256([]byte("test_change_bid_invalid")) + queryComponents, err := encodeQueryComponentsForTests(userAddr.Address(), "stchangetest_change_bid_invalid0", "get_record", []byte{0x01}) + require.NoError(t, err) settleTime := time.Now().Add(24 * time.Hour).Unix() var marketID int64 - err = callCreateMarket(ctx, platform, &userAddr, queryHash[:], settleTime, 5, 20, func(row *common.Row) error { + err = callCreateMarket(ctx, platform, &userAddr, queryComponents, settleTime, 5, 20, func(row *common.Row) error { marketID = row.Values[0].(int64) return nil }) @@ -396,11 +400,12 @@ func testChangeAskSuccess(t *testing.T) func(ctx context.Context, platform *kwil err = giveBalance(ctx, platform, userAddr.Address(), "200000000000000000000") require.NoError(t, err) - queryHash := sha256.Sum256([]byte("test_change_ask_success")) + queryComponents, err := encodeQueryComponentsForTests(userAddr.Address(), "stchangetest_change_ask_success0", "get_record", []byte{0x01}) + require.NoError(t, err) settleTime := time.Now().Add(24 * time.Hour).Unix() var marketID int64 - err = callCreateMarket(ctx, platform, &userAddr, queryHash[:], settleTime, 5, 20, func(row *common.Row) error { + err = callCreateMarket(ctx, platform, &userAddr, queryComponents, settleTime, 5, 20, func(row *common.Row) error { marketID = row.Values[0].(int64) return nil }) @@ -442,11 +447,12 @@ func testChangeAskHigherPrice(t *testing.T) func(ctx context.Context, platform * err = giveBalance(ctx, platform, userAddr.Address(), "200000000000000000000") require.NoError(t, err) - queryHash := sha256.Sum256([]byte("test_change_ask_higher")) + queryComponents, err := encodeQueryComponentsForTests(userAddr.Address(), "stchangetest_change_ask_higher00", "get_record", []byte{0x01}) + require.NoError(t, err) settleTime := time.Now().Add(24 * time.Hour).Unix() var marketID int64 - err = callCreateMarket(ctx, platform, &userAddr, queryHash[:], settleTime, 5, 20, func(row *common.Row) error { + err = callCreateMarket(ctx, platform, &userAddr, queryComponents, settleTime, 5, 20, func(row *common.Row) error { marketID = row.Values[0].(int64) return nil }) @@ -478,11 +484,12 @@ func testChangeAskTimestampPreservation(t *testing.T) func(ctx context.Context, err = giveBalance(ctx, platform, userAddr.Address(), "200000000000000000000") require.NoError(t, err) - queryHash := sha256.Sum256([]byte("test_change_ask_timestamp")) + queryComponents, err := encodeQueryComponentsForTests(userAddr.Address(), "stchangetest_change_ask_timestam", "get_record", []byte{0x01}) + require.NoError(t, err) settleTime := time.Now().Add(24 * time.Hour).Unix() var marketID int64 - err = callCreateMarket(ctx, platform, &userAddr, queryHash[:], settleTime, 5, 20, func(row *common.Row) error { + err = callCreateMarket(ctx, platform, &userAddr, queryComponents, settleTime, 5, 20, func(row *common.Row) error { marketID = row.Values[0].(int64) return nil }) @@ -521,11 +528,12 @@ func testChangeAskInvalidPrices(t *testing.T) func(ctx context.Context, platform err = giveBalance(ctx, platform, userAddr.Address(), "200000000000000000000") require.NoError(t, err) - queryHash := sha256.Sum256([]byte("test_change_ask_invalid")) + queryComponents, err := encodeQueryComponentsForTests(userAddr.Address(), "stchangetest_change_ask_invalid0", "get_record", []byte{0x01}) + require.NoError(t, err) settleTime := time.Now().Add(24 * time.Hour).Unix() var marketID int64 - err = callCreateMarket(ctx, platform, &userAddr, queryHash[:], settleTime, 5, 20, func(row *common.Row) error { + err = callCreateMarket(ctx, platform, &userAddr, queryComponents, settleTime, 5, 20, func(row *common.Row) error { marketID = row.Values[0].(int64) return nil }) @@ -562,11 +570,12 @@ func testChangeAskUpsizing(t *testing.T) func(ctx context.Context, platform *kwi err = giveBalance(ctx, platform, userAddr.Address(), "300000000000000000000") require.NoError(t, err) - queryHash := sha256.Sum256([]byte("test_change_ask_upsize")) + queryComponents, err := encodeQueryComponentsForTests(userAddr.Address(), "stchangetest_change_ask_upsize00", "get_record", []byte{0x01}) + require.NoError(t, err) settleTime := time.Now().Add(24 * time.Hour).Unix() var marketID int64 - err = callCreateMarket(ctx, platform, &userAddr, queryHash[:], settleTime, 5, 20, func(row *common.Row) error { + err = callCreateMarket(ctx, platform, &userAddr, queryComponents, settleTime, 5, 20, func(row *common.Row) error { marketID = row.Values[0].(int64) return nil }) @@ -637,11 +646,12 @@ func testChangeAskDownsizing(t *testing.T) func(ctx context.Context, platform *k err = giveBalance(ctx, platform, userAddr.Address(), "200000000000000000000") require.NoError(t, err) - queryHash := sha256.Sum256([]byte("test_change_ask_downsize")) + queryComponents, err := encodeQueryComponentsForTests(userAddr.Address(), "stchangetest_change_ask_downsize", "get_record", []byte{0x01}) + require.NoError(t, err) settleTime := time.Now().Add(24 * time.Hour).Unix() var marketID int64 - err = callCreateMarket(ctx, platform, &userAddr, queryHash[:], settleTime, 5, 20, func(row *common.Row) error { + err = callCreateMarket(ctx, platform, &userAddr, queryComponents, settleTime, 5, 20, func(row *common.Row) error { marketID = row.Values[0].(int64) return nil }) diff --git a/tests/streams/order_book/create_market_with_components_test.go b/tests/streams/order_book/create_market_with_components_test.go new file mode 100644 index 000000000..37cd15b9c --- /dev/null +++ b/tests/streams/order_book/create_market_with_components_test.go @@ -0,0 +1,528 @@ +//go:build kwiltest + +package order_book + +import ( + "context" + "fmt" + "testing" + "time" + + gethAbi "github.com/ethereum/go-ethereum/accounts/abi" + gethCommon "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" + "github.com/trufnetwork/kwil-db/common" + "github.com/trufnetwork/kwil-db/core/crypto" + coreauth "github.com/trufnetwork/kwil-db/core/crypto/auth" + erc20bridge "github.com/trufnetwork/kwil-db/node/exts/erc20-bridge/erc20" + kwilTesting "github.com/trufnetwork/kwil-db/testing" + "github.com/trufnetwork/node/internal/migrations" + testutils "github.com/trufnetwork/node/tests/streams/utils" + testerc20 "github.com/trufnetwork/node/tests/streams/utils/erc20" + "github.com/trufnetwork/sdk-go/core/util" +) + +// Test constants for bridges +const ( + // TRUF bridge (hoodi_tt) - market creation fee is ALWAYS collected from here + testTRUFChainComponents = "hoodi" + testTRUFEscrowComponents = "0x878d6aaeb6e746033f50b8dc268d54b4631554e7" + testTRUFERC20Components = "0x263ce78fef26600e4e428cebc91c2a52484b4fbf" + testTRUFExtensionNameComponents = "hoodi_tt" + + // USDC bridge (hoodi_tt2) - market collateral for trading + testUSDCChainComponents = "hoodi" + testUSDCEscrowComponents = "0x80D9B3b6941367917816d36748C88B303f7F1415" + testUSDCERC20Components = "0x1591DeAa21710E0BA6CC1b15F49620C9F65B2dEd" + testUSDCExtensionNameComponents = "hoodi_tt2" +) + +// Balance tracking for chained deposits (prevents multi-user balance issues) +// Note: Uses separate counter from market_creation_test.go to avoid conflicts +var ( + balancePointCounterComponents int64 = 200 // Start at 200 to avoid conflicts + lastBalancePointComponents *int64 +) + +// TestCreateMarketWithQueryComponents tests market creation with query_components +func TestCreateMarketWithQueryComponents(t *testing.T) { + testutils.RunSchemaTest(t, kwilTesting.SchemaTest{ + Name: "ORDER_BOOK_CreateMarketWithComponents", + SeedStatements: migrations.GetSeedScriptStatements(), + FunctionTests: []kwilTesting.TestFunc{ + testCreateMarketWithValidComponents(t), + testCreateMarketStoresHashAndComponents(t), + testCreateMarketRejectsDuplicateHash(t), + testCreateMarketRejectsEmptyComponents(t), + testCreateMarketRejectsMalformedABI(t), + testGetMarketInfoReturnsComponentsAndBridge(t), + testCreateMarketWithDifferentBridges(t), + }, + }, testutils.GetTestOptionsWithCache()) +} + +// testCreateMarketWithValidComponents tests successful market creation with query_components +func testCreateMarketWithValidComponents(t *testing.T) func(ctx context.Context, platform *kwilTesting.Platform) error { + return func(ctx context.Context, platform *kwilTesting.Platform) error { + // Reset balance point tracker for this test + lastBalancePointComponents = nil + userAddr := util.Unsafe_NewEthereumAddressFromString("0x1111111111111111111111111111111111111111") + + // Initialize ERC20 extension + err := erc20bridge.ForTestingInitializeExtension(ctx, platform) + require.NoError(t, err) + + // Give user balance + err = giveBalanceChainedComponents(ctx, platform, userAddr.Address(), "100000000000000000000") // 100 TRUF + require.NoError(t, err) + + // Encode query components + dataProvider := userAddr.Address() + streamID := "stbtcusd000000000000000000000000" // Exactly 32 chars + actionID := "get_record" + + queryComponents, err := encodeQueryComponentsForTests(dataProvider, streamID, actionID, nil) + require.NoError(t, err) + + // Create market + settleTime := time.Now().Add(1 * time.Hour).Unix() + var queryID int + err = callCreateMarketWithComponents(ctx, platform, &userAddr, queryComponents, settleTime, int64(5), int64(100), func(row *common.Row) error { + queryID = int(row.Values[0].(int64)) + return nil + }) + require.NoError(t, err, "create_market should succeed with valid query_components") + require.Greater(t, queryID, 0, "query_id should be positive") + + return nil + } +} + +// testCreateMarketStoresHashAndComponents verifies that both hash and components are stored +func testCreateMarketStoresHashAndComponents(t *testing.T) func(ctx context.Context, platform *kwilTesting.Platform) error { + return func(ctx context.Context, platform *kwilTesting.Platform) error { + // Reset balance point tracker for this test + lastBalancePointComponents = nil + + userAddr := util.Unsafe_NewEthereumAddressFromString("0x2222222222222222222222222222222222222222") + + // Initialize and give balance + err := erc20bridge.ForTestingInitializeExtension(ctx, platform) + require.NoError(t, err) + err = giveBalanceChainedComponents(ctx, platform, userAddr.Address(), "100000000000000000000") + require.NoError(t, err) + + // Create query components + dataProvider := userAddr.Address() + streamID := "ststorage00000000000000000000000000" // Exactly 32 chars + actionID := "get_record" + argsBytes := []byte{0x01, 0x02, 0x03} + + queryComponents, err := encodeQueryComponentsABI(dataProvider, streamID, actionID, argsBytes) + require.NoError(t, err) + + // Create market + settleTime := time.Now().Add(2 * time.Hour).Unix() + var queryID int + err = callCreateMarketWithComponents(ctx, platform, &userAddr, queryComponents, settleTime, int64(5), int64(100), func(row *common.Row) error { + queryID = int(row.Values[0].(int64)) + return nil + }) + require.NoError(t, err) + + // Verify market info returns both hash and components + var storedHash []byte + var storedComponents []byte + var storedBridge string + err = callGetMarketInfoWithComponents(ctx, platform, &userAddr, queryID, func(row *common.Row) error { + storedHash = row.Values[0].([]byte) + storedComponents = row.Values[1].([]byte) + storedBridge = row.Values[2].(string) + return nil + }) + require.NoError(t, err) + + // Verify hash is 32 bytes + require.Len(t, storedHash, 32, "hash should be 32 bytes") + + // Verify components match what we sent + require.Equal(t, queryComponents, storedComponents, "stored components should match input") + + // Verify bridge is correct + require.Equal(t, testUSDCExtensionNameComponents, storedBridge, "bridge should match") + + return nil + } +} + +// testCreateMarketRejectsDuplicateHash tests that duplicate hash is rejected +func testCreateMarketRejectsDuplicateHash(t *testing.T) func(ctx context.Context, platform *kwilTesting.Platform) error { + return func(ctx context.Context, platform *kwilTesting.Platform) error { + // Reset balance point tracker for this test + lastBalancePointComponents = nil + + userAddr := util.Unsafe_NewEthereumAddressFromString("0x3333333333333333333333333333333333333333") + + // Initialize and give balance + err := erc20bridge.ForTestingInitializeExtension(ctx, platform) + require.NoError(t, err) + err = giveBalanceChainedComponents(ctx, platform, userAddr.Address(), "100000000000000000000") + require.NoError(t, err) + + // Create query components + dataProvider := userAddr.Address() + streamID := "stduplicate000000000000000000000000" // Exactly 32 chars + actionID := "get_record" + argsBytes := []byte{0xAA, 0xBB} + + queryComponents, err := encodeQueryComponentsABI(dataProvider, streamID, actionID, argsBytes) + require.NoError(t, err) + + // Create first market (should succeed) + settleTime := time.Now().Add(1 * time.Hour).Unix() + err = callCreateMarketWithComponents(ctx, platform, &userAddr, queryComponents, settleTime, int64(5), int64(100), nil) + require.NoError(t, err, "first market creation should succeed") + + // Try to create duplicate (should fail) + settleTime2 := time.Now().Add(2 * time.Hour).Unix() + err = callCreateMarketWithComponents(ctx, platform, &userAddr, queryComponents, settleTime2, int64(5), int64(100), nil) + require.Error(t, err, "duplicate hash should be rejected") + require.Contains(t, err.Error(), "already exists", "error should mention duplicate") + + return nil + } +} + +// testCreateMarketRejectsEmptyComponents tests that empty query_components is rejected +func testCreateMarketRejectsEmptyComponents(t *testing.T) func(ctx context.Context, platform *kwilTesting.Platform) error { + return func(ctx context.Context, platform *kwilTesting.Platform) error { + // Reset balance point tracker for this test + lastBalancePointComponents = nil + + userAddr := util.Unsafe_NewEthereumAddressFromString("0x4444444444444444444444444444444444444444") + + // Initialize and give balance + err := erc20bridge.ForTestingInitializeExtension(ctx, platform) + require.NoError(t, err) + err = giveBalanceChainedComponents(ctx, platform, userAddr.Address(), "100000000000000000000") + require.NoError(t, err) + + // Try to create market with empty components + emptyComponents := []byte{} + settleTime := time.Now().Add(1 * time.Hour).Unix() + err = callCreateMarketWithComponents(ctx, platform, &userAddr, emptyComponents, settleTime, int64(5), int64(100), nil) + require.Error(t, err, "empty query_components should be rejected") + require.Contains(t, err.Error(), "query_components is required", "error should mention query_components") + + return nil + } +} + +// testCreateMarketRejectsMalformedABI tests that malformed ABI encoding is rejected +func testCreateMarketRejectsMalformedABI(t *testing.T) func(ctx context.Context, platform *kwilTesting.Platform) error { + return func(ctx context.Context, platform *kwilTesting.Platform) error { + // Reset balance point tracker for this test + lastBalancePointComponents = nil + + userAddr := util.Unsafe_NewEthereumAddressFromString("0x5555555555555555555555555555555555555555") + + // Initialize and give balance + err := erc20bridge.ForTestingInitializeExtension(ctx, platform) + require.NoError(t, err) + err = giveBalanceChainedComponents(ctx, platform, userAddr.Address(), "100000000000000000000") + require.NoError(t, err) + + // Create malformed ABI data + malformedABI := []byte{0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00} + + // Try to create market with malformed ABI + settleTime := time.Now().Add(1 * time.Hour).Unix() + err = callCreateMarketWithComponents(ctx, platform, &userAddr, malformedABI, settleTime, int64(5), int64(100), nil) + require.Error(t, err, "malformed ABI should be rejected") + // Error should mention decoding or invalid components + t.Logf("Malformed ABI error: %v", err) + + return nil + } +} + +// testGetMarketInfoReturnsComponentsAndBridge tests get_market_info returns query_components and bridge +func testGetMarketInfoReturnsComponentsAndBridge(t *testing.T) func(ctx context.Context, platform *kwilTesting.Platform) error { + return func(ctx context.Context, platform *kwilTesting.Platform) error { + // Reset balance point tracker for this test + lastBalancePointComponents = nil + + userAddr := util.Unsafe_NewEthereumAddressFromString("0x6666666666666666666666666666666666666666") + + // Initialize and give balance + err := erc20bridge.ForTestingInitializeExtension(ctx, platform) + require.NoError(t, err) + err = giveBalanceChainedComponents(ctx, platform, userAddr.Address(), "100000000000000000000") + require.NoError(t, err) + + // Create query components + dataProvider := userAddr.Address() + streamID := "stgetinfo00000000000000000000000000" // Exactly 32 chars + actionID := "get_index" + argsBytes := []byte{0x11, 0x22, 0x33, 0x44} + + queryComponents, err := encodeQueryComponentsABI(dataProvider, streamID, actionID, argsBytes) + require.NoError(t, err) + + // Create market + settleTime := time.Now().Add(3 * time.Hour).Unix() + var queryID int + err = callCreateMarketWithComponents(ctx, platform, &userAddr, queryComponents, settleTime, int64(10), int64(50), func(row *common.Row) error { + queryID = int(row.Values[0].(int64)) + return nil + }) + require.NoError(t, err) + + // Get market info and verify all fields + var hash []byte + var components []byte + var bridge string + var storedSettleTime int64 + var maxSpread int64 + var minOrderSize int64 + err = callGetMarketInfoWithComponents(ctx, platform, &userAddr, queryID, func(row *common.Row) error { + hash = row.Values[0].([]byte) + components = row.Values[1].([]byte) + bridge = row.Values[2].(string) + storedSettleTime = row.Values[3].(int64) + // row.Values[4] = settled (bool) + // row.Values[5] = winning_outcome + // row.Values[6] = settled_at + maxSpread = row.Values[7].(int64) + minOrderSize = row.Values[8].(int64) + return nil + }) + require.NoError(t, err) + + // Verify all fields + require.Len(t, hash, 32, "hash should be 32 bytes") + require.Equal(t, queryComponents, components, "components should match") + require.Equal(t, testUSDCExtensionNameComponents, bridge, "bridge should match") + require.Equal(t, settleTime, storedSettleTime, "settle_time should match") + require.Equal(t, int64(10), maxSpread, "max_spread should match") + require.Equal(t, int64(50), minOrderSize, "min_order_size should match") + + return nil + } +} + +// testCreateMarketWithDifferentBridges tests market creation with different bridge parameters +func testCreateMarketWithDifferentBridges(t *testing.T) func(ctx context.Context, platform *kwilTesting.Platform) error { + return func(ctx context.Context, platform *kwilTesting.Platform) error { + // Reset balance point tracker for this test + lastBalancePointComponents = nil + + userAddr := util.Unsafe_NewEthereumAddressFromString("0x7777777777777777777777777777777777777777") + + // Initialize and give balance + err := erc20bridge.ForTestingInitializeExtension(ctx, platform) + require.NoError(t, err) + err = giveBalanceChainedComponents(ctx, platform, userAddr.Address(), "100000000000000000000") + require.NoError(t, err) + + // Create query components + dataProvider := userAddr.Address() + streamID := "stbridgetest000000000000000000000000" // Exactly 32 chars + actionID := "get_record" + argsBytes := []byte{0x00} + + queryComponents, err := encodeQueryComponentsABI(dataProvider, streamID, actionID, argsBytes) + require.NoError(t, err) + + // Create market with testUSDCExtensionNameComponents bridge + settleTime := time.Now().Add(1 * time.Hour).Unix() + var queryID int + err = callCreateMarketWithComponents(ctx, platform, &userAddr, queryComponents, settleTime, int64(5), int64(100), func(row *common.Row) error { + queryID = int(row.Values[0].(int64)) + return nil + }) + require.NoError(t, err) + + // Verify bridge is stored correctly + var storedBridge string + err = callGetMarketInfoWithComponents(ctx, platform, &userAddr, queryID, func(row *common.Row) error { + storedBridge = row.Values[2].(string) // bridge is 3rd field + return nil + }) + require.NoError(t, err) + require.Equal(t, testUSDCExtensionNameComponents, storedBridge, "bridge should be stored correctly") + + return nil + } +} + +// ===== HELPER FUNCTIONS ===== + +// giveBalanceChainedComponents injects balance to BOTH bridges with proper chaining: +// 1. hoodi_tt (TRUF) for market creation fees +// 2. hoodi_tt2 (USDC) for market collateral/trading +func giveBalanceChainedComponents(ctx context.Context, platform *kwilTesting.Platform, wallet string, amountStr string) error { + // Inject TRUF balance for market creation fees (chained) + balancePointCounterComponents++ + currentPoint := balancePointCounterComponents + + err := testerc20.InjectERC20Transfer( + ctx, + platform, + testTRUFChainComponents, + testTRUFEscrowComponents, + testTRUFERC20Components, + wallet, + wallet, + amountStr, + currentPoint, + lastBalancePointComponents, // Chain to previous + ) + if err != nil { + return fmt.Errorf("failed to inject TRUF balance: %w", err) + } + + p := currentPoint + lastBalancePointComponents = &p + + // Inject USDC balance for market collateral (chained) + balancePointCounterComponents++ + currentPoint = balancePointCounterComponents + + err = testerc20.InjectERC20Transfer( + ctx, + platform, + testUSDCChainComponents, + testUSDCEscrowComponents, + testUSDCERC20Components, + wallet, + wallet, + amountStr, + currentPoint, + lastBalancePointComponents, // Chain to TRUF injection + ) + if err != nil { + return fmt.Errorf("failed to inject USDC balance: %w", err) + } + + p = currentPoint + lastBalancePointComponents = &p + + return nil +} + +func encodeQueryComponentsABI(dataProvider, streamID, actionID string, args []byte) ([]byte, error) { + addressType, err := gethAbi.NewType("address", "", nil) + if err != nil { + return nil, fmt.Errorf("failed to create address type: %w", err) + } + bytes32Type, err := gethAbi.NewType("bytes32", "", nil) + if err != nil { + return nil, fmt.Errorf("failed to create bytes32 type: %w", err) + } + stringType, err := gethAbi.NewType("string", "", nil) + if err != nil { + return nil, fmt.Errorf("failed to create string type: %w", err) + } + bytesType, err := gethAbi.NewType("bytes", "", nil) + if err != nil { + return nil, fmt.Errorf("failed to create bytes type: %w", err) + } + + abiArgs := gethAbi.Arguments{ + {Type: addressType, Name: "data_provider"}, + {Type: bytes32Type, Name: "stream_id"}, + {Type: stringType, Name: "action_id"}, + {Type: bytesType, Name: "args"}, + } + + // Convert data provider to address + dpAddr := gethCommon.HexToAddress(dataProvider) + + // Convert stream ID to bytes32 (pad with zeros on the right) + var streamIDBytes [32]byte + copy(streamIDBytes[:], []byte(streamID)) + + // Pack the ABI + encoded, err := abiArgs.Pack(dpAddr, streamIDBytes, actionID, args) + if err != nil { + return nil, fmt.Errorf("failed to pack ABI: %w", err) + } + + return encoded, nil +} + +func callCreateMarketWithComponents(ctx context.Context, platform *kwilTesting.Platform, signer *util.EthereumAddress, queryComponents []byte, settleTime int64, maxSpread int64, minOrderSize int64, resultFn func(*common.Row) error) error { + // Generate leader key for fee transfers + _, pubGeneric, err := crypto.GenerateSecp256k1Key(nil) + if err != nil { + return err + } + pub := pubGeneric.(*crypto.Secp256k1PublicKey) + + tx := &common.TxContext{ + Ctx: ctx, + BlockContext: &common.BlockContext{ + Height: 1, + Timestamp: time.Now().Unix(), + Proposer: pub, + }, + Signer: signer.Bytes(), + Caller: signer.Address(), + TxID: platform.Txid(), + Authenticator: coreauth.EthPersonalSignAuth, + } + engineCtx := &common.EngineContext{TxContext: tx} + + res, err := platform.Engine.Call( + engineCtx, + platform.DB, + "", + "create_market", + []any{testUSDCExtensionNameComponents, queryComponents, settleTime, maxSpread, minOrderSize}, + resultFn, + ) + // Log NOTICE messages for debugging + if res != nil && len(res.Logs) > 0 { + fmt.Printf("=== create_market logs ===\n") + for i, log := range res.Logs { + fmt.Printf(" [%d] %s\n", i, log) + } + fmt.Printf("==========================\n") + } + if err != nil { + return err + } + if res != nil && res.Error != nil { + return res.Error + } + return nil +} + +func callGetMarketInfoWithComponents(ctx context.Context, platform *kwilTesting.Platform, signer *util.EthereumAddress, queryID int, resultFn func(*common.Row) error) error { + tx := &common.TxContext{ + Ctx: ctx, + BlockContext: &common.BlockContext{Height: 1}, + Signer: signer.Bytes(), + Caller: signer.Address(), + TxID: platform.Txid(), + Authenticator: coreauth.EthPersonalSignAuth, + } + engineCtx := &common.EngineContext{TxContext: tx} + + res, err := platform.Engine.Call( + engineCtx, + platform.DB, + "", + "get_market_info", + []any{queryID}, + resultFn, + ) + if err != nil { + return err + } + if res != nil && res.Error != nil { + return res.Error + } + return nil +} diff --git a/tests/streams/order_book/fee_distribution_audit_test.go b/tests/streams/order_book/fee_distribution_audit_test.go index a73ec5d7e..068bda0ee 100644 --- a/tests/streams/order_book/fee_distribution_audit_test.go +++ b/tests/streams/order_book/fee_distribution_audit_test.go @@ -4,7 +4,6 @@ package order_book import ( "context" - "crypto/sha256" "fmt" "math/big" "strings" @@ -62,11 +61,12 @@ func testAuditRecordCreation(t *testing.T) func(context.Context, *kwilTesting.Pl require.NoError(t, err) // Create market - queryHash := sha256.Sum256([]byte("test_audit_record_creation")) + queryComponents, err := encodeQueryComponentsForTests(user1.Address(), "sttest00000000000000000000000062", "get_record", []byte{0x01}) + require.NoError(t, err) settleTime := time.Now().Add(24 * time.Hour).Unix() var marketID int64 - err = callCreateMarket(ctx, platform, &user1, queryHash[:], settleTime, 5, 20, func(row *common.Row) error { + err = callCreateMarket(ctx, platform, &user1, queryComponents, settleTime, 5, 20, func(row *common.Row) error { marketID = row.Values[0].(int64) return nil }) @@ -184,11 +184,12 @@ func testAuditMultiBlock(t *testing.T) func(context.Context, *kwilTesting.Platfo err = giveBalanceChained(ctx, platform, user2.Address(), "500000000000000000000") require.NoError(t, err) - queryHash := sha256.Sum256([]byte("test_audit_multiblock")) + queryComponents, err := encodeQueryComponentsForTests(user1.Address(), "sttest00000000000000000000000063", "get_record", []byte{0x01}) + require.NoError(t, err) settleTime := time.Now().Add(24 * time.Hour).Unix() var marketID int64 - err = callCreateMarket(ctx, platform, &user1, queryHash[:], settleTime, 5, 20, func(row *common.Row) error { + err = callCreateMarket(ctx, platform, &user1, queryComponents, settleTime, 5, 20, func(row *common.Row) error { marketID = row.Values[0].(int64) return nil }) @@ -253,11 +254,12 @@ func testAuditNoLPs(t *testing.T) func(context.Context, *kwilTesting.Platform) e err = giveBalanceChained(ctx, platform, user1.Address(), "500000000000000000000") require.NoError(t, err) - queryHash := sha256.Sum256([]byte("test_audit_no_lps")) + queryComponents, err := encodeQueryComponentsForTests(user1.Address(), "sttest00000000000000000000000064", "get_record", []byte{0x01}) + require.NoError(t, err) settleTime := time.Now().Add(24 * time.Hour).Unix() var marketID int64 - err = callCreateMarket(ctx, platform, &user1, queryHash[:], settleTime, 5, 20, func(row *common.Row) error { + err = callCreateMarket(ctx, platform, &user1, queryComponents, settleTime, 5, 20, func(row *common.Row) error { marketID = row.Values[0].(int64) return nil }) @@ -307,11 +309,12 @@ func testAuditZeroFees(t *testing.T) func(context.Context, *kwilTesting.Platform err = giveBalanceChained(ctx, platform, user1.Address(), "500000000000000000000") require.NoError(t, err) - queryHash := sha256.Sum256([]byte("test_audit_zero_fees")) + queryComponents, err := encodeQueryComponentsForTests(user1.Address(), "sttest00000000000000000000000065", "get_record", []byte{0x01}) + require.NoError(t, err) settleTime := time.Now().Add(24 * time.Hour).Unix() var marketID int64 - err = callCreateMarket(ctx, platform, &user1, queryHash[:], settleTime, 5, 20, func(row *common.Row) error { + err = callCreateMarket(ctx, platform, &user1, queryComponents, settleTime, 5, 20, func(row *common.Row) error { marketID = row.Values[0].(int64) return nil }) @@ -364,11 +367,12 @@ func testAuditDataIntegrity(t *testing.T) func(context.Context, *kwilTesting.Pla err = giveBalanceChained(ctx, platform, user2.Address(), "500000000000000000000") require.NoError(t, err) - queryHash := sha256.Sum256([]byte("test_audit_data_integrity")) + queryComponents, err := encodeQueryComponentsForTests(user1.Address(), "sttest00000000000000000000000066", "get_record", []byte{0x01}) + require.NoError(t, err) settleTime := time.Now().Add(24 * time.Hour).Unix() var marketID int64 - err = callCreateMarket(ctx, platform, &user1, queryHash[:], settleTime, 5, 20, func(row *common.Row) error { + err = callCreateMarket(ctx, platform, &user1, queryComponents, settleTime, 5, 20, func(row *common.Row) error { marketID = row.Values[0].(int64) return nil }) diff --git a/tests/streams/order_book/fee_distribution_test.go b/tests/streams/order_book/fee_distribution_test.go index be3918727..e94a3a553 100644 --- a/tests/streams/order_book/fee_distribution_test.go +++ b/tests/streams/order_book/fee_distribution_test.go @@ -4,7 +4,6 @@ package order_book import ( "context" - "crypto/sha256" "fmt" "math/big" "testing" @@ -63,11 +62,13 @@ func testDistribution1Block2LPs(t *testing.T) func(context.Context, *kwilTesting require.NoError(t, err) // Create market - queryHash := sha256.Sum256([]byte("test_distribution_1block_2lps")) + queryComponents, err := encodeQueryComponentsForTests(user1.Address(), "sttest00000000000000000000000057", "get_record", []byte{0x01}) + + require.NoError(t, err) settleTime := time.Now().Add(24 * time.Hour).Unix() var marketID int64 - err = callCreateMarket(ctx, platform, &user1, queryHash[:], settleTime, 5, 20, func(row *common.Row) error { + err = callCreateMarket(ctx, platform, &user1, queryComponents, settleTime, 5, 20, func(row *common.Row) error { marketID = row.Values[0].(int64) return nil }) @@ -239,11 +240,13 @@ func testDistribution3Blocks2LPs(t *testing.T) func(context.Context, *kwilTestin require.NoError(t, err) // Create market - queryHash := sha256.Sum256([]byte("test_distribution_3blocks")) + queryComponents, err := encodeQueryComponentsForTests(user1.Address(), "sttest00000000000000000000000058", "get_record", []byte{0x01}) + + require.NoError(t, err) settleTime := time.Now().Add(24 * time.Hour).Unix() var marketID int64 - err = callCreateMarket(ctx, platform, &user1, queryHash[:], settleTime, 5, 20, func(row *common.Row) error { + err = callCreateMarket(ctx, platform, &user1, queryComponents, settleTime, 5, 20, func(row *common.Row) error { marketID = row.Values[0].(int64) return nil }) @@ -421,11 +424,13 @@ func testDistributionNoSamples(t *testing.T) func(context.Context, *kwilTesting. require.NoError(t, err) // Create market - queryHash := sha256.Sum256([]byte("test_distribution_no_samples")) + queryComponents, err := encodeQueryComponentsForTests(user1.Address(), "sttest00000000000000000000000059", "get_record", []byte{0x01}) + + require.NoError(t, err) settleTime := time.Now().Add(24 * time.Hour).Unix() var marketID int64 - err = callCreateMarket(ctx, platform, &user1, queryHash[:], settleTime, 5, 20, func(row *common.Row) error { + err = callCreateMarket(ctx, platform, &user1, queryComponents, settleTime, 5, 20, func(row *common.Row) error { marketID = row.Values[0].(int64) return nil }) @@ -516,11 +521,13 @@ func testDistributionZeroFees(t *testing.T) func(context.Context, *kwilTesting.P require.NoError(t, err) // Create market - queryHash := sha256.Sum256([]byte("test_distribution_zero_fees")) + queryComponents, err := encodeQueryComponentsForTests(user1.Address(), "sttest00000000000000000000000060", "get_record", []byte{0x01}) + + require.NoError(t, err) settleTime := time.Now().Add(24 * time.Hour).Unix() var marketID int64 - err = callCreateMarket(ctx, platform, &user1, queryHash[:], settleTime, 5, 20, func(row *common.Row) error { + err = callCreateMarket(ctx, platform, &user1, queryComponents, settleTime, 5, 20, func(row *common.Row) error { marketID = row.Values[0].(int64) return nil }) @@ -626,11 +633,13 @@ func testDistribution1LP(t *testing.T) func(context.Context, *kwilTesting.Platfo require.NoError(t, err) // Create market - queryHash := sha256.Sum256([]byte("test_distribution_1lp")) + queryComponents, err := encodeQueryComponentsForTests(user1.Address(), "sttest00000000000000000000000061", "get_record", []byte{0x01}) + + require.NoError(t, err) settleTime := time.Now().Add(24 * time.Hour).Unix() var marketID int64 - err = callCreateMarket(ctx, platform, &user1, queryHash[:], settleTime, 5, 20, func(row *common.Row) error { + err = callCreateMarket(ctx, platform, &user1, queryComponents, settleTime, 5, 20, func(row *common.Row) error { marketID = row.Values[0].(int64) return nil }) diff --git a/tests/streams/order_book/hash_compatibility_test.go b/tests/streams/order_book/hash_compatibility_test.go new file mode 100644 index 000000000..9b43c111c --- /dev/null +++ b/tests/streams/order_book/hash_compatibility_test.go @@ -0,0 +1,541 @@ +//go:build kwiltest + +package order_book + +import ( + "context" + "fmt" + "testing" + "time" + + gethAbi "github.com/ethereum/go-ethereum/accounts/abi" + gethCommon "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" + "github.com/trufnetwork/kwil-db/common" + "github.com/trufnetwork/kwil-db/core/crypto" + coreauth "github.com/trufnetwork/kwil-db/core/crypto/auth" + kwilTypes "github.com/trufnetwork/kwil-db/core/types" + erc20bridge "github.com/trufnetwork/kwil-db/node/exts/erc20-bridge/erc20" + kwilTesting "github.com/trufnetwork/kwil-db/testing" + "github.com/trufnetwork/node/extensions/tn_utils" + "github.com/trufnetwork/node/internal/migrations" + attestationTests "github.com/trufnetwork/node/tests/streams/attestation" + testutils "github.com/trufnetwork/node/tests/streams/utils" + testerc20 "github.com/trufnetwork/node/tests/streams/utils/erc20" + "github.com/trufnetwork/node/tests/streams/utils/setup" + "github.com/trufnetwork/sdk-go/core/util" +) + +// TestHashCompatibility tests that market hash matches attestation hash +// THIS IS THE CRITICAL TEST FOR AUTOMATIC SETTLEMENT TO WORK +func TestHashCompatibility(t *testing.T) { + testutils.RunSchemaTest(t, kwilTesting.SchemaTest{ + Name: "ORDER_BOOK_HashCompatibility", + SeedStatements: migrations.GetSeedScriptStatements(), + FunctionTests: []kwilTesting.TestFunc{ + testHashCompatibility_MarketAndAttestation(t), + testHashCompatibility_SameComponentsProduceSameHash(t), + testHashCompatibility_DifferentComponentsProduceDifferentHash(t), + }, + }, testutils.GetTestOptionsWithCache()) +} + +// Test constants for bridges +const ( + // TRUF bridge (hoodi_tt) - market creation fee is ALWAYS collected from here + testTRUFChainHashCompat = "hoodi" + testTRUFEscrowHashCompat = "0x878d6aaeb6e746033f50b8dc268d54b4631554e7" + testTRUFERC20HashCompat = "0x263ce78fef26600e4e428cebc91c2a52484b4fbf" + testTRUFExtensionNameHashCompat = "hoodi_tt" + + // USDC bridge (hoodi_tt2) - market collateral for trading + testUSDCChainHashCompat = "hoodi" + testUSDCEscrowHashCompat = "0x80D9B3b6941367917816d36748C88B303f7F1415" + testUSDCERC20HashCompat = "0x1591DeAa21710E0BA6CC1b15F49620C9F65B2dEd" + testUSDCExtensionNameHashCompat = "hoodi_tt2" +) + +var ( + hashCompatBalanceCounter int64 = 300 + hashCompatLastPoint *int64 +) + +// testHashCompatibility_MarketAndAttestation is THE critical test +// It verifies that a market created with query_components produces the SAME hash +// as an attestation requested with the same components. +// This is ESSENTIAL for automatic settlement to work. +func testHashCompatibility_MarketAndAttestation(t *testing.T) func(context.Context, *kwilTesting.Platform) error { + return func(ctx context.Context, platform *kwilTesting.Platform) error { + // CRITICAL: Reset balance chain pointer for this test + hashCompatLastPoint = nil + + // Setup + deployer := util.Unsafe_NewEthereumAddressFromString("0x1111111111111111111111111111111111111111") + platform.Deployer = deployer.Bytes() + + // Initialize ERC20 extension and attestation helper + err := erc20bridge.ForTestingInitializeExtension(ctx, platform) + require.NoError(t, err) + + helper := attestationTests.NewAttestationTestHelper(t, ctx, platform) + + // Give balance + err = giveBalanceChainedHashCompat(ctx, platform, deployer.Address(), "100000000000000000000") + require.NoError(t, err) + + // Create data provider + err = setup.CreateDataProvider(ctx, platform, deployer.Address()) + require.NoError(t, err) + + // Use a simple stream ID (exactly 32 characters) + streamID := "sthashcompat00000000000000000000" + dataProvider := deployer.Address() + + // Create primitive stream and insert data + engineCtx := helper.NewEngineContext() + + _, err = platform.Engine.Call(engineCtx, platform.DB, "", "create_stream", + []any{streamID, "primitive"}, + nil) + require.NoError(t, err) + + // Insert outcome data + eventTime := int64(1000) + valueStr := "1.000000000000000000" // 1.0 = YES outcome + valueDecimal, err := kwilTypes.ParseDecimalExplicit(valueStr, 36, 18) + require.NoError(t, err) + + _, err = platform.Engine.Call(engineCtx, platform.DB, "", "insert_records", + []any{ + []string{dataProvider}, + []string{streamID}, + []int64{eventTime}, + []*kwilTypes.Decimal{valueDecimal}, + }, + nil) + require.NoError(t, err) + + // ====================================================================== + // STEP 1: Create market with query_components + // ====================================================================== + + // Build query components for get_record action + // Args: data_provider, stream_id, from, to, frozen_at, use_cache + argsBytes, err := tn_utils.EncodeActionArgs([]any{ + dataProvider, + streamID, + int64(500), // from + int64(1500), // to + nil, // frozen_at (NULL = latest) + false, // use_cache + }) + require.NoError(t, err) + + // Encode query components using ABI + queryComponents, err := encodeQueryComponentsABIHashCompat(dataProvider, streamID, "get_record", argsBytes) + require.NoError(t, err) + + // Create market + settleTime := time.Now().Add(1 * time.Hour).Unix() + var marketQueryID int + var marketHash []byte + + // Use separate engine context for market creation + marketCtx := helper.NewEngineContext() + res, err := platform.Engine.Call(marketCtx, platform.DB, "", "create_market", + []any{testUSDCExtensionNameHashCompat, queryComponents, settleTime, int64(5), int64(100)}, + func(row *common.Row) error { + marketQueryID = int(row.Values[0].(int64)) + return nil + }) + require.NoError(t, err, "engine call failed") + if res != nil && res.Error != nil { + t.Logf("create_market error: %v", res.Error) + require.NoError(t, res.Error, "create_market action failed") + } + + // Get the market hash + err = callGetMarketInfoHashCompat(ctx, platform, &deployer, marketQueryID, func(row *common.Row) error { + marketHash = row.Values[0].([]byte) + return nil + }) + require.NoError(t, err) + require.Len(t, marketHash, 32, "market hash should be 32 bytes") + + t.Logf("Market hash: %x", marketHash) + + // ====================================================================== + // STEP 2: Request attestation with same components + // ====================================================================== + + var requestTxID string + var attestationHash []byte + + // Use separate engine context for attestation request + attestCtx := helper.NewEngineContext() + res, err = platform.Engine.Call(attestCtx, platform.DB, "", "request_attestation", + []any{ + dataProvider, + streamID, + "get_record", + argsBytes, + false, // encrypt_sig + nil, // max_fee + }, + func(row *common.Row) error { + requestTxID = row.Values[0].(string) + attestationHash = append([]byte(nil), row.Values[1].([]byte)...) + return nil + }) + require.NoError(t, err, "engine call failed") + if res != nil && res.Error != nil { + t.Logf("request_attestation error: %v", res.Error) + require.NoError(t, res.Error, "request_attestation action failed") + } + + require.NotEmpty(t, requestTxID, "request_tx_id should not be empty") + require.Len(t, attestationHash, 32, "attestation hash should be 32 bytes") + + t.Logf("Attestation hash: %x", attestationHash) + + // ====================================================================== + // STEP 3: CRITICAL ASSERTION - Hashes MUST match + // ====================================================================== + + require.Equal(t, marketHash, attestationHash, + "Market hash MUST equal attestation hash for automatic settlement to work!\n"+ + "Market hash: %x\n"+ + "Attestation hash: %x", + marketHash, attestationHash) + + t.Logf("✅ SUCCESS: Market hash matches attestation hash!") + + return nil + } +} + +// testHashCompatibility_SameComponentsProduceSameHash tests determinism +func testHashCompatibility_SameComponentsProduceSameHash(t *testing.T) func(context.Context, *kwilTesting.Platform) error { + return func(ctx context.Context, platform *kwilTesting.Platform) error { + // CRITICAL: Reset balance chain pointer for this test + hashCompatLastPoint = nil + + deployer := util.Unsafe_NewEthereumAddressFromString("0x2222222222222222222222222222222222222222") + + // Initialize and give balance + err := erc20bridge.ForTestingInitializeExtension(ctx, platform) + require.NoError(t, err) + err = giveBalanceChainedHashCompat(ctx, platform, deployer.Address(), "100000000000000000000") + require.NoError(t, err) + + // Create query components + dataProvider := deployer.Address() + streamID := "stsamecomponents0000000000000000" + actionID := "get_record" + argsBytes := []byte{0x01, 0x02, 0x03, 0x04} + + queryComponents, err := encodeQueryComponentsABIHashCompat(dataProvider, streamID, actionID, argsBytes) + require.NoError(t, err) + + // Create two markets with SAME components (will fail on second due to duplicate hash) + settleTime1 := time.Now().Add(1 * time.Hour).Unix() + var hash1 []byte + var queryID1 int + + _, pubGeneric, err := crypto.GenerateSecp256k1Key(nil) + require.NoError(t, err) + pub := pubGeneric.(*crypto.Secp256k1PublicKey) + + tx := &common.TxContext{ + Ctx: ctx, + BlockContext: &common.BlockContext{ + Height: 1, + Timestamp: time.Now().Unix(), + Proposer: pub, + }, + Signer: deployer.Bytes(), + Caller: deployer.Address(), + TxID: platform.Txid(), + Authenticator: coreauth.EthPersonalSignAuth, + } + engineCtx := &common.EngineContext{TxContext: tx} + + res, err := platform.Engine.Call(engineCtx, platform.DB, "", "create_market", + []any{testUSDCExtensionNameHashCompat, queryComponents, settleTime1, int64(5), int64(100)}, + func(row *common.Row) error { + queryID1 = int(row.Values[0].(int64)) + return nil + }) + require.NoError(t, err) + if res != nil && res.Error != nil { + require.NoError(t, res.Error) + } + + // Get hash from first market + err = callGetMarketInfoHashCompat(ctx, platform, &deployer, queryID1, func(row *common.Row) error { + hash1 = row.Values[0].([]byte) + return nil + }) + require.NoError(t, err) + require.Len(t, hash1, 32, "hash should be 32 bytes") + + // Try to create second market with same components (will fail due to duplicate hash) + settleTime2 := time.Now().Add(2 * time.Hour).Unix() + tx2 := &common.TxContext{ + Ctx: ctx, + BlockContext: &common.BlockContext{ + Height: 2, + Timestamp: time.Now().Unix(), + Proposer: pub, + }, + Signer: deployer.Bytes(), + Caller: deployer.Address(), + TxID: platform.Txid(), + Authenticator: coreauth.EthPersonalSignAuth, + } + engineCtx2 := &common.EngineContext{TxContext: tx2} + + res, err = platform.Engine.Call(engineCtx2, platform.DB, "", "create_market", + []any{testUSDCExtensionNameHashCompat, queryComponents, settleTime2, int64(5), int64(100)}, + nil) + require.NoError(t, err, "engine call should not error") + require.NotNil(t, res, "result should not be nil") + require.Error(t, res.Error, "should fail due to duplicate hash") + require.Contains(t, res.Error.Error(), "already exists", "error should mention duplicate") + + t.Logf("✅ Same components produce same hash (duplicate correctly rejected)") + + return nil + } +} + +// testHashCompatibility_DifferentComponentsProduceDifferentHash tests uniqueness +func testHashCompatibility_DifferentComponentsProduceDifferentHash(t *testing.T) func(context.Context, *kwilTesting.Platform) error { + return func(ctx context.Context, platform *kwilTesting.Platform) error { + // CRITICAL: Reset balance chain pointer for this test + hashCompatLastPoint = nil + + deployer := util.Unsafe_NewEthereumAddressFromString("0x3333333333333333333333333333333333333333") + + // Initialize and give balance + err := erc20bridge.ForTestingInitializeExtension(ctx, platform) + require.NoError(t, err) + err = giveBalanceChainedHashCompat(ctx, platform, deployer.Address(), "100000000000000000000") + require.NoError(t, err) + + dataProvider := deployer.Address() + streamID1 := "stdiffcomp1000000000000000000000" + streamID2 := "stdiffcomp2000000000000000000000" + actionID := "get_record" + argsBytes := []byte{0x00} + + // Create two markets with DIFFERENT stream IDs + components1, err := encodeQueryComponentsABIHashCompat(dataProvider, streamID1, actionID, argsBytes) + require.NoError(t, err) + + components2, err := encodeQueryComponentsABIHashCompat(dataProvider, streamID2, actionID, argsBytes) + require.NoError(t, err) + + settleTime := time.Now().Add(1 * time.Hour).Unix() + var queryID1, queryID2 int + + // Create first market + _, pubGeneric, err := crypto.GenerateSecp256k1Key(nil) + require.NoError(t, err) + pub := pubGeneric.(*crypto.Secp256k1PublicKey) + + tx1 := &common.TxContext{ + Ctx: ctx, + BlockContext: &common.BlockContext{ + Height: 1, + Timestamp: time.Now().Unix(), + Proposer: pub, + }, + Signer: deployer.Bytes(), + Caller: deployer.Address(), + TxID: platform.Txid(), + Authenticator: coreauth.EthPersonalSignAuth, + } + engineCtx1 := &common.EngineContext{TxContext: tx1} + + res, err := platform.Engine.Call(engineCtx1, platform.DB, "", "create_market", + []any{testUSDCExtensionNameHashCompat, components1, settleTime, int64(5), int64(100)}, + func(row *common.Row) error { + queryID1 = int(row.Values[0].(int64)) + return nil + }) + require.NoError(t, err) + if res != nil && res.Error != nil { + require.NoError(t, res.Error) + } + + // Create second market + tx2 := &common.TxContext{ + Ctx: ctx, + BlockContext: &common.BlockContext{ + Height: 2, + Timestamp: time.Now().Unix(), + Proposer: pub, + }, + Signer: deployer.Bytes(), + Caller: deployer.Address(), + TxID: platform.Txid(), + Authenticator: coreauth.EthPersonalSignAuth, + } + engineCtx2 := &common.EngineContext{TxContext: tx2} + + res, err = platform.Engine.Call(engineCtx2, platform.DB, "", "create_market", + []any{testUSDCExtensionNameHashCompat, components2, settleTime, int64(5), int64(100)}, + func(row *common.Row) error { + queryID2 = int(row.Values[0].(int64)) + return nil + }) + require.NoError(t, err) + if res != nil && res.Error != nil { + require.NoError(t, res.Error) + } + + // Get hashes from both markets + var hash1, hash2 []byte + err = callGetMarketInfoHashCompat(ctx, platform, &deployer, queryID1, func(row *common.Row) error { + hash1 = row.Values[0].([]byte) + return nil + }) + require.NoError(t, err) + + err = callGetMarketInfoHashCompat(ctx, platform, &deployer, queryID2, func(row *common.Row) error { + hash2 = row.Values[0].([]byte) + return nil + }) + require.NoError(t, err) + + // Hashes MUST be different + require.NotEqual(t, hash1, hash2, "different components should produce different hashes") + + t.Logf("✅ Different components produce different hashes") + t.Logf("Hash 1: %x", hash1) + t.Logf("Hash 2: %x", hash2) + + return nil + } +} + +// ===== HELPER FUNCTIONS ===== + +// giveBalanceChainedHashCompat injects balance to BOTH bridges with proper chaining: +// 1. hoodi_tt (TRUF) for market creation fees +// 2. hoodi_tt2 (USDC) for market collateral/trading +func giveBalanceChainedHashCompat(ctx context.Context, platform *kwilTesting.Platform, wallet string, amountStr string) error { + // Inject TRUF balance for market creation fees (chained) + hashCompatBalanceCounter++ + currentPoint := hashCompatBalanceCounter + + err := testerc20.InjectERC20Transfer( + ctx, + platform, + testTRUFChainHashCompat, + testTRUFEscrowHashCompat, + testTRUFERC20HashCompat, + wallet, + wallet, + amountStr, + currentPoint, + hashCompatLastPoint, + ) + if err != nil { + return fmt.Errorf("failed to inject TRUF balance: %w", err) + } + + p := currentPoint + hashCompatLastPoint = &p + + // Inject USDC balance for market collateral (chained) + hashCompatBalanceCounter++ + currentPoint = hashCompatBalanceCounter + + err = testerc20.InjectERC20Transfer( + ctx, + platform, + testUSDCChainHashCompat, + testUSDCEscrowHashCompat, + testUSDCERC20HashCompat, + wallet, + wallet, + amountStr, + currentPoint, + hashCompatLastPoint, + ) + if err != nil { + return fmt.Errorf("failed to inject USDC balance: %w", err) + } + + p = currentPoint + hashCompatLastPoint = &p + + return nil +} + +func encodeQueryComponentsABIHashCompat(dataProvider, streamID, actionID string, args []byte) ([]byte, error) { + addressType, err := gethAbi.NewType("address", "", nil) + if err != nil { + return nil, err + } + bytes32Type, err := gethAbi.NewType("bytes32", "", nil) + if err != nil { + return nil, err + } + stringType, err := gethAbi.NewType("string", "", nil) + if err != nil { + return nil, err + } + bytesType, err := gethAbi.NewType("bytes", "", nil) + if err != nil { + return nil, err + } + + abiArgs := gethAbi.Arguments{ + {Type: addressType, Name: "data_provider"}, + {Type: bytes32Type, Name: "stream_id"}, + {Type: stringType, Name: "action_id"}, + {Type: bytesType, Name: "args"}, + } + + dpAddr := gethCommon.HexToAddress(dataProvider) + var streamIDBytes [32]byte + copy(streamIDBytes[:], []byte(streamID)) + + encoded, err := abiArgs.Pack(dpAddr, streamIDBytes, actionID, args) + if err != nil { + return nil, err + } + + return encoded, nil +} + +func callGetMarketInfoHashCompat(ctx context.Context, platform *kwilTesting.Platform, signer *util.EthereumAddress, queryID int, resultFn func(*common.Row) error) error { + tx := &common.TxContext{ + Ctx: ctx, + BlockContext: &common.BlockContext{Height: 1}, + Signer: signer.Bytes(), + Caller: signer.Address(), + TxID: platform.Txid(), + Authenticator: coreauth.EthPersonalSignAuth, + } + engineCtx := &common.EngineContext{TxContext: tx} + + res, err := platform.Engine.Call( + engineCtx, + platform.DB, + "", + "get_market_info", + []any{queryID}, + resultFn, + ) + if err != nil { + return err + } + if res != nil && res.Error != nil { + return res.Error + } + return nil +} diff --git a/tests/streams/order_book/market_creation_test.go b/tests/streams/order_book/market_creation_test.go index 32995ac15..acf2c3173 100644 --- a/tests/streams/order_book/market_creation_test.go +++ b/tests/streams/order_book/market_creation_test.go @@ -10,10 +10,13 @@ import ( "testing" "time" + gethAbi "github.com/ethereum/go-ethereum/accounts/abi" + gethCommon "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/require" "github.com/trufnetwork/kwil-db/common" "github.com/trufnetwork/kwil-db/core/crypto" coreauth "github.com/trufnetwork/kwil-db/core/crypto/auth" + orderedsync "github.com/trufnetwork/kwil-db/node/exts/ordered-sync" kwilTesting "github.com/trufnetwork/kwil-db/testing" "github.com/trufnetwork/node/internal/migrations" testutils "github.com/trufnetwork/node/tests/streams/utils" @@ -23,16 +26,32 @@ import ( // Test constants - match existing erc20-bridge configuration const ( - testChain = "sepolia" - testEscrow = "0x502430eD0BbE0f230215870c9C2853e126eE5Ae3" - testERC20 = "0x2222222222222222222222222222222222222222" - testExtensionName = "sepolia_bridge" - marketFee = "2000000000000000000" // 2 TRUF with 18 decimals + // TRUF bridge (hoodi_tt) - market creation fee is ALWAYS collected from here + testTRUFChain = "hoodi" + testTRUFEscrow = "0x878d6aaeb6e746033f50b8dc268d54b4631554e7" + testTRUFERC20 = "0x263ce78fef26600e4e428cebc91c2a52484b4fbf" + testTRUFExtensionName = "hoodi_tt" + + // USDC bridge (hoodi_tt2) - market collateral for trading + // IMPORTANT: Must match 000-extension.sql registration (source of truth) + testUSDCChain = "hoodi" + testUSDCEscrow = "0x80D9B3b6941367917816d36748C88B303f7F1415" + testUSDCERC20 = "0x1591DeAa21710E0BA6CC1b15F49620C9F65B2dEd" + testUSDCExtensionName = "hoodi_tt2" + + // Aliases for other test files that don't use create_market + testChain = testUSDCChain + testEscrow = testUSDCEscrow + testERC20 = testUSDCERC20 + testExtensionName = testUSDCExtensionName + + marketFee = "2000000000000000000" // 2 TRUF with 18 decimals ) var ( - twoTRUF = mustParseBigInt(marketFee) - pointCounter int64 = 100 // Start from 100 to avoid conflicts with other tests + twoTRUF = mustParseBigInt(marketFee) + trufPointCounter int64 = 100 // Counter for TRUF bridge (hoodi_tt) + usdcPointCounter int64 = 10000 // Counter for USDC bridge (hoodi_tt2) - far apart to avoid conflicts ) func mustParseBigInt(s string) *big.Int { @@ -69,9 +88,14 @@ func testCreateMarketHappyPath(t *testing.T) func(ctx context.Context, platform err := giveBalance(ctx, platform, userAddr.Address(), "100000000000000000000") // 100 TRUF require.NoError(t, err, "failed to give balance") - // Generate test query hash - queryData := []byte("test_data_provider:test_stream:get_record:args") - queryHash := sha256.Sum256(queryData) + // Encode query components + dataProvider := userAddr.Address() + streamID := "sthappypath000000000000000000000000" // Exactly 32 chars + actionID := "get_record" + argsBytes := []byte{0x01, 0x02, 0x03} + + queryComponents, err := encodeQueryComponents(dataProvider, streamID, actionID, argsBytes) + require.NoError(t, err, "failed to encode query components") // Set settlement time to 1 hour from now settleTime := time.Now().Add(1 * time.Hour).Unix() @@ -82,7 +106,7 @@ func testCreateMarketHappyPath(t *testing.T) func(ctx context.Context, platform // Create market var queryID int64 - err = callCreateMarket(ctx, platform, &userAddr, queryHash[:], settleTime, int64(5), int64(20), func(row *common.Row) error { + err = callCreateMarket(ctx, platform, &userAddr, queryComponents, settleTime, int64(5), int64(20), func(row *common.Row) error { require.Len(t, row.Values, 1, "expected 1 return value") queryID = row.Values[0].(int64) return nil @@ -100,16 +124,22 @@ func testCreateMarketHappyPath(t *testing.T) func(ctx context.Context, platform // Verify market was created via get_market_info var storedHash []byte + var storedComponents []byte + var storedBridge string var storedSettleTime int64 var settled bool err = callGetMarketInfo(ctx, platform, &userAddr, int(queryID), func(row *common.Row) error { storedHash = row.Values[0].([]byte) - storedSettleTime = row.Values[1].(int64) - settled = row.Values[2].(bool) + storedComponents = row.Values[1].([]byte) + storedBridge = row.Values[2].(string) + storedSettleTime = row.Values[3].(int64) + settled = row.Values[4].(bool) return nil }) require.NoError(t, err, "get_market_info should succeed") - require.Equal(t, queryHash[:], storedHash, "hash should match") + require.Len(t, storedHash, 32, "hash should be 32 bytes") + require.Equal(t, queryComponents, storedComponents, "components should match") + require.Equal(t, testUSDCExtensionName, storedBridge, "bridge should match") require.Equal(t, settleTime, storedSettleTime, "settle_time should match") require.False(t, settled, "market should not be settled") @@ -126,32 +156,40 @@ func testCreateMarketValidation(t *testing.T) func(ctx context.Context, platform err := giveBalance(ctx, platform, userAddr.Address(), "100000000000000000000") require.NoError(t, err) - validHash := sha256.Sum256([]byte("test_validation")) + // Encode valid query components + dataProvider := userAddr.Address() + streamID := "stvalidation00000000000000000000000" // Exactly 32 chars + actionID := "get_record" + argsBytes := []byte{0x01} + + validComponents, err := encodeQueryComponents(dataProvider, streamID, actionID, argsBytes) + require.NoError(t, err) + futureTime := time.Now().Add(1 * time.Hour).Unix() - // Test: Invalid hash length (not 32 bytes) - shortHash := []byte("too_short") // Less than 32 bytes - err = callCreateMarket(ctx, platform, &userAddr, shortHash, futureTime, int64(5), int64(20), nil) - require.Error(t, err, "should fail with invalid hash length") - require.Contains(t, err.Error(), "32 bytes", "error should mention 32 bytes") + // Test: Empty query_components + emptyComponents := []byte{} + err = callCreateMarket(ctx, platform, &userAddr, emptyComponents, futureTime, int64(5), int64(20), nil) + require.Error(t, err, "should fail with empty query_components") + require.Contains(t, err.Error(), "query_components", "error should mention query_components") // Test: Settlement time in the past pastTime := time.Now().Add(-1 * time.Hour).Unix() - err = callCreateMarket(ctx, platform, &userAddr, validHash[:], pastTime, int64(5), int64(20), nil) + err = callCreateMarket(ctx, platform, &userAddr, validComponents, pastTime, int64(5), int64(20), nil) require.Error(t, err, "should fail with past settlement time") require.Contains(t, err.Error(), "future", "error should mention future") // Test: Invalid max_spread (too high) - err = callCreateMarket(ctx, platform, &userAddr, validHash[:], futureTime, int64(100), int64(20), nil) + err = callCreateMarket(ctx, platform, &userAddr, validComponents, futureTime, int64(100), int64(20), nil) require.Error(t, err, "should fail with max_spread > 50") require.Contains(t, err.Error(), "spread", "error should mention spread") // Test: Invalid max_spread (zero) - err = callCreateMarket(ctx, platform, &userAddr, validHash[:], futureTime, int64(0), int64(20), nil) + err = callCreateMarket(ctx, platform, &userAddr, validComponents, futureTime, int64(0), int64(20), nil) require.Error(t, err, "should fail with max_spread = 0") // Test: Invalid min_order_size (zero) - err = callCreateMarket(ctx, platform, &userAddr, validHash[:], futureTime, int64(5), int64(0), nil) + err = callCreateMarket(ctx, platform, &userAddr, validComponents, futureTime, int64(5), int64(0), nil) require.Error(t, err, "should fail with min_order_size = 0") return nil @@ -167,15 +205,23 @@ func testCreateMarketDuplicateHash(t *testing.T) func(ctx context.Context, platf err := giveBalance(ctx, platform, userAddr.Address(), "100000000000000000000") require.NoError(t, err) - queryHash := sha256.Sum256([]byte("unique_market_hash")) + // Encode query components + dataProvider := userAddr.Address() + streamID := "stduplicate000000000000000000000000" // Exactly 32 chars + actionID := "get_record" + argsBytes := []byte{0x01} + + queryComponents, err := encodeQueryComponents(dataProvider, streamID, actionID, argsBytes) + require.NoError(t, err) + settleTime := time.Now().Add(1 * time.Hour).Unix() // Create first market (should succeed) - err = callCreateMarket(ctx, platform, &userAddr, queryHash[:], settleTime, int64(5), int64(20), nil) + err = callCreateMarket(ctx, platform, &userAddr, queryComponents, settleTime, int64(5), int64(20), nil) require.NoError(t, err, "first market creation should succeed") // Try to create duplicate (should fail due to UNIQUE constraint) - err = callCreateMarket(ctx, platform, &userAddr, queryHash[:], settleTime+3600, int64(5), int64(20), nil) + err = callCreateMarket(ctx, platform, &userAddr, queryComponents, settleTime+3600, int64(5), int64(20), nil) require.Error(t, err, "duplicate hash should be rejected") return nil @@ -191,13 +237,21 @@ func testCreateMarketInsufficientBalance(t *testing.T) func(ctx context.Context, err := giveBalance(ctx, platform, userAddr.Address(), "1000000000000000000") require.NoError(t, err) - queryHash := sha256.Sum256([]byte("insufficient_balance_test")) + // Encode query components + dataProvider := userAddr.Address() + streamID := "stinsufficient00000000000000000000000" // Exactly 32 chars + actionID := "get_record" + argsBytes := []byte{0x01} + + queryComponents, err := encodeQueryComponents(dataProvider, streamID, actionID, argsBytes) + require.NoError(t, err) + settleTime := time.Now().Add(1 * time.Hour).Unix() - // Try to create market (should fail due to insufficient balance) - err = callCreateMarket(ctx, platform, &userAddr, queryHash[:], settleTime, int64(5), int64(20), nil) - require.Error(t, err, "should fail with insufficient balance") - require.Contains(t, err.Error(), "Insufficient balance", "error should mention insufficient balance") + // Try to create market (should fail due to insufficient TRUF balance) + err = callCreateMarket(ctx, platform, &userAddr, queryComponents, settleTime, int64(5), int64(20), nil) + require.Error(t, err, "should fail with insufficient TRUF balance") + require.Contains(t, err.Error(), "Insufficient TRUF balance", "error should mention insufficient TRUF balance") return nil } @@ -212,11 +266,19 @@ func testGetMarketInfo(t *testing.T) func(ctx context.Context, platform *kwilTes err := giveBalance(ctx, platform, userAddr.Address(), "100000000000000000000") require.NoError(t, err) - queryHash := sha256.Sum256([]byte("get_market_info_test")) + // Encode query components + dataProvider := userAddr.Address() + streamID := "stgetmarketinfo000000000000000000000" // Exactly 32 chars + actionID := "get_index" + argsBytes := []byte{0x01, 0x02} + + queryComponents, err := encodeQueryComponents(dataProvider, streamID, actionID, argsBytes) + require.NoError(t, err) + settleTime := time.Now().Add(2 * time.Hour).Unix() var queryID int64 - err = callCreateMarket(ctx, platform, &userAddr, queryHash[:], settleTime, int64(10), int64(50), func(row *common.Row) error { + err = callCreateMarket(ctx, platform, &userAddr, queryComponents, settleTime, int64(10), int64(50), func(row *common.Row) error { queryID = row.Values[0].(int64) return nil }) @@ -224,21 +286,27 @@ func testGetMarketInfo(t *testing.T) func(ctx context.Context, platform *kwilTes // Test get_market_info var hash []byte + var components []byte + var bridge string var storedSettleTime int64 var maxSpread int64 var minOrderSize int64 err = callGetMarketInfo(ctx, platform, &userAddr, int(queryID), func(row *common.Row) error { hash = row.Values[0].([]byte) - storedSettleTime = row.Values[1].(int64) - // settled = row.Values[2].(bool) - // winningOutcome = row.Values[3] - // settledAt = row.Values[4] - maxSpread = row.Values[5].(int64) - minOrderSize = row.Values[6].(int64) + components = row.Values[1].([]byte) + bridge = row.Values[2].(string) + storedSettleTime = row.Values[3].(int64) + // settled = row.Values[4].(bool) + // winningOutcome = row.Values[5] + // settledAt = row.Values[6] + maxSpread = row.Values[7].(int64) + minOrderSize = row.Values[8].(int64) return nil }) require.NoError(t, err) - require.Equal(t, queryHash[:], hash) + require.Len(t, hash, 32, "hash should be 32 bytes") + require.Equal(t, queryComponents, components, "components should match") + require.Equal(t, testUSDCExtensionName, bridge, "bridge should match") require.Equal(t, settleTime, storedSettleTime) require.Equal(t, int64(10), maxSpread) require.Equal(t, int64(50), minOrderSize) @@ -263,9 +331,15 @@ func testListMarkets(t *testing.T) func(ctx context.Context, platform *kwilTesti // Create 3 markets for i := 0; i < 3; i++ { - queryHash := sha256.Sum256([]byte(fmt.Sprintf("list_markets_test_%d", i))) + dataProvider := userAddr.Address() + // Generate exactly 32-char stream ID + streamID := fmt.Sprintf("stlistmarkets%02d0000000000000000", i)[:32] + actionID := "get_record" + argsBytes := []byte{byte(i)} + queryComponents, err := encodeQueryComponents(dataProvider, streamID, actionID, argsBytes) + require.NoError(t, err) settleTime := time.Now().Add(time.Duration(i+1) * time.Hour).Unix() - err = callCreateMarket(ctx, platform, &userAddr, queryHash[:], settleTime, int64(5), int64(20), nil) + err = callCreateMarket(ctx, platform, &userAddr, queryComponents, settleTime, int64(5), int64(20), nil) require.NoError(t, err) } @@ -291,15 +365,35 @@ func testMarketExists(t *testing.T) func(ctx context.Context, platform *kwilTest err := giveBalance(ctx, platform, userAddr.Address(), "100000000000000000000") require.NoError(t, err) + // Encode query components + dataProvider := userAddr.Address() + streamID := "stmarketexists0000000000000000000000" // Exactly 32 chars + actionID := "get_record" + argsBytes := []byte{0x01} + + queryComponents, err := encodeQueryComponents(dataProvider, streamID, actionID, argsBytes) + require.NoError(t, err) + // Create a market - queryHash := sha256.Sum256([]byte("market_exists_test")) settleTime := time.Now().Add(1 * time.Hour).Unix() - err = callCreateMarket(ctx, platform, &userAddr, queryHash[:], settleTime, int64(5), int64(20), nil) + var queryID int64 + err = callCreateMarket(ctx, platform, &userAddr, queryComponents, settleTime, int64(5), int64(20), func(row *common.Row) error { + queryID = row.Values[0].(int64) + return nil + }) + require.NoError(t, err) + + // Get the hash from get_market_info + var queryHash []byte + err = callGetMarketInfo(ctx, platform, &userAddr, int(queryID), func(row *common.Row) error { + queryHash = row.Values[0].([]byte) + return nil + }) require.NoError(t, err) // Test market_exists for existing market var exists bool - err = callMarketExists(ctx, platform, &userAddr, queryHash[:], func(row *common.Row) error { + err = callMarketExists(ctx, platform, &userAddr, queryHash, func(row *common.Row) error { exists = row.Values[0].(bool) return nil }) @@ -321,24 +415,57 @@ func testMarketExists(t *testing.T) func(ctx context.Context, platform *kwilTest // ===== HELPER FUNCTIONS ===== +// giveBalance injects balance to BOTH bridges: +// 1. hoodi_tt (TRUF) for market creation fees +// 2. hoodi_tt2 (USDC) for market collateral/trading func giveBalance(ctx context.Context, platform *kwilTesting.Platform, wallet string, amountStr string) error { - pointCounter++ - return testerc20.InjectERC20Transfer( + // Reset ordered-sync state to start fresh (clears last_processed_point) + orderedsync.ForTestingReset() + + // Inject TRUF balance (use separate counter per bridge, starting fresh each test) + trufPointCounter++ + fmt.Printf("DEBUG giveBalance: Injecting TRUF at point %d\n", trufPointCounter) + err := testerc20.InjectERC20Transfer( + ctx, + platform, + testTRUFChain, + testTRUFEscrow, + testTRUFERC20, + wallet, + wallet, + amountStr, + trufPointCounter, + nil, // No chaining + ) + if err != nil { + return fmt.Errorf("failed to inject TRUF: %w", err) + } + + // Inject USDC balance (use separate counter) + usdcPointCounter++ + fmt.Printf("DEBUG giveBalance: Injecting USDC at point %d\n", usdcPointCounter) + err = testerc20.InjectERC20Transfer( ctx, platform, - testChain, - testEscrow, - testERC20, + testUSDCChain, + testUSDCEscrow, + testUSDCERC20, wallet, wallet, amountStr, - pointCounter, - nil, + usdcPointCounter, + nil, // No chaining - separate topic from TRUF ) + if err != nil { + return fmt.Errorf("failed to inject USDC: %w", err) + } + + return nil } func getBalance(ctx context.Context, platform *kwilTesting.Platform, wallet string) (*big.Int, error) { - balanceStr, err := testerc20.GetUserBalance(ctx, platform, testExtensionName, wallet) + // Market creation tests check TRUF balance (hoodi_tt) since fee is deducted from there + balanceStr, err := testerc20.GetUserBalance(ctx, platform, testTRUFExtensionName, wallet) if err != nil { return nil, err } @@ -351,7 +478,7 @@ func getBalance(ctx context.Context, platform *kwilTesting.Platform, wallet stri return balance, nil } -func callCreateMarket(ctx context.Context, platform *kwilTesting.Platform, signer *util.EthereumAddress, queryHash []byte, settleTime int64, maxSpread int64, minOrderSize int64, resultFn func(*common.Row) error) error { +func callCreateMarket(ctx context.Context, platform *kwilTesting.Platform, signer *util.EthereumAddress, queryComponents []byte, settleTime int64, maxSpread int64, minOrderSize int64, resultFn func(*common.Row) error) error { // Generate leader key for fee transfers _, pubGeneric, err := crypto.GenerateSecp256k1Key(nil) if err != nil { @@ -378,7 +505,7 @@ func callCreateMarket(ctx context.Context, platform *kwilTesting.Platform, signe platform.DB, "", "create_market", - []any{testExtensionName, queryHash, settleTime, maxSpread, minOrderSize}, + []any{testUSDCExtensionName, queryComponents, settleTime, maxSpread, minOrderSize}, resultFn, ) if err != nil { @@ -473,3 +600,46 @@ func callMarketExists(ctx context.Context, platform *kwilTesting.Platform, signe } return nil } + +// encodeQueryComponents encodes query components using ABI format +// Format: (address data_provider, bytes32 stream_id, string action_id, bytes args) +func encodeQueryComponents(dataProvider, streamID, actionID string, args []byte) ([]byte, error) { + addressType, err := gethAbi.NewType("address", "", nil) + if err != nil { + return nil, fmt.Errorf("failed to create address type: %w", err) + } + bytes32Type, err := gethAbi.NewType("bytes32", "", nil) + if err != nil { + return nil, fmt.Errorf("failed to create bytes32 type: %w", err) + } + stringType, err := gethAbi.NewType("string", "", nil) + if err != nil { + return nil, fmt.Errorf("failed to create string type: %w", err) + } + bytesType, err := gethAbi.NewType("bytes", "", nil) + if err != nil { + return nil, fmt.Errorf("failed to create bytes type: %w", err) + } + + abiArgs := gethAbi.Arguments{ + {Type: addressType, Name: "data_provider"}, + {Type: bytes32Type, Name: "stream_id"}, + {Type: stringType, Name: "action_id"}, + {Type: bytesType, Name: "args"}, + } + + // Convert data provider to address + dpAddr := gethCommon.HexToAddress(dataProvider) + + // Convert stream ID to bytes32 (pad with zeros on the right) + var streamIDBytes [32]byte + copy(streamIDBytes[:], []byte(streamID)) + + // Pack the ABI + encoded, err := abiArgs.Pack(dpAddr, streamIDBytes, actionID, args) + if err != nil { + return nil, fmt.Errorf("failed to pack ABI: %w", err) + } + + return encoded, nil +} diff --git a/tests/streams/order_book/matching_engine_test.go b/tests/streams/order_book/matching_engine_test.go index 06c853402..ce5ec6cd1 100644 --- a/tests/streams/order_book/matching_engine_test.go +++ b/tests/streams/order_book/matching_engine_test.go @@ -4,7 +4,6 @@ package order_book import ( "context" - "crypto/sha256" "fmt" "math/big" "testing" @@ -105,10 +104,12 @@ func testDirectMatchFullMatch(t *testing.T) func(context.Context, *kwilTesting.P require.NoError(t, err) // Create market - queryHash := sha256.Sum256([]byte("test_direct_match")) + queryComponents, err := encodeQueryComponentsForTests(user1Addr.Address(), "sttest00000000000000000000000036", "get_record", []byte{0x01}) + + require.NoError(t, err) settleTime := time.Now().Add(24 * time.Hour).Unix() var marketID int64 - err = callCreateMarket(ctx, platform, &user1Addr, queryHash[:], settleTime, 5, 20, func(row *common.Row) error { + err = callCreateMarket(ctx, platform, &user1Addr, queryComponents, settleTime, 5, 20, func(row *common.Row) error { marketID = row.Values[0].(int64) return nil }) @@ -170,10 +171,12 @@ func testMintMatchFullMatch(t *testing.T) func(context.Context, *kwilTesting.Pla require.NoError(t, err) // Create market - queryHash := sha256.Sum256([]byte("test_mint_match")) + queryComponents, err := encodeQueryComponentsForTests(user1Addr.Address(), "sttest00000000000000000000000037", "get_record", []byte{0x01}) + + require.NoError(t, err) settleTime := time.Now().Add(24 * time.Hour).Unix() var marketID int64 - err = callCreateMarket(ctx, platform, &user1Addr, queryHash[:], settleTime, 5, 20, func(row *common.Row) error { + err = callCreateMarket(ctx, platform, &user1Addr, queryComponents, settleTime, 5, 20, func(row *common.Row) error { marketID = row.Values[0].(int64) return nil }) @@ -236,10 +239,12 @@ func testBurnMatchFullMatch(t *testing.T) func(context.Context, *kwilTesting.Pla require.NoError(t, err) // Create market - queryHash := sha256.Sum256([]byte("test_burn_match")) + queryComponents, err := encodeQueryComponentsForTests(user1Addr.Address(), "sttest00000000000000000000000038", "get_record", []byte{0x01}) + + require.NoError(t, err) settleTime := time.Now().Add(24 * time.Hour).Unix() var marketID int64 - err = callCreateMarket(ctx, platform, &user1Addr, queryHash[:], settleTime, 5, 20, func(row *common.Row) error { + err = callCreateMarket(ctx, platform, &user1Addr, queryComponents, settleTime, 5, 20, func(row *common.Row) error { marketID = row.Values[0].(int64) return nil }) @@ -329,10 +334,12 @@ func testDirectMatchPartialFill(t *testing.T) func(context.Context, *kwilTesting require.NoError(t, err) // Create market - queryHash := sha256.Sum256([]byte("test_direct_partial")) + queryComponents, err := encodeQueryComponentsForTests(user1Addr.Address(), "sttest00000000000000000000000039", "get_record", []byte{0x01}) + + require.NoError(t, err) settleTime := time.Now().Add(24 * time.Hour).Unix() var marketID int64 - err = callCreateMarket(ctx, platform, &user1Addr, queryHash[:], settleTime, 5, 20, func(row *common.Row) error { + err = callCreateMarket(ctx, platform, &user1Addr, queryComponents, settleTime, 5, 20, func(row *common.Row) error { marketID = row.Values[0].(int64) return nil }) @@ -398,10 +405,12 @@ func testMintMatchPartialFill(t *testing.T) func(context.Context, *kwilTesting.P require.NoError(t, err) // Create market - queryHash := sha256.Sum256([]byte("test_mint_partial")) + queryComponents, err := encodeQueryComponentsForTests(user1Addr.Address(), "sttest00000000000000000000000040", "get_record", []byte{0x01}) + + require.NoError(t, err) settleTime := time.Now().Add(24 * time.Hour).Unix() var marketID int64 - err = callCreateMarket(ctx, platform, &user1Addr, queryHash[:], settleTime, 5, 20, func(row *common.Row) error { + err = callCreateMarket(ctx, platform, &user1Addr, queryComponents, settleTime, 5, 20, func(row *common.Row) error { marketID = row.Values[0].(int64) return nil }) @@ -470,10 +479,12 @@ func testBurnMatchPartialFill(t *testing.T) func(context.Context, *kwilTesting.P require.NoError(t, err) // Create market - queryHash := sha256.Sum256([]byte("test_burn_partial")) + queryComponents, err := encodeQueryComponentsForTests(user1Addr.Address(), "sttest00000000000000000000000041", "get_record", []byte{0x01}) + + require.NoError(t, err) settleTime := time.Now().Add(24 * time.Hour).Unix() var marketID int64 - err = callCreateMarket(ctx, platform, &user1Addr, queryHash[:], settleTime, 5, 20, func(row *common.Row) error { + err = callCreateMarket(ctx, platform, &user1Addr, queryComponents, settleTime, 5, 20, func(row *common.Row) error { marketID = row.Values[0].(int64) return nil }) @@ -573,10 +584,12 @@ func testDirectMatchMultipleRounds(t *testing.T) func(context.Context, *kwilTest require.NoError(t, err) // Create market - queryHash := sha256.Sum256([]byte("test_multiple_rounds")) + queryComponents, err := encodeQueryComponentsForTests(user1Addr.Address(), "sttest00000000000000000000000042", "get_record", []byte{0x01}) + + require.NoError(t, err) settleTime := time.Now().Add(24 * time.Hour).Unix() var marketID int64 - err = callCreateMarket(ctx, platform, &user1Addr, queryHash[:], settleTime, 5, 20, func(row *common.Row) error { + err = callCreateMarket(ctx, platform, &user1Addr, queryComponents, settleTime, 5, 20, func(row *common.Row) error { marketID = row.Values[0].(int64) return nil }) @@ -657,10 +670,12 @@ func testNoMatchingOrders(t *testing.T) func(context.Context, *kwilTesting.Platf require.NoError(t, err) // Create market - queryHash := sha256.Sum256([]byte("test_no_match")) + queryComponents, err := encodeQueryComponentsForTests(user1Addr.Address(), "sttest00000000000000000000000043", "get_record", []byte{0x01}) + + require.NoError(t, err) settleTime := time.Now().Add(24 * time.Hour).Unix() var marketID int64 - err = callCreateMarket(ctx, platform, &user1Addr, queryHash[:], settleTime, 5, 20, func(row *common.Row) error { + err = callCreateMarket(ctx, platform, &user1Addr, queryComponents, settleTime, 5, 20, func(row *common.Row) error { marketID = row.Values[0].(int64) return nil }) diff --git a/tests/streams/order_book/queries_test.go b/tests/streams/order_book/queries_test.go index 8e06ab724..735dddf92 100644 --- a/tests/streams/order_book/queries_test.go +++ b/tests/streams/order_book/queries_test.go @@ -4,8 +4,6 @@ package order_book import ( "context" - "crypto/sha256" - "fmt" "testing" "time" @@ -871,20 +869,21 @@ func giveBalanceQueries(ctx context.Context, platform *kwilTesting.Platform, wal } func createTestMarketQueries(t *testing.T, ctx context.Context, platform *kwilTesting.Platform, signer *util.EthereumAddress) (int, []byte) { - queryData := []byte(fmt.Sprintf("test:stream:%d", time.Now().UnixNano())) - queryHash := sha256.Sum256(queryData) + queryComponents, err := encodeQueryComponentsForTests(signer.Address(), "sttest00000000000000000000000067", "get_record", []byte{0x01}) + require.NoError(t, err) + settleTime := time.Now().Add(1 * time.Hour).Unix() var queryID int64 - err := callActionQueries(ctx, platform, signer, "create_market", - []any{testExtensionNameQueries, queryHash[:], settleTime, int64(5), int64(20)}, + err = callActionQueries(ctx, platform, signer, "create_market", + []any{testExtensionNameQueries, queryComponents, settleTime, int64(5), int64(20)}, func(row *common.Row) error { queryID = row.Values[0].(int64) return nil }) require.NoError(t, err) - return int(queryID), queryHash[:] + return int(queryID), queryComponents } func callGetOrderBook(ctx context.Context, platform *kwilTesting.Platform, signer *util.EthereumAddress, queryID int, outcome bool, resultFn func(*common.Row) error) error { diff --git a/tests/streams/order_book/rewards_test.go b/tests/streams/order_book/rewards_test.go index d65b60801..8f735ef88 100644 --- a/tests/streams/order_book/rewards_test.go +++ b/tests/streams/order_book/rewards_test.go @@ -4,7 +4,6 @@ package order_book import ( "context" - "crypto/sha256" "fmt" "strconv" "testing" @@ -135,11 +134,12 @@ func testSampleRewardsMarketSettled(t *testing.T) func(context.Context, *kwilTes require.NoError(t, err) // Create market - queryHash := sha256.Sum256([]byte("test_rewards_settled")) + queryComponents, err := encodeQueryComponentsForTests(userAddr.Address(), "sttest00000000000000000000000046", "get_record", []byte{0x01}) + require.NoError(t, err) settleTime := time.Now().Add(24 * time.Hour).Unix() var marketID int64 - err = callCreateMarket(ctx, platform, &userAddr, queryHash[:], settleTime, 5, 20, func(row *common.Row) error { + err = callCreateMarket(ctx, platform, &userAddr, queryComponents, settleTime, 5, 20, func(row *common.Row) error { marketID = row.Values[0].(int64) return nil }) @@ -192,11 +192,12 @@ func testSampleRewardsNoOrderBook(t *testing.T) func(context.Context, *kwilTesti require.NoError(t, err) // Create market - queryHash := sha256.Sum256([]byte("test_rewards_no_orders")) + queryComponents, err := encodeQueryComponentsForTests(userAddr.Address(), "sttest00000000000000000000000047", "get_record", []byte{0x01}) + require.NoError(t, err) settleTime := time.Now().Add(24 * time.Hour).Unix() var marketID int64 - err = callCreateMarket(ctx, platform, &userAddr, queryHash[:], settleTime, 5, 20, func(row *common.Row) error { + err = callCreateMarket(ctx, platform, &userAddr, queryComponents, settleTime, 5, 20, func(row *common.Row) error { marketID = row.Values[0].(int64) return nil }) @@ -230,11 +231,12 @@ func testSampleRewardsIncompleteOrderBook(t *testing.T) func(context.Context, *k require.NoError(t, err) // Create market - queryHash := sha256.Sum256([]byte("test_rewards_incomplete")) + queryComponents, err := encodeQueryComponentsForTests(userAddr.Address(), "sttest00000000000000000000000048", "get_record", []byte{0x01}) + require.NoError(t, err) settleTime := time.Now().Add(24 * time.Hour).Unix() var marketID int64 - err = callCreateMarket(ctx, platform, &userAddr, queryHash[:], settleTime, 5, 20, func(row *common.Row) error { + err = callCreateMarket(ctx, platform, &userAddr, queryComponents, settleTime, 5, 20, func(row *common.Row) error { marketID = row.Values[0].(int64) return nil }) @@ -273,11 +275,12 @@ func testSampleRewardsSpread5Cents(t *testing.T) func(context.Context, *kwilTest require.NoError(t, err) // Create market - queryHash := sha256.Sum256([]byte("test_rewards_spread_5")) + queryComponents, err := encodeQueryComponentsForTests(user1.Address(), "sttest00000000000000000000000049", "get_record", []byte{0x01}) + require.NoError(t, err) settleTime := time.Now().Add(24 * time.Hour).Unix() var marketID int64 - err = callCreateMarket(ctx, platform, &user1, queryHash[:], settleTime, 5, 20, func(row *common.Row) error { + err = callCreateMarket(ctx, platform, &user1, queryComponents, settleTime, 5, 20, func(row *common.Row) error { marketID = row.Values[0].(int64) return nil }) @@ -316,11 +319,12 @@ func testSampleRewardsSpread4Cents(t *testing.T) func(context.Context, *kwilTest require.NoError(t, err) // Create market - queryHash := sha256.Sum256([]byte("test_rewards_spread_4")) + queryComponents, err := encodeQueryComponentsForTests(user1.Address(), "sttest00000000000000000000000050", "get_record", []byte{0x01}) + require.NoError(t, err) settleTime := time.Now().Add(24 * time.Hour).Unix() var marketID int64 - err = callCreateMarket(ctx, platform, &user1, queryHash[:], settleTime, 5, 20, func(row *common.Row) error { + err = callCreateMarket(ctx, platform, &user1, queryComponents, settleTime, 5, 20, func(row *common.Row) error { marketID = row.Values[0].(int64) return nil }) @@ -358,11 +362,12 @@ func testSampleRewardsSpread3Cents(t *testing.T) func(context.Context, *kwilTest require.NoError(t, err) // Create market - queryHash := sha256.Sum256([]byte("test_rewards_spread_3")) + queryComponents, err := encodeQueryComponentsForTests(user1.Address(), "sttest00000000000000000000000051", "get_record", []byte{0x01}) + require.NoError(t, err) settleTime := time.Now().Add(24 * time.Hour).Unix() var marketID int64 - err = callCreateMarket(ctx, platform, &user1, queryHash[:], settleTime, 5, 20, func(row *common.Row) error { + err = callCreateMarket(ctx, platform, &user1, queryComponents, settleTime, 5, 20, func(row *common.Row) error { marketID = row.Values[0].(int64) return nil }) @@ -400,11 +405,12 @@ func testSampleRewardsIneligibleMarket(t *testing.T) func(context.Context, *kwil require.NoError(t, err) // Create market - queryHash := sha256.Sum256([]byte("test_rewards_ineligible")) + queryComponents, err := encodeQueryComponentsForTests(user1.Address(), "sttest00000000000000000000000052", "get_record", []byte{0x01}) + require.NoError(t, err) settleTime := time.Now().Add(24 * time.Hour).Unix() var marketID int64 - err = callCreateMarket(ctx, platform, &user1, queryHash[:], settleTime, 5, 20, func(row *common.Row) error { + err = callCreateMarket(ctx, platform, &user1, queryComponents, settleTime, 5, 20, func(row *common.Row) error { marketID = row.Values[0].(int64) return nil }) @@ -442,11 +448,12 @@ func testSampleRewardsSingleLP(t *testing.T) func(context.Context, *kwilTesting. require.NoError(t, err) // Create market - queryHash := sha256.Sum256([]byte("test_rewards_single_lp")) + queryComponents, err := encodeQueryComponentsForTests(user1.Address(), "sttest00000000000000000000000053", "get_record", []byte{0x01}) + require.NoError(t, err) settleTime := time.Now().Add(24 * time.Hour).Unix() var marketID int64 - err = callCreateMarket(ctx, platform, &user1, queryHash[:], settleTime, 5, 20, func(row *common.Row) error { + err = callCreateMarket(ctx, platform, &user1, queryComponents, settleTime, 5, 20, func(row *common.Row) error { marketID = row.Values[0].(int64) return nil }) @@ -509,11 +516,12 @@ func testSampleRewardsTwoLPs(t *testing.T) func(context.Context, *kwilTesting.Pl require.NoError(t, err) // Create market - queryHash := sha256.Sum256([]byte("test_rewards_two_lps")) + queryComponents, err := encodeQueryComponentsForTests(user1.Address(), "sttest00000000000000000000000054", "get_record", []byte{0x01}) + require.NoError(t, err) settleTime := time.Now().Add(24 * time.Hour).Unix() var marketID int64 - err = callCreateMarket(ctx, platform, &user1, queryHash[:], settleTime, 5, 20, func(row *common.Row) error { + err = callCreateMarket(ctx, platform, &user1, queryComponents, settleTime, 5, 20, func(row *common.Row) error { marketID = row.Values[0].(int64) return nil }) @@ -592,11 +600,12 @@ func testSampleRewardsMultipleLPs(t *testing.T) func(context.Context, *kwilTesti require.NoError(t, err) // Create market - queryHash := sha256.Sum256([]byte("test_rewards_multiple_lps")) + queryComponents, err := encodeQueryComponentsForTests(user1.Address(), "sttest00000000000000000000000055", "get_record", []byte{0x01}) + require.NoError(t, err) settleTime := time.Now().Add(24 * time.Hour).Unix() var marketID int64 - err = callCreateMarket(ctx, platform, &user1, queryHash[:], settleTime, 5, 20, func(row *common.Row) error { + err = callCreateMarket(ctx, platform, &user1, queryComponents, settleTime, 5, 20, func(row *common.Row) error { marketID = row.Values[0].(int64) return nil }) @@ -680,11 +689,12 @@ func testSampleRewardsNoQualifyingOrders(t *testing.T) func(context.Context, *kw require.NoError(t, err) // Create market - queryHash := sha256.Sum256([]byte("test_rewards_no_qualifying")) + queryComponents, err := encodeQueryComponentsForTests(user1.Address(), "sttest00000000000000000000000056", "get_record", []byte{0x01}) + require.NoError(t, err) settleTime := time.Now().Add(24 * time.Hour).Unix() var marketID int64 - err = callCreateMarket(ctx, platform, &user1, queryHash[:], settleTime, 5, 20, func(row *common.Row) error { + err = callCreateMarket(ctx, platform, &user1, queryComponents, settleTime, 5, 20, func(row *common.Row) error { marketID = row.Values[0].(int64) return nil }) @@ -727,11 +737,14 @@ func testConstraintSellBuyPair(t *testing.T) func(context.Context, *kwilTesting. err = giveBalanceChained(ctx, platform, user1.Address(), "1000000000000000000000") require.NoError(t, err) - queryHash := sha256.Sum256([]byte("test_constraint_sell_buy")) + queryComponents, err := encodeQueryComponentsForTests(user1.Address(), "sttest00000000000000000000000044", "get_record", []byte{0x01}) + + + require.NoError(t, err) settleTime := time.Now().Add(24 * time.Hour).Unix() var queryID int64 - err = callCreateMarket(ctx, platform, &user1, queryHash[:], settleTime, 5, 20, func(row *common.Row) error { + err = callCreateMarket(ctx, platform, &user1, queryComponents, settleTime, 5, 20, func(row *common.Row) error { queryID = row.Values[0].(int64) return nil }) @@ -788,11 +801,14 @@ func testConstraintNoDuplicates(t *testing.T) func(context.Context, *kwilTesting err = giveBalanceChained(ctx, platform, user2.Address(), "1000000000000000000000") require.NoError(t, err) - queryHash := sha256.Sum256([]byte("test_constraint_no_dup")) + queryComponents, err := encodeQueryComponentsForTests(user2.Address(), "sttest00000000000000000000000045", "get_record", []byte{0x01}) + + + require.NoError(t, err) settleTime := time.Now().Add(24 * time.Hour).Unix() var queryID int64 - err = callCreateMarket(ctx, platform, &user2, queryHash[:], settleTime, 5, 20, func(row *common.Row) error { + err = callCreateMarket(ctx, platform, &user2, queryComponents, settleTime, 5, 20, func(row *common.Row) error { queryID = row.Values[0].(int64) return nil }) diff --git a/tests/streams/order_book/sell_order_test.go b/tests/streams/order_book/sell_order_test.go index a5dd91393..1882e73c9 100644 --- a/tests/streams/order_book/sell_order_test.go +++ b/tests/streams/order_book/sell_order_test.go @@ -4,7 +4,6 @@ package order_book import ( "context" - "crypto/sha256" "fmt" "testing" "time" @@ -59,11 +58,12 @@ func testSellOrderSuccessful(t *testing.T) func(ctx context.Context, platform *k require.NoError(t, err, "failed to give balance") // Create market - queryHash := sha256.Sum256([]byte("test_market_sell_1")) + queryComponents, err := encodeQueryComponentsForTests(userAddr.Address(), "sttest00000000000000000000000008", "get_record", []byte{0x01}) + require.NoError(t, err) settleTime := time.Now().Add(24 * time.Hour).Unix() var marketID int64 - err = callCreateMarket(ctx, platform, &userAddr, queryHash[:], settleTime, 5, 20, func(row *common.Row) error { + err = callCreateMarket(ctx, platform, &userAddr, queryComponents, settleTime, 5, 20, func(row *common.Row) error { marketID = row.Values[0].(int64) return nil }) @@ -123,11 +123,12 @@ func testSellOrderInsufficientShares(t *testing.T) func(ctx context.Context, pla require.NoError(t, err) // Create market - queryHash := sha256.Sum256([]byte("test_market_sell_2")) + queryComponents, err := encodeQueryComponentsForTests(userAddr.Address(), "sttest00000000000000000000000009", "get_record", []byte{0x01}) + require.NoError(t, err) settleTime := time.Now().Add(24 * time.Hour).Unix() var marketID int64 - err = callCreateMarket(ctx, platform, &userAddr, queryHash[:], settleTime, 5, 20, func(row *common.Row) error { + err = callCreateMarket(ctx, platform, &userAddr, queryComponents, settleTime, 5, 20, func(row *common.Row) error { marketID = row.Values[0].(int64) return nil }) @@ -160,11 +161,12 @@ func testSellOrderNoSharesAtAll(t *testing.T) func(ctx context.Context, platform require.NoError(t, err) // Create market - queryHash := sha256.Sum256([]byte("test_market_sell_3")) + queryComponents, err := encodeQueryComponentsForTests(userAddr.Address(), "sttest00000000000000000000000010", "get_record", []byte{0x01}) + require.NoError(t, err) settleTime := time.Now().Add(24 * time.Hour).Unix() var marketID int64 - err = callCreateMarket(ctx, platform, &userAddr, queryHash[:], settleTime, 5, 20, func(row *common.Row) error { + err = callCreateMarket(ctx, platform, &userAddr, queryComponents, settleTime, 5, 20, func(row *common.Row) error { marketID = row.Values[0].(int64) return nil }) @@ -192,11 +194,12 @@ func testSellOrderWrongOutcome(t *testing.T) func(ctx context.Context, platform require.NoError(t, err) // Create market - queryHash := sha256.Sum256([]byte("test_market_sell_4")) + queryComponents, err := encodeQueryComponentsForTests(userAddr.Address(), "sttest00000000000000000000000011", "get_record", []byte{0x01}) + require.NoError(t, err) settleTime := time.Now().Add(24 * time.Hour).Unix() var marketID int64 - err = callCreateMarket(ctx, platform, &userAddr, queryHash[:], settleTime, 5, 20, func(row *common.Row) error { + err = callCreateMarket(ctx, platform, &userAddr, queryComponents, settleTime, 5, 20, func(row *common.Row) error { marketID = row.Values[0].(int64) return nil }) @@ -241,11 +244,12 @@ func testSellOrderMarketSettled(t *testing.T) func(ctx context.Context, platform require.NoError(t, err) // Create market - queryHash := sha256.Sum256([]byte("test_market_sell_6")) + queryComponents, err := encodeQueryComponentsForTests(userAddr.Address(), "sttest00000000000000000000000012", "get_record", []byte{0x01}) + require.NoError(t, err) settleTime := time.Now().Add(24 * time.Hour).Unix() var marketID int64 - err = callCreateMarket(ctx, platform, &userAddr, queryHash[:], settleTime, 5, 20, func(row *common.Row) error { + err = callCreateMarket(ctx, platform, &userAddr, queryComponents, settleTime, 5, 20, func(row *common.Row) error { marketID = row.Values[0].(int64) return nil }) @@ -297,11 +301,12 @@ func testSellOrderInvalidPrice(t *testing.T) func(ctx context.Context, platform require.NoError(t, err) // Create market - queryHash := sha256.Sum256([]byte("test_market_sell_7")) + queryComponents, err := encodeQueryComponentsForTests(userAddr.Address(), "sttest00000000000000000000000013", "get_record", []byte{0x01}) + require.NoError(t, err) settleTime := time.Now().Add(24 * time.Hour).Unix() var marketID int64 - err = callCreateMarket(ctx, platform, &userAddr, queryHash[:], settleTime, 5, 20, func(row *common.Row) error { + err = callCreateMarket(ctx, platform, &userAddr, queryComponents, settleTime, 5, 20, func(row *common.Row) error { marketID = row.Values[0].(int64) return nil }) @@ -339,11 +344,12 @@ func testSellOrderInvalidAmount(t *testing.T) func(ctx context.Context, platform require.NoError(t, err) // Create market - queryHash := sha256.Sum256([]byte("test_market_sell_8")) + queryComponents, err := encodeQueryComponentsForTests(userAddr.Address(), "sttest00000000000000000000000014", "get_record", []byte{0x01}) + require.NoError(t, err) settleTime := time.Now().Add(24 * time.Hour).Unix() var marketID int64 - err = callCreateMarket(ctx, platform, &userAddr, queryHash[:], settleTime, 5, 20, func(row *common.Row) error { + err = callCreateMarket(ctx, platform, &userAddr, queryComponents, settleTime, 5, 20, func(row *common.Row) error { marketID = row.Values[0].(int64) return nil }) @@ -380,11 +386,12 @@ func testSellOrderMultipleDifferentPrices(t *testing.T) func(ctx context.Context require.NoError(t, err) // Create market - queryHash := sha256.Sum256([]byte("test_market_sell_9")) + queryComponents, err := encodeQueryComponentsForTests(userAddr.Address(), "sttest00000000000000000000000015", "get_record", []byte{0x01}) + require.NoError(t, err) settleTime := time.Now().Add(24 * time.Hour).Unix() var marketID int64 - err = callCreateMarket(ctx, platform, &userAddr, queryHash[:], settleTime, 5, 20, func(row *common.Row) error { + err = callCreateMarket(ctx, platform, &userAddr, queryComponents, settleTime, 5, 20, func(row *common.Row) error { marketID = row.Values[0].(int64) return nil }) @@ -448,11 +455,12 @@ func testSellOrderMultipleSamePrice(t *testing.T) func(ctx context.Context, plat require.NoError(t, err) // Create market - queryHash := sha256.Sum256([]byte("test_market_sell_10")) + queryComponents, err := encodeQueryComponentsForTests(userAddr.Address(), "sttest00000000000000000000000016", "get_record", []byte{0x01}) + require.NoError(t, err) settleTime := time.Now().Add(24 * time.Hour).Unix() var marketID int64 - err = callCreateMarket(ctx, platform, &userAddr, queryHash[:], settleTime, 5, 20, func(row *common.Row) error { + err = callCreateMarket(ctx, platform, &userAddr, queryComponents, settleTime, 5, 20, func(row *common.Row) error { marketID = row.Values[0].(int64) return nil }) @@ -508,11 +516,12 @@ func testSellOrderExactAmount(t *testing.T) func(ctx context.Context, platform * require.NoError(t, err) // Create market - queryHash := sha256.Sum256([]byte("test_market_sell_11")) + queryComponents, err := encodeQueryComponentsForTests(userAddr.Address(), "sttest00000000000000000000000017", "get_record", []byte{0x01}) + require.NoError(t, err) settleTime := time.Now().Add(24 * time.Hour).Unix() var marketID int64 - err = callCreateMarket(ctx, platform, &userAddr, queryHash[:], settleTime, 5, 20, func(row *common.Row) error { + err = callCreateMarket(ctx, platform, &userAddr, queryComponents, settleTime, 5, 20, func(row *common.Row) error { marketID = 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 5e147d532..3672b108f 100644 --- a/tests/streams/order_book/settlement_payout_test.go +++ b/tests/streams/order_book/settlement_payout_test.go @@ -171,7 +171,12 @@ func testWinnerReceives98PercentPayout(t *testing.T) func(context.Context, *kwil // Sign the attestation helper.SignAttestation(requestTxID) - // Create market using the attestation hash + // Encode query components for create_market (must match attestation args!) + queryComponents, err := encodeQueryComponentsForTests( + dataProvider, streamID, "get_record", argsBytes) + require.NoError(t, err) + + // Create market using query_components settleTime := int64(100) // Future timestamp maxSpread := int64(5) minOrderSize := int64(1) @@ -181,7 +186,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{testExtensionName, attestationHash, settleTime, maxSpread, minOrderSize}, + []any{testExtensionName, queryComponents, 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 56d9263b4..becbee1cc 100644 --- a/tests/streams/order_book/settlement_test.go +++ b/tests/streams/order_book/settlement_test.go @@ -65,8 +65,12 @@ func testSettleMarketHappyPath(t *testing.T) func(context.Context, *kwilTesting. // Setup attestation helper (handles ERC20 initialization) helper := attestationTests.NewAttestationTestHelper(t, ctx, platform) + // Give balance for market creation and trading + err := giveBalance(ctx, platform, deployer.Address(), "500000000000000000000") + require.NoError(t, err) + // Create data provider - err := setup.CreateDataProvider(ctx, platform, deployer.Address()) + err = setup.CreateDataProvider(ctx, platform, deployer.Address()) require.NoError(t, err) // Use simple stream ID (exactly 32 characters) @@ -137,7 +141,6 @@ func testSettleMarketHappyPath(t *testing.T) func(context.Context, *kwilTesting. require.NoError(t, err) var requestTxID string - var attestationHash []byte engineCtx = helper.NewEngineContext() res, err := platform.Engine.Call(engineCtx, platform.DB, "", "request_attestation", []any{ @@ -150,7 +153,7 @@ func testSettleMarketHappyPath(t *testing.T) func(context.Context, *kwilTesting. }, func(row *common.Row) error { requestTxID = row.Values[0].(string) - attestationHash = append([]byte(nil), row.Values[1].([]byte)...) + // attestationHash not needed - using queryComponents return nil }) require.NoError(t, err) @@ -159,13 +162,17 @@ func testSettleMarketHappyPath(t *testing.T) func(context.Context, *kwilTesting. require.NoError(t, res.Error, "request_attestation failed") } require.NotEmpty(t, requestTxID) - require.NotEmpty(t, attestationHash) - t.Logf("Created attestation: txID=%s, hash=%x", requestTxID, attestationHash) + t.Logf("Created attestation: txID=%s", requestTxID) // Sign the attestation helper.SignAttestation(requestTxID) - // Create market using the attestation hash + // Encode query components for create_market (must match attestation args!) + queryComponents, err := encodeQueryComponentsForTests( + dataProvider, streamID, "get_record", argsBytes) + require.NoError(t, err) + + // Create market using query_components settleTime := int64(100) // Future timestamp maxSpread := int64(5) minOrderSize := int64(1) @@ -175,7 +182,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{testExtensionName, attestationHash, settleTime, maxSpread, minOrderSize}, + []any{testExtensionName, queryComponents, settleTime, maxSpread, minOrderSize}, func(row *common.Row) error { queryID = int(row.Values[0].(int64)) return nil @@ -239,7 +246,13 @@ func testSettleMarketWithNoOutcome(t *testing.T) func(context.Context, *kwilTest platform.Deployer = deployer.Bytes() helper := attestationTests.NewAttestationTestHelper(t, ctx, platform) - err := setup.CreateDataProvider(ctx, platform, deployer.Address()) + + // Give balance for market creation and trading + err := giveBalance(ctx, platform, deployer.Address(), "500000000000000000000") + require.NoError(t, err) + + // Create data provider + err = setup.CreateDataProvider(ctx, platform, deployer.Address()) require.NoError(t, err) streamID := "stnooutcome000000000000000000000" @@ -287,24 +300,27 @@ func testSettleMarketWithNoOutcome(t *testing.T) func(context.Context, *kwilTest require.NoError(t, err) var requestTxID string - var attestationHash []byte engineCtx = helper.NewEngineContext() _, err = platform.Engine.Call(engineCtx, platform.DB, "", "request_attestation", []any{dataProvider, streamID, "get_record", argsBytes, false, nil}, func(row *common.Row) error { requestTxID = row.Values[0].(string) - attestationHash = append([]byte(nil), row.Values[1].([]byte)...) + // attestationHash not needed - using queryComponents return nil }) require.NoError(t, err) helper.SignAttestation(requestTxID) + // Encode query components for create_market + queryComponents, err := encodeQueryComponentsForTests(dataProvider, streamID, "get_record", argsBytes) + require.NoError(t, err) + // Create and settle market var queryID int engineCtx = helper.NewEngineContext() engineCtx.TxContext.BlockContext.Timestamp = 50 _, err = platform.Engine.Call(engineCtx, platform.DB, "", "create_market", - []any{testExtensionName, attestationHash, int64(100), int64(5), int64(1)}, + []any{testExtensionName, queryComponents, int64(100), int64(5), int64(1)}, func(row *common.Row) error { queryID = int(row.Values[0].(int64)) return nil @@ -349,7 +365,13 @@ func testSettleMarketWithMultipleDatapoints(t *testing.T) func(context.Context, platform.Deployer = deployer.Bytes() helper := attestationTests.NewAttestationTestHelper(t, ctx, platform) - err := setup.CreateDataProvider(ctx, platform, deployer.Address()) + + // Give balance for market creation and trading + err := giveBalance(ctx, platform, deployer.Address(), "500000000000000000000") + require.NoError(t, err) + + // Create data provider + err = setup.CreateDataProvider(ctx, platform, deployer.Address()) require.NoError(t, err) streamID := "stmultiple0000000000000000000000" @@ -385,24 +407,27 @@ func testSettleMarketWithMultipleDatapoints(t *testing.T) func(context.Context, require.NoError(t, err) var requestTxID string - var attestationHash []byte engineCtx = helper.NewEngineContext() _, err = platform.Engine.Call(engineCtx, platform.DB, "", "request_attestation", []any{dataProvider, streamID, "get_record", argsBytes, false, nil}, func(row *common.Row) error { requestTxID = row.Values[0].(string) - attestationHash = append([]byte(nil), row.Values[1].([]byte)...) + // attestationHash not needed - using queryComponents return nil }) require.NoError(t, err) helper.SignAttestation(requestTxID) + // Encode query components for create_market + queryComponents, err := encodeQueryComponentsForTests(dataProvider, streamID, "get_record", argsBytes) + require.NoError(t, err) + // Create and settle market var queryID int engineCtx = helper.NewEngineContext() engineCtx.TxContext.BlockContext.Timestamp = 50 _, err = platform.Engine.Call(engineCtx, platform.DB, "", "create_market", - []any{testExtensionName, attestationHash, int64(100), int64(5), int64(1)}, + []any{testExtensionName, queryComponents, int64(100), int64(5), int64(1)}, func(row *common.Row) error { queryID = int(row.Values[0].(int64)) return nil @@ -471,7 +496,13 @@ func testSettleMarketAlreadySettled(t *testing.T) func(context.Context, *kwilTes platform.Deployer = deployer.Bytes() helper := attestationTests.NewAttestationTestHelper(t, ctx, platform) - err := setup.CreateDataProvider(ctx, platform, deployer.Address()) + + // Give balance for market creation and trading + err := giveBalance(ctx, platform, deployer.Address(), "500000000000000000000") + require.NoError(t, err) + + // Create data provider + err = setup.CreateDataProvider(ctx, platform, deployer.Address()) require.NoError(t, err) streamID := "stalreadysettled0000000000000000" @@ -498,24 +529,27 @@ func testSettleMarketAlreadySettled(t *testing.T) func(context.Context, *kwilTes }) var requestTxID string - var attestationHash []byte engineCtx = helper.NewEngineContext() _, err = platform.Engine.Call(engineCtx, platform.DB, "", "request_attestation", []any{dataProvider, streamID, "get_record", argsBytes, false, nil}, func(row *common.Row) error { requestTxID = row.Values[0].(string) - attestationHash = append([]byte(nil), row.Values[1].([]byte)...) + // attestationHash not needed - using queryComponents return nil }) require.NoError(t, err) helper.SignAttestation(requestTxID) + // Encode query components for create_market + queryComponents, err := encodeQueryComponentsForTests(dataProvider, streamID, "get_record", argsBytes) + require.NoError(t, err) + // Create market var queryID int engineCtx = helper.NewEngineContext() engineCtx.TxContext.BlockContext.Timestamp = 50 _, err = platform.Engine.Call(engineCtx, platform.DB, "", "create_market", - []any{testExtensionName, attestationHash, int64(100), int64(5), int64(1)}, + []any{testExtensionName, queryComponents, int64(100), int64(5), int64(1)}, func(row *common.Row) error { queryID = int(row.Values[0].(int64)) return nil @@ -553,7 +587,13 @@ func testSettleMarketTooEarly(t *testing.T) func(context.Context, *kwilTesting.P platform.Deployer = deployer.Bytes() helper := attestationTests.NewAttestationTestHelper(t, ctx, platform) - err := setup.CreateDataProvider(ctx, platform, deployer.Address()) + + // Give balance for market creation and trading + err := giveBalance(ctx, platform, deployer.Address(), "500000000000000000000") + require.NoError(t, err) + + // Create data provider + err = setup.CreateDataProvider(ctx, platform, deployer.Address()) require.NoError(t, err) streamID := "sttooearly0000000000000000000000" @@ -580,24 +620,27 @@ func testSettleMarketTooEarly(t *testing.T) func(context.Context, *kwilTesting.P }) var requestTxID string - var attestationHash []byte engineCtx = helper.NewEngineContext() _, err = platform.Engine.Call(engineCtx, platform.DB, "", "request_attestation", []any{dataProvider, streamID, "get_record", argsBytes, false, nil}, func(row *common.Row) error { requestTxID = row.Values[0].(string) - attestationHash = append([]byte(nil), row.Values[1].([]byte)...) + // attestationHash not needed - using queryComponents return nil }) require.NoError(t, err) helper.SignAttestation(requestTxID) + // Encode query components for create_market + queryComponents, err := encodeQueryComponentsForTests(dataProvider, streamID, "get_record", argsBytes) + require.NoError(t, err) + // Create market with settle_time = 1000 var queryID int engineCtx = helper.NewEngineContext() engineCtx.TxContext.BlockContext.Timestamp = 50 _, err = platform.Engine.Call(engineCtx, platform.DB, "", "create_market", - []any{testExtensionName, attestationHash, int64(1000), int64(5), int64(1)}, + []any{testExtensionName, queryComponents, int64(1000), int64(5), int64(1)}, func(row *common.Row) error { queryID = int(row.Values[0].(int64)) return nil @@ -628,17 +671,25 @@ func testSettleMarketNoAttestation(t *testing.T) func(context.Context, *kwilTest helper := attestationTests.NewAttestationTestHelper(t, ctx, platform) - // Create market with fake attestation hash (no actual attestation exists) - fakeHash := make([]byte, 32) - for i := range fakeHash { - fakeHash[i] = 0xAA - } + // Give balance for market creation and trading + err := giveBalance(ctx, platform, deployer.Address(), "500000000000000000000") + require.NoError(t, err) + + // Create data provider + err = setup.CreateDataProvider(ctx, platform, deployer.Address()) + require.NoError(t, err) + + // Create fake query components (no actual attestation exists) + dataProvider := deployer.Address() + streamID := "stfakeattest0000000000000000000" + fakeQueryComponents, err := encodeQueryComponentsForTests(dataProvider, streamID, "get_record", nil) + require.NoError(t, err) var queryID int engineCtx := helper.NewEngineContext() engineCtx.TxContext.BlockContext.Timestamp = 50 - _, err := platform.Engine.Call(engineCtx, platform.DB, "", "create_market", - []any{testExtensionName, fakeHash, int64(100), int64(5), int64(1)}, + _, err = platform.Engine.Call(engineCtx, platform.DB, "", "create_market", + []any{testExtensionName, fakeQueryComponents, int64(100), int64(5), int64(1)}, func(row *common.Row) error { queryID = int(row.Values[0].(int64)) return nil @@ -668,7 +719,13 @@ func testSettleMarketAttestationNotSigned(t *testing.T) func(context.Context, *k platform.Deployer = deployer.Bytes() helper := attestationTests.NewAttestationTestHelper(t, ctx, platform) - err := setup.CreateDataProvider(ctx, platform, deployer.Address()) + + // Give balance for market creation and trading + err := giveBalance(ctx, platform, deployer.Address(), "500000000000000000000") + require.NoError(t, err) + + // Create data provider + err = setup.CreateDataProvider(ctx, platform, deployer.Address()) require.NoError(t, err) streamID := "stunsigned0000000000000000000000" @@ -694,23 +751,26 @@ func testSettleMarketAttestationNotSigned(t *testing.T) func(context.Context, *k dataProvider, streamID, int64(500), int64(1500), nil, false, }) - var attestationHash []byte engineCtx = helper.NewEngineContext() _, err = platform.Engine.Call(engineCtx, platform.DB, "", "request_attestation", []any{dataProvider, streamID, "get_record", argsBytes, false, nil}, func(row *common.Row) error { - attestationHash = append([]byte(nil), row.Values[1].([]byte)...) + // attestation hash not needed - using queryComponents return nil }) require.NoError(t, err) // NOTE: Intentionally NOT signing the attestation + // Encode query components for create_market + queryComponents, err := encodeQueryComponentsForTests(dataProvider, streamID, "get_record", argsBytes) + require.NoError(t, err) + // Create market var queryID int engineCtx = helper.NewEngineContext() engineCtx.TxContext.BlockContext.Timestamp = 50 _, err = platform.Engine.Call(engineCtx, platform.DB, "", "create_market", - []any{testExtensionName, attestationHash, int64(100), int64(5), int64(1)}, + []any{testExtensionName, queryComponents, int64(100), int64(5), int64(1)}, func(row *common.Row) error { queryID = int(row.Values[0].(int64)) return nil @@ -752,7 +812,13 @@ func testSettleMarketValidationIntegration(t *testing.T) func(context.Context, * platform.Deployer = deployer.Bytes() helper := attestationTests.NewAttestationTestHelper(t, ctx, platform) - err := setup.CreateDataProvider(ctx, platform, deployer.Address()) + + // Give balance for market creation and trading + err := giveBalance(ctx, platform, deployer.Address(), "500000000000000000000") + require.NoError(t, err) + + // Create data provider + err = setup.CreateDataProvider(ctx, platform, deployer.Address()) require.NoError(t, err) streamID := "stvalidationtest0000000000000000" @@ -783,24 +849,27 @@ func testSettleMarketValidationIntegration(t *testing.T) func(context.Context, * require.NoError(t, err) var requestTxID string - var attestationHash []byte engineCtx = helper.NewEngineContext() _, err = platform.Engine.Call(engineCtx, platform.DB, "", "request_attestation", []any{dataProvider, streamID, "get_record", argsBytes, false, nil}, func(row *common.Row) error { requestTxID = row.Values[0].(string) - attestationHash = append([]byte(nil), row.Values[1].([]byte)...) + // attestationHash not needed - using queryComponents return nil }) require.NoError(t, err) helper.SignAttestation(requestTxID) + // Encode query components for create_market + queryComponents, err := encodeQueryComponentsForTests(dataProvider, streamID, "get_record", argsBytes) + require.NoError(t, err) + // Create market var queryID int engineCtx = helper.NewEngineContext() engineCtx.TxContext.BlockContext.Timestamp = 50 _, err = platform.Engine.Call(engineCtx, platform.DB, "", "create_market", - []any{testExtensionName, attestationHash, int64(100), int64(5), int64(1)}, + []any{testExtensionName, queryComponents, int64(100), int64(5), int64(1)}, func(row *common.Row) error { queryID = int(row.Values[0].(int64)) return nil @@ -850,7 +919,13 @@ func testSettleMarketBlockedByBinaryParityViolation(t *testing.T) func(context.C platform.Deployer = deployer.Bytes() helper := attestationTests.NewAttestationTestHelper(t, ctx, platform) - err := setup.CreateDataProvider(ctx, platform, deployer.Address()) + + // Give balance for market creation and trading + err := giveBalance(ctx, platform, deployer.Address(), "500000000000000000000") + require.NoError(t, err) + + // Create data provider + err = setup.CreateDataProvider(ctx, platform, deployer.Address()) require.NoError(t, err) streamID := "stparityviolation000000000000000" @@ -878,24 +953,27 @@ func testSettleMarketBlockedByBinaryParityViolation(t *testing.T) func(context.C }) var requestTxID string - var attestationHash []byte engineCtx = helper.NewEngineContext() _, err = platform.Engine.Call(engineCtx, platform.DB, "", "request_attestation", []any{dataProvider, streamID, "get_record", argsBytes, false, nil}, func(row *common.Row) error { requestTxID = row.Values[0].(string) - attestationHash = append([]byte(nil), row.Values[1].([]byte)...) + // attestationHash not needed - using queryComponents return nil }) require.NoError(t, err) helper.SignAttestation(requestTxID) + // Encode query components for create_market + queryComponents, err := encodeQueryComponentsForTests(dataProvider, streamID, "get_record", argsBytes) + require.NoError(t, err) + // Create market var queryID int engineCtx = helper.NewEngineContext() engineCtx.TxContext.BlockContext.Timestamp = 50 _, err = platform.Engine.Call(engineCtx, platform.DB, "", "create_market", - []any{testExtensionName, attestationHash, int64(100), int64(5), int64(1)}, + []any{testExtensionName, queryComponents, int64(100), int64(5), int64(1)}, func(row *common.Row) error { queryID = int(row.Values[0].(int64)) return nil @@ -986,7 +1064,13 @@ func testSettleMarketBlockedByCollateralMismatch(t *testing.T) func(context.Cont platform.Deployer = deployer.Bytes() helper := attestationTests.NewAttestationTestHelper(t, ctx, platform) - err := setup.CreateDataProvider(ctx, platform, deployer.Address()) + + // Give balance for market creation and trading + err := giveBalance(ctx, platform, deployer.Address(), "500000000000000000000") + require.NoError(t, err) + + // Create data provider + err = setup.CreateDataProvider(ctx, platform, deployer.Address()) require.NoError(t, err) // Initialize ERC20 for placing orders @@ -1021,13 +1105,12 @@ func testSettleMarketBlockedByCollateralMismatch(t *testing.T) func(context.Cont }) require.NoError(t, err) var requestTxID1 string - var attestationHash1 []byte engineCtx = helper.NewEngineContext() res1, err := platform.Engine.Call(engineCtx, platform.DB, "", "request_attestation", []any{dataProvider, streamID1, "get_record", argsBytes1, false, nil}, func(row *common.Row) error { requestTxID1 = row.Values[0].(string) - attestationHash1 = append([]byte(nil), row.Values[1].([]byte)...) + // attestation hash not needed - using queryComponents return nil }) require.NoError(t, err) @@ -1037,6 +1120,11 @@ func testSettleMarketBlockedByCollateralMismatch(t *testing.T) func(context.Cont } helper.SignAttestation(requestTxID1) + // Encode query components for market 1 + queryComponents1, err := encodeQueryComponentsForTests( + dataProvider, streamID1, "get_record", argsBytes1) + require.NoError(t, err) + // ===== STREAM 2 ===== streamID2 := "stcollateralmismatch200000000000" engineCtx = helper.NewEngineContext() @@ -1059,13 +1147,12 @@ func testSettleMarketBlockedByCollateralMismatch(t *testing.T) func(context.Cont }) require.NoError(t, err) var requestTxID2 string - var attestationHash2 []byte engineCtx = helper.NewEngineContext() res2, err := platform.Engine.Call(engineCtx, platform.DB, "", "request_attestation", []any{dataProvider, streamID2, "get_record", argsBytes2, false, nil}, func(row *common.Row) error { requestTxID2 = row.Values[0].(string) - attestationHash2 = append([]byte(nil), row.Values[1].([]byte)...) + // attestation hash not needed - using queryComponents return nil }) require.NoError(t, err) @@ -1075,13 +1162,18 @@ func testSettleMarketBlockedByCollateralMismatch(t *testing.T) func(context.Cont } helper.SignAttestation(requestTxID2) + // Encode query components for market 2 + queryComponents2, err := encodeQueryComponentsForTests( + dataProvider, streamID2, "get_record", argsBytes2) + require.NoError(t, err) + // ===== CREATE MARKETS ===== var queryID1, queryID2 int engineCtx = helper.NewEngineContext() engineCtx.TxContext.BlockContext.Timestamp = 50 _, err = platform.Engine.Call(engineCtx, platform.DB, "", "create_market", - []any{testExtensionName, attestationHash1, int64(100), int64(5), int64(1)}, + []any{testExtensionName, queryComponents1, int64(100), int64(5), int64(1)}, func(row *common.Row) error { queryID1 = int(row.Values[0].(int64)) return nil @@ -1091,7 +1183,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{testExtensionName, attestationHash2, int64(100), int64(5), int64(1)}, + []any{testExtensionName, queryComponents2, int64(100), int64(5), int64(1)}, func(row *common.Row) error { queryID2 = int(row.Values[0].(int64)) return nil diff --git a/tests/streams/order_book/split_limit_order_test.go b/tests/streams/order_book/split_limit_order_test.go index 1d8f51bde..ca969d978 100644 --- a/tests/streams/order_book/split_limit_order_test.go +++ b/tests/streams/order_book/split_limit_order_test.go @@ -4,7 +4,6 @@ package order_book import ( "context" - "crypto/sha256" "math/big" "testing" "time" @@ -83,11 +82,12 @@ func testSplitOrderMarketSettled(t *testing.T) func(ctx context.Context, platfor require.NoError(t, err) // Create market in the future (valid), then we'll manually settle it - queryHash := sha256.Sum256([]byte("test_market_split_settled")) + queryComponents, err := encodeQueryComponentsForTests(userAddr.Address(), "sttest00000000000000000000000018", "get_record", []byte{0x01}) + require.NoError(t, err) settleTime := time.Now().Add(24 * time.Hour).Unix() var marketID int64 - err = callCreateMarket(ctx, platform, &userAddr, queryHash[:], settleTime, 5, 20, func(row *common.Row) error { + err = callCreateMarket(ctx, platform, &userAddr, queryComponents, settleTime, 5, 20, func(row *common.Row) error { marketID = row.Values[0].(int64) return nil }) @@ -149,11 +149,12 @@ func testSplitOrderInvalidPrice(t *testing.T) func(ctx context.Context, platform require.NoError(t, err) // Create market - queryHash := sha256.Sum256([]byte("test_market_split_price")) + queryComponents, err := encodeQueryComponentsForTests(userAddr.Address(), "sttest00000000000000000000000019", "get_record", []byte{0x01}) + require.NoError(t, err) settleTime := time.Now().Add(24 * time.Hour).Unix() var marketID int64 - err = callCreateMarket(ctx, platform, &userAddr, queryHash[:], settleTime, 5, 20, func(row *common.Row) error { + err = callCreateMarket(ctx, platform, &userAddr, queryComponents, settleTime, 5, 20, func(row *common.Row) error { marketID = row.Values[0].(int64) return nil }) @@ -192,11 +193,12 @@ func testSplitOrderInvalidAmount(t *testing.T) func(ctx context.Context, platfor require.NoError(t, err) // Create market - queryHash := sha256.Sum256([]byte("test_market_split_amount")) + queryComponents, err := encodeQueryComponentsForTests(userAddr.Address(), "sttest00000000000000000000000020", "get_record", []byte{0x01}) + require.NoError(t, err) settleTime := time.Now().Add(24 * time.Hour).Unix() var marketID int64 - err = callCreateMarket(ctx, platform, &userAddr, queryHash[:], settleTime, 5, 20, func(row *common.Row) error { + err = callCreateMarket(ctx, platform, &userAddr, queryComponents, settleTime, 5, 20, func(row *common.Row) error { marketID = row.Values[0].(int64) return nil }) @@ -235,11 +237,12 @@ func testSplitOrderInsufficientBalance(t *testing.T) func(ctx context.Context, p require.NoError(t, err) // Create market - queryHash := sha256.Sum256([]byte("test_market_split_insufficient_balance")) + queryComponents, err := encodeQueryComponentsForTests(userAddr.Address(), "sttest00000000000000000000000021", "get_record", []byte{0x01}) + require.NoError(t, err) settleTime := time.Now().Add(24 * time.Hour).Unix() var marketID int64 - err = callCreateMarket(ctx, platform, &userAddr, queryHash[:], settleTime, 5, 20, func(row *common.Row) error { + err = callCreateMarket(ctx, platform, &userAddr, queryComponents, settleTime, 5, 20, func(row *common.Row) error { marketID = row.Values[0].(int64) return nil }) @@ -268,11 +271,12 @@ func testSplitOrderSuccessful(t *testing.T) func(ctx context.Context, platform * require.NoError(t, err) // Create market - queryHash := sha256.Sum256([]byte("test_market_split_success")) + queryComponents, err := encodeQueryComponentsForTests(userAddr.Address(), "sttest00000000000000000000000022", "get_record", []byte{0x01}) + require.NoError(t, err) settleTime := time.Now().Add(24 * time.Hour).Unix() var marketID int64 - err = callCreateMarket(ctx, platform, &userAddr, queryHash[:], settleTime, 5, 20, func(row *common.Row) error { + err = callCreateMarket(ctx, platform, &userAddr, queryComponents, settleTime, 5, 20, func(row *common.Row) error { marketID = row.Values[0].(int64) return nil }) @@ -321,11 +325,12 @@ func testSplitOrderMultipleDifferentPrices(t *testing.T) func(ctx context.Contex require.NoError(t, err) // Create market - queryHash := sha256.Sum256([]byte("test_market_split_multi_prices")) + queryComponents, err := encodeQueryComponentsForTests(userAddr.Address(), "sttest00000000000000000000000023", "get_record", []byte{0x01}) + require.NoError(t, err) settleTime := time.Now().Add(24 * time.Hour).Unix() var marketID int64 - err = callCreateMarket(ctx, platform, &userAddr, queryHash[:], settleTime, 5, 20, func(row *common.Row) error { + err = callCreateMarket(ctx, platform, &userAddr, queryComponents, settleTime, 5, 20, func(row *common.Row) error { marketID = row.Values[0].(int64) return nil }) @@ -386,11 +391,12 @@ func testSplitOrderMultipleSamePrice(t *testing.T) func(ctx context.Context, pla require.NoError(t, err) // Create market - queryHash := sha256.Sum256([]byte("test_market_split_same_price")) + queryComponents, err := encodeQueryComponentsForTests(userAddr.Address(), "sttest00000000000000000000000024", "get_record", []byte{0x01}) + require.NoError(t, err) settleTime := time.Now().Add(24 * time.Hour).Unix() var marketID int64 - err = callCreateMarket(ctx, platform, &userAddr, queryHash[:], settleTime, 5, 20, func(row *common.Row) error { + err = callCreateMarket(ctx, platform, &userAddr, queryComponents, settleTime, 5, 20, func(row *common.Row) error { marketID = row.Values[0].(int64) return nil }) @@ -448,11 +454,12 @@ func testSplitOrderBalanceChanges(t *testing.T) func(ctx context.Context, platfo require.NoError(t, err) // Create market - queryHash := sha256.Sum256([]byte("test_market_split_balance_changes")) + queryComponents, err := encodeQueryComponentsForTests(userAddr.Address(), "sttest00000000000000000000000025", "get_record", []byte{0x01}) + require.NoError(t, err) settleTime := time.Now().Add(24 * time.Hour).Unix() var marketID int64 - err = callCreateMarket(ctx, platform, &userAddr, queryHash[:], settleTime, 5, 20, func(row *common.Row) error { + err = callCreateMarket(ctx, platform, &userAddr, queryComponents, settleTime, 5, 20, func(row *common.Row) error { marketID = row.Values[0].(int64) return nil }) @@ -495,11 +502,12 @@ func testSplitOrderPositionVerification(t *testing.T) func(ctx context.Context, require.NoError(t, err) // Create market - queryHash := sha256.Sum256([]byte("test_market_split_verify")) + queryComponents, err := encodeQueryComponentsForTests(userAddr.Address(), "sttest00000000000000000000000026", "get_record", []byte{0x01}) + require.NoError(t, err) settleTime := time.Now().Add(24 * time.Hour).Unix() var marketID int64 - err = callCreateMarket(ctx, platform, &userAddr, queryHash[:], settleTime, 5, 20, func(row *common.Row) error { + err = callCreateMarket(ctx, platform, &userAddr, queryComponents, settleTime, 5, 20, func(row *common.Row) error { marketID = row.Values[0].(int64) return nil }) diff --git a/tests/streams/order_book/test_helpers_orderbook.go b/tests/streams/order_book/test_helpers_orderbook.go new file mode 100644 index 000000000..4cd4545bf --- /dev/null +++ b/tests/streams/order_book/test_helpers_orderbook.go @@ -0,0 +1,73 @@ +//go:build kwiltest + +package order_book + +import ( + "fmt" + + gethAbi "github.com/ethereum/go-ethereum/accounts/abi" + gethCommon "github.com/ethereum/go-ethereum/common" + "github.com/trufnetwork/node/extensions/tn_utils" +) + +// encodeQueryComponentsForTests encodes query components using ABI format for testing +// This is a shared helper for all order_book tests +// If argsBytes is provided, it uses those args; otherwise creates default args for get_record +func encodeQueryComponentsForTests(dataProvider, streamID, actionID string, argsBytes []byte) ([]byte, error) { + // If args not provided, encode default args for get_record action + if argsBytes == nil { + // For get_record action, args are: (data_provider, stream_id, from, to, frozen_at, use_cache) + var err error + argsBytes, err = tn_utils.EncodeActionArgs([]any{ + dataProvider, // data_provider (TEXT) + streamID, // stream_id (TEXT) + int64(0), // from (INT8) + int64(999999999), // to (INT8) - far future + nil, // frozen_at (INT8, nullable) + false, // use_cache (BOOL) + }) + if err != nil { + return nil, fmt.Errorf("failed to encode action args: %w", err) + } + } + + // Now encode the full query components as ABI + addressType, err := gethAbi.NewType("address", "", nil) + if err != nil { + return nil, fmt.Errorf("failed to create address type: %w", err) + } + bytes32Type, err := gethAbi.NewType("bytes32", "", nil) + if err != nil { + return nil, fmt.Errorf("failed to create bytes32 type: %w", err) + } + stringType, err := gethAbi.NewType("string", "", nil) + if err != nil { + return nil, fmt.Errorf("failed to create string type: %w", err) + } + bytesType, err := gethAbi.NewType("bytes", "", nil) + if err != nil { + return nil, fmt.Errorf("failed to create bytes type: %w", err) + } + + abiArgs := gethAbi.Arguments{ + {Type: addressType, Name: "data_provider"}, + {Type: bytes32Type, Name: "stream_id"}, + {Type: stringType, Name: "action_id"}, + {Type: bytesType, Name: "args"}, + } + + // Convert data provider to address + dpAddr := gethCommon.HexToAddress(dataProvider) + + // Convert stream ID to bytes32 (pad with zeros on the right) + var streamIDBytes [32]byte + copy(streamIDBytes[:], []byte(streamID)) + + // Pack the ABI + encoded, err := abiArgs.Pack(dpAddr, streamIDBytes, actionID, argsBytes) + if err != nil { + return nil, fmt.Errorf("failed to pack ABI: %w", err) + } + + return encoded, nil +} diff --git a/tests/streams/order_book/validate_market_collateral_test.go b/tests/streams/order_book/validate_market_collateral_test.go index 3724676f5..327b8feac 100644 --- a/tests/streams/order_book/validate_market_collateral_test.go +++ b/tests/streams/order_book/validate_market_collateral_test.go @@ -181,11 +181,11 @@ func testValidMarketWithPositions(t *testing.T) func(context.Context, *kwilTesti require.NoError(t, err) // Create market - queryHash := [32]byte{} - copy(queryHash[:], []byte("test_positions_validation")) + queryComponents, err := encodeQueryComponentsForTests(userAddr.Address(), "sttest00000000000000000000000068", "get_record", []byte{0x01}) + require.NoError(t, err) var marketID int64 - err = callCreateMarket(ctx, platform, &userAddr, queryHash[:], + err = callCreateMarket(ctx, platform, &userAddr, queryComponents, 9999999999, 5, 20, func(row *common.Row) error { marketID = row.Values[0].(int64) return nil @@ -375,13 +375,15 @@ func validateMarket( // createMarketWithHelper creates a market using the existing helper func createMarketWithHelper(ctx context.Context, platform *kwilTesting.Platform, signer *util.EthereumAddress) (int, error) { - // Create a proper 32-byte hash - queryHash := [32]byte{} - copy(queryHash[:], []byte("test_query_hash_validation_test")) + // Create proper query components + queryComponents, err := encodeQueryComponentsForTests(signer.Address(), "sttest00000000000000000000000069", "get_record", []byte{0x01}) + if err != nil { + return 0, err + } // Use existing createMarket helper from market_creation_test.go var marketID int64 - err := callCreateMarket(ctx, platform, signer, queryHash[:], + err = callCreateMarket(ctx, platform, signer, queryComponents, 9999999999, 5, 20, func(row *common.Row) error { marketID = row.Values[0].(int64) return nil diff --git a/tests/streams/utils/erc20/helper.go b/tests/streams/utils/erc20/helper.go index 5ba6e3c40..c5a914294 100644 --- a/tests/streams/utils/erc20/helper.go +++ b/tests/streams/utils/erc20/helper.go @@ -46,7 +46,33 @@ func CreditUserBalance(ctx context.Context, platform *kwilTesting.Platform, exte } // GetUserBalance queries the user's current balance via the extension. +// IMPORTANT: The balance() action requires TWO parameters: instance_id (UUID) and user_address (text). +// We compute the instance ID deterministically from the extension configuration. func GetUserBalance(ctx context.Context, platform *kwilTesting.Platform, extensionAlias, userAddr string) (string, error) { + // CRITICAL: Get instance ID from extension configuration + // The balance() precompile signature is: balance(id UUID, user TEXT) + // We need to pass the instance ID as the first parameter + + // For test extensions, we need to know the chain and escrow address + // Extract from known test configurations + var chainName, escrowAddr string + switch extensionAlias { + case "hoodi_tt": + chainName = "hoodi" + escrowAddr = "0x878d6aaeb6e746033f50b8dc268d54b4631554e7" + case "hoodi_tt2": + chainName = "hoodi" + escrowAddr = "0x80D9B3b6941367917816d36748C88B303f7F1415" + case "sepolia_bridge": + chainName = "sepolia" + escrowAddr = "0x80d9b3b6941367917816d36748c88b303f7f1415" + default: + return "", fmt.Errorf("unknown extension alias: %s", extensionAlias) + } + + // Compute instance ID deterministically (same as ForTestingForceSyncInstance) + instanceID := erc20shim.ForTestingGetInstanceID(chainName, escrowAddr) + txCtx := &common.TxContext{ Ctx: ctx, BlockContext: &common.BlockContext{Height: 1}, @@ -57,7 +83,8 @@ func GetUserBalance(ctx context.Context, platform *kwilTesting.Platform, extensi engCtx := &common.EngineContext{TxContext: txCtx, OverrideAuthz: true} var balance string - r, err := platform.Engine.Call(engCtx, platform.DB, extensionAlias, "balance", []any{userAddr}, func(row *common.Row) error { + // Pass instance ID as FIRST parameter, user address as SECOND + r, err := platform.Engine.Call(engCtx, platform.DB, "kwil_erc20_meta", "balance", []any{instanceID, userAddr}, func(row *common.Row) error { if len(row.Values) != 1 { return fmt.Errorf("expected 1 column, got %d", len(row.Values)) }