From 0cfc1803826ca4c3bf9afd683dad86f1145198f6 Mon Sep 17 00:00:00 2001 From: Michael Buntarman Date: Mon, 11 May 2026 18:15:58 +0700 Subject: [PATCH 1/4] fix: unknown sender and tx ID on explorer transactions --- internal/migrations/001-common-actions.sql | 4 +- .../migrations/003-primitive-insertion.sql | 4 +- internal/migrations/004-composed-taxonomy.sql | 4 +- .../010-get-latest-write-activity.sql | 144 ++---------------- .../migrations/024-attestation-actions.sql | 4 +- internal/migrations/migration.go | 28 ++-- .../order_book/settlement_payout_test.go | 6 +- .../streams/transaction_events_ledger_test.go | 35 +---- .../streams/utils/feefund/feefund_kwiltest.go | 8 +- tests/streams/withdrawal_fee_test.go | 2 +- 10 files changed, 44 insertions(+), 195 deletions(-) diff --git a/internal/migrations/001-common-actions.sql b/internal/migrations/001-common-actions.sql index a87d4d56e..a6538382c 100644 --- a/internal/migrations/001-common-actions.sql +++ b/internal/migrations/001-common-actions.sql @@ -101,14 +101,14 @@ CREATE OR REPLACE ACTION create_streams( } $leader_hex := encode(@leader_sender, 'hex')::TEXT; - $caller_balance := ethereum_bridge.balance(@caller); + $caller_balance := sepolia_bridge.balance(@caller); IF $caller_balance < $total_fee { -- Derive human-readable fee from $total_fee ERROR('Insufficient balance for stream creation. Required: ' || ($total_fee / 1000000000000000000::NUMERIC(78, 0))::TEXT || ' TRUF for ' || $num_streams::TEXT || ' stream(s)'); } - ethereum_bridge.transfer($leader_hex, $total_fee); + sepolia_bridge.transfer($leader_hex, $total_fee); $fee_total := $total_fee; $fee_recipient := '0x' || $leader_hex; -- ===== END FEE COLLECTION ===== diff --git a/internal/migrations/003-primitive-insertion.sql b/internal/migrations/003-primitive-insertion.sql index f4428a39c..6faa7097c 100644 --- a/internal/migrations/003-primitive-insertion.sql +++ b/internal/migrations/003-primitive-insertion.sql @@ -72,14 +72,14 @@ CREATE OR REPLACE ACTION insert_records( } $leader_hex := encode(@leader_sender, 'hex')::TEXT; - $caller_balance := ethereum_bridge.balance(@caller); + $caller_balance := sepolia_bridge.balance(@caller); IF $caller_balance < $total_fee { -- Derive human-readable fee from $total_fee ERROR('Insufficient balance for write fee. Required: ' || ($total_fee / 1000000000000000000::NUMERIC(78, 0))::TEXT || ' TRUF for ' || $num_records::TEXT || ' record(s)'); } - ethereum_bridge.transfer($leader_hex, $total_fee); + sepolia_bridge.transfer($leader_hex, $total_fee); $fee_total := $total_fee; $fee_recipient := '0x' || $leader_hex; -- ===== END FEE COLLECTION ===== diff --git a/internal/migrations/004-composed-taxonomy.sql b/internal/migrations/004-composed-taxonomy.sql index 943b55702..82ce85a16 100644 --- a/internal/migrations/004-composed-taxonomy.sql +++ b/internal/migrations/004-composed-taxonomy.sql @@ -53,14 +53,14 @@ CREATE OR REPLACE ACTION insert_taxonomy( } $leader_hex := encode(@leader_sender, 'hex')::TEXT; - $caller_balance := ethereum_bridge.balance(@caller); + $caller_balance := sepolia_bridge.balance(@caller); IF $caller_balance < $total_fee { -- Derive human-readable fee from $total_fee ERROR('Insufficient balance for taxonomies creation. Required: ' || ($total_fee / 1000000000000000000::NUMERIC(78, 0))::TEXT || ' TRUF for ' || $num_children::TEXT || ' child stream(s)'); } - ethereum_bridge.transfer($leader_hex, $total_fee); + sepolia_bridge.transfer($leader_hex, $total_fee); $fee_total := $total_fee; $fee_recipient := '0x' || $leader_hex; -- ===== END FEE COLLECTION ===== diff --git a/internal/migrations/010-get-latest-write-activity.sql b/internal/migrations/010-get-latest-write-activity.sql index 082d66de6..5803a26f5 100644 --- a/internal/migrations/010-get-latest-write-activity.sql +++ b/internal/migrations/010-get-latest-write-activity.sql @@ -1,91 +1,20 @@ /** - * Transaction history views + * Transaction history view * - * get_last_transactions_v1 - legacy implementation (no fee/caller metadata) - * get_last_transactions_v2 - ledger-backed implementation (redefined later in migration 027) - * get_last_transactions - temporary wrapper returning the v2 signature but - * still sourcing data from v1. This will be replaced - * with v2 once callers migrate. + * `get_last_transactions` returns the most recent ledger-backed transactions, + * optionally filtered to those involving a given wallet (caller, fee_recipient, + * or distribution recipient). One row per fee distribution; transactions + * without distributions return a single row with NULL distribution fields. + * + * Parameter name `$data_provider` is preserved from the previous wrapper + * signature for SDK backward compatibility. The earlier `_v1` and `_v2` + * variants have been retired — see `maintenance/` for the operational DROP + * script that removes them from already-migrated databases. */ --- This is a legacy implementation that is no longer used. It will be removed in a future migration. -CREATE OR REPLACE ACTION get_last_transactions_v1( +CREATE OR REPLACE ACTION get_last_transactions( $data_provider TEXT, - $limit_size INT8 -) PUBLIC VIEW RETURNS TABLE( - created_at INT8, - method TEXT -) { - $normalized_provider TEXT := NULL; - IF COALESCE($data_provider, '') != '' { - $normalized_provider := LOWER($data_provider); - } - - IF $limit_size IS NULL { - $limit_size := 6; - } - IF $limit_size <= 0 { - $limit_size := 6; - } - - IF $limit_size > 100 { - ERROR('Limit size cannot exceed 100'); - } - - RETURN SELECT created_at, method FROM ( - SELECT created_at, method, ROW_NUMBER() OVER (PARTITION BY created_at ORDER BY priority ASC) AS rn FROM ( - SELECT s.created_at, 'deployStream' AS method, 1 AS priority - FROM ( - SELECT DISTINCT s.created_at - FROM streams s - JOIN data_providers dp ON s.data_provider_id = dp.id - WHERE COALESCE($normalized_provider, '') = '' OR dp.address = $normalized_provider - ORDER BY s.created_at DESC - LIMIT $limit_size - ) s - UNION ALL - SELECT pe.created_at, 'insertRecords', 2 - FROM ( - SELECT DISTINCT pe.created_at - FROM primitive_events pe - JOIN streams s ON pe.stream_ref = s.id - JOIN data_providers dp ON s.data_provider_id = dp.id - WHERE COALESCE($normalized_provider, '') = '' OR dp.address = $normalized_provider - ORDER BY pe.created_at DESC - LIMIT $limit_size - ) pe - UNION ALL - SELECT t.created_at, 'setTaxonomies', 3 - FROM ( - SELECT DISTINCT t.created_at - FROM taxonomies t - JOIN streams s ON t.stream_ref = s.id - JOIN data_providers dp ON s.data_provider_id = dp.id - WHERE COALESCE($normalized_provider, '') = '' OR dp.address = $normalized_provider - ORDER BY t.created_at DESC - LIMIT $limit_size - ) t - UNION ALL - SELECT m.created_at, 'setMetadata', 4 - FROM ( - SELECT DISTINCT m.created_at - FROM metadata m - JOIN streams s ON m.stream_ref = s.id - JOIN data_providers dp ON s.data_provider_id = dp.id - WHERE COALESCE($normalized_provider, '') = '' OR dp.address = $normalized_provider - ORDER BY m.created_at DESC - LIMIT $limit_size - ) m - ) AS combined - ) AS ranked - WHERE rn = 1 - ORDER BY created_at DESC - LIMIT $limit_size; -}; - -CREATE OR REPLACE ACTION get_last_transactions_v2( - $wallet TEXT, - $limit_size INT8 + $limit_size INT8 ) PUBLIC VIEW RETURNS TABLE( tx_id TEXT, created_at INT8, @@ -99,10 +28,10 @@ CREATE OR REPLACE ACTION get_last_transactions_v2( distribution_amount NUMERIC(78, 0) ) { $normalized_wallet TEXT := NULL; - IF COALESCE($wallet, '') != '' { - $normalized_wallet := LOWER($wallet); + IF COALESCE($data_provider, '') != '' { + $normalized_wallet := LOWER($data_provider); IF NOT check_ethereum_address($normalized_wallet) { - ERROR('Invalid wallet. Must be a valid Ethereum address: ' || $wallet); + ERROR('Invalid wallet. Must be a valid Ethereum address: ' || $data_provider); } } @@ -158,46 +87,3 @@ CREATE OR REPLACE ACTION get_last_transactions_v2( le.tx_id DESC, COALESCE(ted.sequence, 0) ASC; }; - -CREATE OR REPLACE ACTION get_last_transactions( - $data_provider TEXT, - $limit_size INT8 -) PUBLIC VIEW RETURNS TABLE( - tx_id TEXT, - created_at INT8, - method TEXT, - caller TEXT, - fee_amount NUMERIC(78, 0), - fee_recipient TEXT, - metadata TEXT, - fee_distributions TEXT -) { - $normalized_provider TEXT := NULL; - IF COALESCE($data_provider, '') != '' { - $normalized_provider := LOWER($data_provider); - IF NOT check_ethereum_address($normalized_provider) { - ERROR('Invalid data provider address. Must be a valid Ethereum address: ' || $data_provider); - } - } - - $limit_val INT := COALESCE($limit_size, 6); - IF $limit_val <= 0 { - $limit_val := 6; - } - - IF $limit_val > 100 { - ERROR('Limit size cannot exceed 100'); - } - - FOR $row IN get_last_transactions_v1($normalized_provider, $limit_val) { - $fee_distributions TEXT := ''; - RETURN NEXT NULL::TEXT, - $row.created_at, - $row.method, - NULL::TEXT, - NULL::NUMERIC(78, 0), - NULL::TEXT, - NULL::TEXT, - $fee_distributions; - } -}; diff --git a/internal/migrations/024-attestation-actions.sql b/internal/migrations/024-attestation-actions.sql index c75503e59..98e008042 100644 --- a/internal/migrations/024-attestation-actions.sql +++ b/internal/migrations/024-attestation-actions.sql @@ -55,7 +55,7 @@ CREATE OR REPLACE ACTION request_attestation( } } - $caller_balance := ethereum_bridge.balance(@caller); + $caller_balance := sepolia_bridge.balance(@caller); IF $caller_balance < $attestation_fee { ERROR('Insufficient balance for attestation. Required: 40 TRUF'); @@ -67,7 +67,7 @@ CREATE OR REPLACE ACTION request_attestation( ERROR('Leader address not available for fee transfer'); } - ethereum_bridge.transfer($leader_addr, $attestation_fee); + sepolia_bridge.transfer($leader_addr, $attestation_fee); -- ===== END FEE COLLECTION ===== -- Get current block height diff --git a/internal/migrations/migration.go b/internal/migrations/migration.go index f722ccca1..e6684b93f 100644 --- a/internal/migrations/migration.go +++ b/internal/migrations/migration.go @@ -47,16 +47,18 @@ func GetSeedScriptPaths() []string { return seedFilesPaths } -// GetSeedScriptStatements returns migration SQL statements with test-specific replacements. -// This is used for test environments to replace production bridge namespace with test bridge. +// GetSeedScriptStatements returns the dev/test migration SQL statements as-is. // -// Replacement strategy: -// - Production migrations use 'ethereum_bridge' (mainnet) -// - Test environments need 'sepolia_bridge' (testnet) -// - Runtime string replacement avoids duplicate migration files +// Dev source-of-truth lives in the literal `*.sql` files; mainnet overrides +// live in matching `*.prod.sql` files. Each environment's bridge namespace +// (dev: hoodi_tt / hoodi_tt2 / sepolia_bridge; mainnet: eth_truf / eth_usdc) +// appears verbatim in its own file — no runtime placeholder substitution. // -// This implements the approach discussed to avoid maintaining separate test-only override files -// that duplicate logic and can become a bug source if production and test logic diverges. +// (Earlier revisions rewrote `ethereum_bridge` → `sepolia_bridge` here so a +// single `*.sql` could target both environments. That created a placeholder +// that only worked through this loader; `scripts/migrate.sh` applied files +// literally, so any path that bypassed in-process tests hit a missing +// `ethereum_bridge` namespace. The placeholder is now removed.) func GetSeedScriptStatements() []string { var statements []string @@ -89,20 +91,12 @@ func GetSeedScriptStatements() []string { // Sort paths to ensure consistent loading order (0_test_only/ loads after production) sort.Strings(sqlPaths) - // Read each file and apply test-specific replacements for _, path := range sqlPaths { content, err := seedFiles.ReadFile(path) if err != nil { panic("failed to read migration file " + path + ": " + err.Error()) } - - sql := string(content) - - // Replace production bridge namespace with test bridge namespace - // This allows tests to use the same migration logic without duplication - sql = strings.ReplaceAll(sql, "ethereum_bridge", "sepolia_bridge") - - statements = append(statements, sql) + statements = append(statements, string(content)) } return statements diff --git a/tests/streams/order_book/settlement_payout_test.go b/tests/streams/order_book/settlement_payout_test.go index b91524573..4131ea0d7 100644 --- a/tests/streams/order_book/settlement_payout_test.go +++ b/tests/streams/order_book/settlement_payout_test.go @@ -69,9 +69,9 @@ func testWinnerReceivesFullPayout(t *testing.T) func(context.Context, *kwilTesti err = erc20bridge.ForTestingInitializeExtension(ctx, platform) require.NoError(t, err) - // Fund DP on the sepolia bridge (where ethereum_bridge resolves under tests) - // for create_stream (6 TRUF), insert_records (6 TRUF), request_attestation (40 TRUF). - // Give 100 TRUF to leave headroom. + // Fund DP on the sepolia_bridge (the dev TRUF substitute) for + // create_stream (6 TRUF), insert_records (6 TRUF), request_attestation + // (40 TRUF). Give 100 TRUF to leave headroom. err = feefund.EnsureWalletFunded(ctx, platform, dpAddr.Address(), "100000000000000000000") require.NoError(t, err) diff --git a/tests/streams/transaction_events_ledger_test.go b/tests/streams/transaction_events_ledger_test.go index a24390bb2..839529d43 100644 --- a/tests/streams/transaction_events_ledger_test.go +++ b/tests/streams/transaction_events_ledger_test.go @@ -374,21 +374,6 @@ func runTransactionEventsLedgerScenario(t *testing.T) func(ctx context.Context, } require.True(t, foundBonus, "expected to find distribution row for bonus recipient") - legacyRows, err := fetchLegacyLastTransactions(ctx, platform, actor.Address(), userLower, int64(len(expected))) - require.NoError(t, err) - require.NotEmpty(t, legacyRows) - - methodSet := make(map[string]struct{}) - for _, exp := range expected { - methodSet[exp.method] = struct{}{} - } - - for _, row := range legacyRows { - require.Greater(t, row.CreatedAt, int64(0)) - _, ok := methodSet[row.Method] - require.True(t, ok, "unexpected method in legacy view: %s", row.Method) - } - for txID, exp := range expected { eventRow, err := fetchTransactionEvent(ctx, platform, actor.Address(), txID) require.NoError(t, err, "failed to fetch transaction event for %s", txID) @@ -412,11 +397,6 @@ type ledgerExpectation struct { assertMetadata func(meta metadataMap) } -type legacyRow struct { - CreatedAt int64 - Method string -} - type ledgerRow struct { TxID string CreatedAt int64 @@ -508,7 +488,7 @@ func fetchLastTransactions(ctx context.Context, platform *kwilTesting.Platform, rows := make([]ledgerRow, 0, 8) addr := strings.ToLower(address) args := []any{addr, limit} - err := callView(ctx, platform, caller, "get_last_transactions_v2", args, func(row *common.Row) error { + err := callView(ctx, platform, caller, "get_last_transactions", args, func(row *common.Row) error { feeRecipient := "" if row.Values[5] != nil { feeRecipient = strings.ToLower(row.Values[5].(string)) @@ -533,19 +513,6 @@ func fetchLastTransactions(ctx context.Context, platform *kwilTesting.Platform, return rows, err } -func fetchLegacyLastTransactions(ctx context.Context, platform *kwilTesting.Platform, caller string, dataProvider string, limit int64) ([]legacyRow, error) { - rows := make([]legacyRow, 0, 8) - args := []any{dataProvider, limit} - err := callView(ctx, platform, caller, "get_last_transactions_v1", args, func(row *common.Row) error { - rows = append(rows, legacyRow{ - CreatedAt: row.Values[0].(int64), - Method: row.Values[1].(string), - }) - return nil - }) - return rows, err -} - func fetchTransactionEvent(ctx context.Context, platform *kwilTesting.Platform, caller string, txID string) (ledgerRow, error) { var result ledgerRow err := callView(ctx, platform, caller, "get_transaction_event", []any{txID}, func(row *common.Row) error { diff --git a/tests/streams/utils/feefund/feefund_kwiltest.go b/tests/streams/utils/feefund/feefund_kwiltest.go index 18002afd2..0600489ae 100644 --- a/tests/streams/utils/feefund/feefund_kwiltest.go +++ b/tests/streams/utils/feefund/feefund_kwiltest.go @@ -32,9 +32,11 @@ const ( ) const ( - // Test bridge constants — match the alias produced by GetSeedScriptStatements' - // ethereum_bridge → sepolia_bridge replacement and the USE block in - // internal/migrations/erc20-bridge/000-extension.sql. + // Test bridge constants — must match the `sepolia_bridge` USE block in + // internal/migrations/erc20-bridge/000-extension.sql, since the dev + // fee-collection actions (001/003/004/024) call sepolia_bridge.balance() + // and sepolia_bridge.transfer() directly. (Mainnet override calls + // eth_truf via the matching *.prod.sql files.) testFundingChain = "sepolia" testFundingEscrow = "0x502430eD0BbE0f230215870c9C2853e126eE5Ae3" testFundingERC20 = "0x2222222222222222222222222222222222222222" diff --git a/tests/streams/withdrawal_fee_test.go b/tests/streams/withdrawal_fee_test.go index cc816685a..b5b852d6e 100644 --- a/tests/streams/withdrawal_fee_test.go +++ b/tests/streams/withdrawal_fee_test.go @@ -276,7 +276,7 @@ func callWithdrawalAction(ctx context.Context, platform *kwilTesting.Platform, s engineCtx, platform.DB, "", - "sepolia_bridge_tokens", // Test-specific action (ethereum_bridge_tokens also becomes this in tests) + "sepolia_bridge_tokens", // Dev/test withdrawal action — mainnet override is eth_truf_bridge_tokens []any{recipient, amount}, func(row *common.Row) error { return nil }, ) From bf8f95a6931e5c7bbf004b389ce93f1235a2e0af Mon Sep 17 00:00:00 2001 From: Michael Buntarman Date: Mon, 11 May 2026 18:37:54 +0700 Subject: [PATCH 2/4] chore: bridge naming for CI --- internal/migrations/001-common-actions.sql | 4 +-- .../migrations/003-primitive-insertion.sql | 4 +-- internal/migrations/004-composed-taxonomy.sql | 4 +-- .../migrations/024-attestation-actions.sql | 4 +-- scripts/generate_prod_migrations.py | 25 +++++++++++-------- 5 files changed, 22 insertions(+), 19 deletions(-) diff --git a/internal/migrations/001-common-actions.sql b/internal/migrations/001-common-actions.sql index a6538382c..58927c0ba 100644 --- a/internal/migrations/001-common-actions.sql +++ b/internal/migrations/001-common-actions.sql @@ -101,14 +101,14 @@ CREATE OR REPLACE ACTION create_streams( } $leader_hex := encode(@leader_sender, 'hex')::TEXT; - $caller_balance := sepolia_bridge.balance(@caller); + $caller_balance := hoodi_tt.balance(@caller); IF $caller_balance < $total_fee { -- Derive human-readable fee from $total_fee ERROR('Insufficient balance for stream creation. Required: ' || ($total_fee / 1000000000000000000::NUMERIC(78, 0))::TEXT || ' TRUF for ' || $num_streams::TEXT || ' stream(s)'); } - sepolia_bridge.transfer($leader_hex, $total_fee); + hoodi_tt.transfer($leader_hex, $total_fee); $fee_total := $total_fee; $fee_recipient := '0x' || $leader_hex; -- ===== END FEE COLLECTION ===== diff --git a/internal/migrations/003-primitive-insertion.sql b/internal/migrations/003-primitive-insertion.sql index 6faa7097c..62775dc1e 100644 --- a/internal/migrations/003-primitive-insertion.sql +++ b/internal/migrations/003-primitive-insertion.sql @@ -72,14 +72,14 @@ CREATE OR REPLACE ACTION insert_records( } $leader_hex := encode(@leader_sender, 'hex')::TEXT; - $caller_balance := sepolia_bridge.balance(@caller); + $caller_balance := hoodi_tt.balance(@caller); IF $caller_balance < $total_fee { -- Derive human-readable fee from $total_fee ERROR('Insufficient balance for write fee. Required: ' || ($total_fee / 1000000000000000000::NUMERIC(78, 0))::TEXT || ' TRUF for ' || $num_records::TEXT || ' record(s)'); } - sepolia_bridge.transfer($leader_hex, $total_fee); + hoodi_tt.transfer($leader_hex, $total_fee); $fee_total := $total_fee; $fee_recipient := '0x' || $leader_hex; -- ===== END FEE COLLECTION ===== diff --git a/internal/migrations/004-composed-taxonomy.sql b/internal/migrations/004-composed-taxonomy.sql index 82ce85a16..bf1a3c332 100644 --- a/internal/migrations/004-composed-taxonomy.sql +++ b/internal/migrations/004-composed-taxonomy.sql @@ -53,14 +53,14 @@ CREATE OR REPLACE ACTION insert_taxonomy( } $leader_hex := encode(@leader_sender, 'hex')::TEXT; - $caller_balance := sepolia_bridge.balance(@caller); + $caller_balance := hoodi_tt.balance(@caller); IF $caller_balance < $total_fee { -- Derive human-readable fee from $total_fee ERROR('Insufficient balance for taxonomies creation. Required: ' || ($total_fee / 1000000000000000000::NUMERIC(78, 0))::TEXT || ' TRUF for ' || $num_children::TEXT || ' child stream(s)'); } - sepolia_bridge.transfer($leader_hex, $total_fee); + hoodi_tt.transfer($leader_hex, $total_fee); $fee_total := $total_fee; $fee_recipient := '0x' || $leader_hex; -- ===== END FEE COLLECTION ===== diff --git a/internal/migrations/024-attestation-actions.sql b/internal/migrations/024-attestation-actions.sql index 98e008042..eab44e22f 100644 --- a/internal/migrations/024-attestation-actions.sql +++ b/internal/migrations/024-attestation-actions.sql @@ -55,7 +55,7 @@ CREATE OR REPLACE ACTION request_attestation( } } - $caller_balance := sepolia_bridge.balance(@caller); + $caller_balance := hoodi_tt.balance(@caller); IF $caller_balance < $attestation_fee { ERROR('Insufficient balance for attestation. Required: 40 TRUF'); @@ -67,7 +67,7 @@ CREATE OR REPLACE ACTION request_attestation( ERROR('Leader address not available for fee transfer'); } - sepolia_bridge.transfer($leader_addr, $attestation_fee); + hoodi_tt.transfer($leader_addr, $attestation_fee); -- ===== END FEE COLLECTION ===== -- Get current block height diff --git a/scripts/generate_prod_migrations.py b/scripts/generate_prod_migrations.py index 07f43cb8a..b24e4ef10 100644 --- a/scripts/generate_prod_migrations.py +++ b/scripts/generate_prod_migrations.py @@ -462,22 +462,25 @@ def transform_wrap(source_sql: str) -> tuple[str, list[str]]: def transform_fee(source_sql: str) -> tuple[str, list[str]]: - """Mode C. Emit each action that calls `ethereum_bridge` directly - (TRUF fee-collection pattern), with `ethereum_bridge` → `eth_truf` - substitution. - - No if/else dispatch collapse — `ethereum_bridge` here appears as - unconditional method calls inside fee-collection blocks, not as a - branch in a bridge-dispatch chain. Order-book files where - `ethereum_bridge` was a dispatch branch are handled by mode `core` - instead, which drops those branches entirely. + """Mode C. Emit each action that calls the dev TRUF bridge directly + (fee-collection pattern), substituting it for `eth_truf` on mainnet. + + Recognised dev bridge aliases: `hoodi_tt` (current) and `ethereum_bridge` + (legacy — kept so historical dev migrations regenerate cleanly). + + No if/else dispatch collapse — the bridge call here is an unconditional + method invocation inside a fee-collection block, not a branch in a + bridge-dispatch chain. Order-book files where the bridge appears as a + dispatch branch are handled by mode `core` instead. """ parts: list[str] = [] names: list[str] = [] for name, raw in split_actions(source_sql): - if "ethereum_bridge" not in raw: + if "hoodi_tt" not in raw and "ethereum_bridge" not in raw: continue - substituted = raw.replace("ethereum_bridge", "eth_truf") + substituted = raw.replace("hoodi_tt", "eth_truf").replace( + "ethereum_bridge", "eth_truf" + ) parts.append(substituted.rstrip() + "\n") names.append(name) return "\n".join(parts), names From e2f02db220195d1ba38fef85d8f283be60a80e1a Mon Sep 17 00:00:00 2001 From: Michael Buntarman Date: Mon, 11 May 2026 19:08:44 +0700 Subject: [PATCH 3/4] chore: apply suggestion --- .../migrations/010-get-latest-write-activity.sql | 16 +++++++++++++--- internal/migrations/erc20-bridge/001-actions.sql | 4 ++-- tests/streams/utils/feefund/feefund_kwiltest.go | 12 ++++++------ 3 files changed, 21 insertions(+), 11 deletions(-) diff --git a/internal/migrations/010-get-latest-write-activity.sql b/internal/migrations/010-get-latest-write-activity.sql index 5803a26f5..af90c54ff 100644 --- a/internal/migrations/010-get-latest-write-activity.sql +++ b/internal/migrations/010-get-latest-write-activity.sql @@ -7,9 +7,19 @@ * without distributions return a single row with NULL distribution fields. * * Parameter name `$data_provider` is preserved from the previous wrapper - * signature for SDK backward compatibility. The earlier `_v1` and `_v2` - * variants have been retired — see `maintenance/` for the operational DROP - * script that removes them from already-migrated databases. + * signature so existing SDK call sites continue to work unchanged after the + * binary upgrade — no SDK release needs to land in lockstep. + * + * Retired variants `get_last_transactions_v1` and `get_last_transactions_v2` + * existed in earlier revisions of this file and remain as orphan action + * definitions on already-migrated databases (this file is the only place + * either was defined; trimming it does not auto-drop them). They are not + * removed by an embedded migration on purpose: dropping a callable action is + * a one-way change that should be performed manually per environment, after + * confirming no external caller still depends on the legacy signatures. Apply + * the following manually once per env: + * DROP ACTION IF EXISTS get_last_transactions_v1; + * DROP ACTION IF EXISTS get_last_transactions_v2; */ CREATE OR REPLACE ACTION get_last_transactions( diff --git a/internal/migrations/erc20-bridge/001-actions.sql b/internal/migrations/erc20-bridge/001-actions.sql index e4f2a508d..79b5ea509 100644 --- a/internal/migrations/erc20-bridge/001-actions.sql +++ b/internal/migrations/erc20-bridge/001-actions.sql @@ -103,7 +103,7 @@ CREATE OR REPLACE ACTION sepolia_bridge_tokens($recipient TEXT DEFAULT NULL, $am $withdrawal_amount := $amount::NUMERIC(78, 0); $total_required := $withdrawal_amount + $withdrawal_fee; - $caller_balance := COALESCE(ethereum_bridge.balance(@caller), 0::NUMERIC(78, 0)); + $caller_balance := COALESCE(sepolia_bridge.balance(@caller), 0::NUMERIC(78, 0)); IF $caller_balance < $total_required { ERROR('Insufficient balance for withdrawal. Required: ' || @@ -116,7 +116,7 @@ CREATE OR REPLACE ACTION sepolia_bridge_tokens($recipient TEXT DEFAULT NULL, $am ERROR('Leader address not available for fee transfer'); } $leader_hex TEXT := encode(@leader_sender, 'hex')::TEXT; - ethereum_bridge.transfer($leader_hex, $withdrawal_fee); + sepolia_bridge.transfer($leader_hex, $withdrawal_fee); -- ===== END FEE COLLECTION ===== $bridge_recipient TEXT := LOWER(COALESCE($recipient, @caller)); diff --git a/tests/streams/utils/feefund/feefund_kwiltest.go b/tests/streams/utils/feefund/feefund_kwiltest.go index 0600489ae..01135f73d 100644 --- a/tests/streams/utils/feefund/feefund_kwiltest.go +++ b/tests/streams/utils/feefund/feefund_kwiltest.go @@ -32,13 +32,13 @@ const ( ) const ( - // Test bridge constants — must match the `sepolia_bridge` USE block in + // Test bridge constants — must match the `hoodi_tt` USE block in // internal/migrations/erc20-bridge/000-extension.sql, since the dev - // fee-collection actions (001/003/004/024) call sepolia_bridge.balance() - // and sepolia_bridge.transfer() directly. (Mainnet override calls - // eth_truf via the matching *.prod.sql files.) - testFundingChain = "sepolia" - testFundingEscrow = "0x502430eD0BbE0f230215870c9C2853e126eE5Ae3" + // fee-collection actions (001/003/004/024) call hoodi_tt.balance() and + // hoodi_tt.transfer() directly. (Mainnet override calls eth_truf via the + // matching *.prod.sql files.) + testFundingChain = "hoodi" + testFundingEscrow = "0x878d6aaeb6e746033f50b8dc268d54b4631554e7" testFundingERC20 = "0x2222222222222222222222222222222222222222" ) From 9794c3df105e8c88bb7f93b35a70d0de7755f707 Mon Sep 17 00:00:00 2001 From: Michael Buntarman Date: Mon, 11 May 2026 19:44:49 +0700 Subject: [PATCH 4/4] patch CI --- .../settlement_integration_test.go | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/extensions/tn_settlement/settlement_integration_test.go b/extensions/tn_settlement/settlement_integration_test.go index 5eddf9bd4..29785f543 100644 --- a/extensions/tn_settlement/settlement_integration_test.go +++ b/extensions/tn_settlement/settlement_integration_test.go @@ -87,7 +87,9 @@ func testFindUnsettledMarkets(t *testing.T) func(context.Context, *kwilTesting.P require.NoError(t, err) // Give TRUF balance for market creation fee - err = giveTrufBalance(ctx, platform, deployer.Address(), "10000000000000000000") // 10 TRUF + // Cover one market's worth of fees: create_stream (6) + insert_records + // (6) + request_attestation (40) = 52 TRUF. Fund 100 TRUF for headroom. + err = giveTrufBalance(ctx, platform, deployer.Address(), "100000000000000000000") // 100 TRUF require.NoError(t, err) // Create stream and attestation - returns query_components (ABI-encoded) @@ -163,7 +165,9 @@ func testAttestationExists(t *testing.T) func(context.Context, *kwilTesting.Plat require.NoError(t, err) // Give TRUF balance for market creation fee - err = giveTrufBalance(ctx, platform, deployer.Address(), "10000000000000000000") // 10 TRUF + // Cover one market's worth of fees: create_stream (6) + insert_records + // (6) + request_attestation (40) = 52 TRUF. Fund 100 TRUF for headroom. + err = giveTrufBalance(ctx, platform, deployer.Address(), "100000000000000000000") // 100 TRUF require.NoError(t, err) streamID := "stattexists000000000000000000000" @@ -238,7 +242,9 @@ func testSettleMarketViaAction(t *testing.T) func(context.Context, *kwilTesting. require.NoError(t, err) // Give TRUF balance for market creation fee - err = giveTrufBalance(ctx, platform, deployer.Address(), "10000000000000000000") // 10 TRUF + // Cover one market's worth of fees: create_stream (6) + insert_records + // (6) + request_attestation (40) = 52 TRUF. Fund 100 TRUF for headroom. + err = giveTrufBalance(ctx, platform, deployer.Address(), "100000000000000000000") // 100 TRUF require.NoError(t, err) streamID := "stsettleaction000000000000000000" @@ -350,7 +356,9 @@ func testSkipMarketWithoutAttestation(t *testing.T) func(context.Context, *kwilT require.NoError(t, err) // Give TRUF balance for market creation fee - err = giveTrufBalance(ctx, platform, deployer.Address(), "10000000000000000000") // 10 TRUF + // Cover one market's worth of fees: create_stream (6) + insert_records + // (6) + request_attestation (40) = 52 TRUF. Fund 100 TRUF for headroom. + err = giveTrufBalance(ctx, platform, deployer.Address(), "100000000000000000000") // 100 TRUF require.NoError(t, err) // Create stream and attestation WITHOUT signing (skip the SignAttestation step) @@ -435,8 +443,10 @@ func testMultipleMarketsProcessing(t *testing.T) func(context.Context, *kwilTest err = erc20bridge.ForTestingInitializeExtension(ctx, platform) require.NoError(t, err) - // Give TRUF balance for market creation fees (need 3 markets × 2 TRUF = 6 TRUF minimum) - err = giveTrufBalance(ctx, platform, deployer.Address(), "20000000000000000000") // 20 TRUF + // Cover three markets' worth of fees: 3 × (create_stream 6 + + // insert_records 6 + request_attestation 40) = 156 TRUF. Fund 500 TRUF + // for headroom. + err = giveTrufBalance(ctx, platform, deployer.Address(), "500000000000000000000") // 500 TRUF require.NoError(t, err) // Create 3 markets (settleTime in future relative to BlockContext)