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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 16 additions & 6 deletions extensions/tn_settlement/settlement_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
4 changes: 2 additions & 2 deletions internal/migrations/001-common-actions.sql
Original file line number Diff line number Diff line change
Expand Up @@ -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 := 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)');
}

ethereum_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 =====
Expand Down
4 changes: 2 additions & 2 deletions internal/migrations/003-primitive-insertion.sql
Original file line number Diff line number Diff line change
Expand Up @@ -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 := 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)');
}

ethereum_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 =====
Expand Down
4 changes: 2 additions & 2 deletions internal/migrations/004-composed-taxonomy.sql
Original file line number Diff line number Diff line change
Expand Up @@ -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 := 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)');
}

ethereum_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 =====
Expand Down
154 changes: 25 additions & 129 deletions internal/migrations/010-get-latest-write-activity.sql
Original file line number Diff line number Diff line change
@@ -1,91 +1,30 @@
/**
* 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 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;
*/
Comment thread
coderabbitai[bot] marked this conversation as resolved.

-- 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,
Expand All @@ -99,10 +38,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);
}
}

Expand Down Expand Up @@ -158,46 +97,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;
}
};
4 changes: 2 additions & 2 deletions internal/migrations/024-attestation-actions.sql
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ CREATE OR REPLACE ACTION request_attestation(
}
}

$caller_balance := ethereum_bridge.balance(@caller);
$caller_balance := hoodi_tt.balance(@caller);

IF $caller_balance < $attestation_fee {
ERROR('Insufficient balance for attestation. Required: 40 TRUF');
Expand All @@ -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);
hoodi_tt.transfer($leader_addr, $attestation_fee);
-- ===== END FEE COLLECTION =====

-- Get current block height
Expand Down
4 changes: 2 additions & 2 deletions internal/migrations/erc20-bridge/001-actions.sql
Original file line number Diff line number Diff line change
Expand Up @@ -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: ' ||
Expand All @@ -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));
Expand Down
28 changes: 11 additions & 17 deletions internal/migrations/migration.go
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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))
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

return statements
Expand Down
Loading
Loading