diff --git a/internal/migrations/001-common-actions.prod.sql b/internal/migrations/001-common-actions.prod.sql index ed2a4bda..3169eb91 100644 --- a/internal/migrations/001-common-actions.prod.sql +++ b/internal/migrations/001-common-actions.prod.sql @@ -40,11 +40,10 @@ CREATE OR REPLACE ACTION create_streams( } -- ===== FEE COLLECTION ===== - $num_streams INT := array_length($stream_ids); - - -- Charge 6 TRUF per stream to every caller (no role-based exemption). - $fee_per_stream := 6000000000000000000::NUMERIC(78, 0); -- 6 TRUF with 18 decimals - $total_fee := $fee_per_stream * $num_streams::NUMERIC(78, 0); + -- Flat 1 TRUF per transaction (write-fee policy per issue #3805). + -- Cost is independent of $num_streams: batching N streams in one call + -- charges the same 1 TRUF as creating a single stream. + $total_fee := 1000000000000000000::NUMERIC(78, 0); -- 1 TRUF with 18 decimals IF @leader_sender IS NULL { ERROR('Leader address not available for fee transfer'); @@ -54,8 +53,7 @@ CREATE OR REPLACE ACTION create_streams( $caller_balance := eth_truf.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)'); + ERROR('Insufficient balance for stream creation. Required: 1 TRUF'); } eth_truf.transfer($leader_hex, $total_fee); diff --git a/internal/migrations/001-common-actions.sql b/internal/migrations/001-common-actions.sql index 58927c0b..5eaf2ec8 100644 --- a/internal/migrations/001-common-actions.sql +++ b/internal/migrations/001-common-actions.sql @@ -54,7 +54,7 @@ CREATE OR REPLACE ACTION create_stream( /** * create_streams: Creates multiple streams at once. - * Fee: 6 TRUF per stream created (charged to every caller, no exemptions) + * Fee: 1 TRUF flat per transaction (charged to every caller, no exemptions) * Validates stream_id format, data provider address, and stream type. * Sets default metadata including type, owner, visibility, and readonly keys. * @@ -90,11 +90,10 @@ CREATE OR REPLACE ACTION create_streams( } -- ===== FEE COLLECTION ===== - $num_streams INT := array_length($stream_ids); - - -- Charge 6 TRUF per stream to every caller (no role-based exemption). - $fee_per_stream := 6000000000000000000::NUMERIC(78, 0); -- 6 TRUF with 18 decimals - $total_fee := $fee_per_stream * $num_streams::NUMERIC(78, 0); + -- Flat 1 TRUF per transaction (write-fee policy per issue #3805). + -- Cost is independent of $num_streams: batching N streams in one call + -- charges the same 1 TRUF as creating a single stream. + $total_fee := 1000000000000000000::NUMERIC(78, 0); -- 1 TRUF with 18 decimals IF @leader_sender IS NULL { ERROR('Leader address not available for fee transfer'); @@ -104,8 +103,7 @@ CREATE OR REPLACE ACTION create_streams( $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)'); + ERROR('Insufficient balance for stream creation. Required: 1 TRUF'); } hoodi_tt.transfer($leader_hex, $total_fee); diff --git a/tests/streams/allow_zeros_test.go b/tests/streams/allow_zeros_test.go index 9d0c25cf..cb0d5f2e 100644 --- a/tests/streams/allow_zeros_test.go +++ b/tests/streams/allow_zeros_test.go @@ -325,7 +325,7 @@ func createStreamWithAllowZeros(ctx context.Context, platform *kwilTesting.Platf } // Fund for the universal create_stream fee — mirror setup.UntypedCreateStream. - if err := feefund.EnsureWalletFunded(ctx, platform, addr.Address(), feefund.PerStreamWei); err != nil { + if err := feefund.EnsureWalletFunded(ctx, platform, addr.Address(), feefund.WriteFeeWei); err != nil { return errors.Wrap(err, "fund wallet for create_stream fee") } diff --git a/tests/streams/attestation/request_attestation_fee_test.go b/tests/streams/attestation/request_attestation_fee_test.go index a4a1ea52..081a948a 100644 --- a/tests/streams/attestation/request_attestation_fee_test.go +++ b/tests/streams/attestation/request_attestation_fee_test.go @@ -24,12 +24,16 @@ import ( "github.com/trufnetwork/sdk-go/core/util" ) -// Test constants for attestation fees +// Test constants for attestation fees — bridge configuration must match the +// `hoodi_tt` USE block in erc20-bridge/000-extension.sql since +// `request_attestation` charges its fee through `hoodi_tt.balance` / +// `hoodi_tt.transfer`. Crediting `sepolia_bridge` here would credit the wrong +// instance and the fee check would always see a zero balance. const ( - testAttestationChain = "sepolia" - testAttestationEscrow = "0x502430eD0BbE0f230215870c9C2853e126eE5Ae3" + testAttestationChain = "hoodi" + testAttestationEscrow = "0x878d6aaeb6e746033f50b8dc268d54b4631554e7" testAttestationERC20 = "0x2222222222222222222222222222222222222222" - testAttestationExtensionName = "sepolia_bridge" + testAttestationExtensionName = "hoodi_tt" attestationFeeAmount = "40000000000000000000" // 40 TRUF with 18 decimals ) diff --git a/tests/streams/attestation/test_helpers.go b/tests/streams/attestation/test_helpers.go index 04f55b6f..ae9618b8 100644 --- a/tests/streams/attestation/test_helpers.go +++ b/tests/streams/attestation/test_helpers.go @@ -34,9 +34,12 @@ const ( DefaultBlockHeight = 10 InvalidTxID = "0x0000000000000000000000000000000000000000000000000000000000000000" - // ERC20 instance constants for balance operations - testChain = "sepolia" - testEscrow = "0x502430eD0BbE0f230215870c9C2853e126eE5Ae3" + // ERC20 instance constants for balance operations — must match the + // `hoodi_tt` USE block in erc20-bridge/000-extension.sql so the balance + // credited via ForTestingCreditBalance lands on the same instance the + // fee-collection actions (001/003/004/024) read via `hoodi_tt.balance`. + testChain = "hoodi" + testEscrow = "0x878d6aaeb6e746033f50b8dc268d54b4631554e7" testERC20 = "0x2222222222222222222222222222222222222222" ) diff --git a/tests/streams/primitive_batch_insert_alignment_test.go b/tests/streams/primitive_batch_insert_alignment_test.go index 5c8f97a0..33e7bc6a 100644 --- a/tests/streams/primitive_batch_insert_alignment_test.go +++ b/tests/streams/primitive_batch_insert_alignment_test.go @@ -78,10 +78,10 @@ func testBatchAlignment(t *testing.T) func(ctx context.Context, platform *kwilTe } // Fund deployer for the per-record write fee (universal fee enforcement). - // Mirrors the migration 003 charge of feefund.PerStreamWei (6 TRUF) per record. - feePerRecord, ok := new(big.Int).SetString(feefund.PerStreamWei, 10) + // Mirrors the migration 003 charge of feefund.WriteFeeWei (6 TRUF) per record. + feePerRecord, ok := new(big.Int).SetString(feefund.WriteFeeWei, 10) if !ok { - return errors.Errorf("invalid feefund.PerStreamWei: %s", feefund.PerStreamWei) + return errors.Errorf("invalid feefund.WriteFeeWei: %s", feefund.WriteFeeWei) } totalFee := new(big.Int).Mul(feePerRecord, big.NewInt(int64(len(eventTimes)))) if err := feefund.EnsureWalletFunded(ctx, platform, deployer.Address(), totalFee.String()); err != nil { diff --git a/tests/streams/roles/permission_gates_test.go b/tests/streams/roles/permission_gates_test.go index 0390ea0f..af352682 100644 --- a/tests/streams/roles/permission_gates_test.go +++ b/tests/streams/roles/permission_gates_test.go @@ -30,7 +30,7 @@ func TestPermissionGates(t *testing.T) { func testStreamCreationPermissionGates(t *testing.T, ctx context.Context, platform *kwilTesting.Platform) { // Universal write-fee enforcement removed the role-based exemption: every caller - // pays 6 TRUF per stream regardless of network_writer membership. This test now + // pays a flat 1 TRUF per tx regardless of network_writer membership. This test now // only covers the still-meaningful gate — that an authorized data provider can // create streams when their wallet is funded (UntypedCreateStream tops up the // balance via feefund). Cases that asserted role-based exemption or expected diff --git a/tests/streams/stream_creation_fee_test.go b/tests/streams/stream_creation_fee_test.go index 73e08279..d4ec5f73 100644 --- a/tests/streams/stream_creation_fee_test.go +++ b/tests/streams/stream_creation_fee_test.go @@ -21,19 +21,24 @@ import ( "github.com/trufnetwork/sdk-go/core/util" ) -// Test constants - must match erc20-bridge/000-extension.sql configuration +// Test constants — must match the `hoodi_tt` USE block in +// erc20-bridge/000-extension.sql. The fee-collection actions (001/003/004) +// call hoodi_tt.balance / hoodi_tt.transfer directly, so this test helper +// credits balance into the matching bridge instance. const ( - testChain = "sepolia" - testEscrow = "0x502430eD0BbE0f230215870c9C2853e126eE5Ae3" // From erc20-bridge/000-extension.sql + testChain = "hoodi" + testEscrow = "0x878d6aaeb6e746033f50b8dc268d54b4631554e7" testERC20 = "0x2222222222222222222222222222222222222222" - testExtensionName = "sepolia_bridge" + testExtensionName = "hoodi_tt" ) var ( - // sixTRUF is parsed from feefund.PerStreamWei — the same constant the + // oneTRUF is parsed from feefund.WriteFeeWei — the same constant the // migration uses, so a fee-schedule change in one place can't drift - // from test assertions silently. - sixTRUF = mustParseBigInt(feefund.PerStreamWei) + // from test assertions silently. Per issue #3805 the write fee is now a + // flat 1 TRUF per transaction, regardless of how many streams the tx + // creates. + oneTRUF = mustParseBigInt(feefund.WriteFeeWei) pointCounter int64 = 10 // Start from 10, increment for each balance injection ) @@ -56,7 +61,7 @@ func TestStreamCreationFees(t *testing.T) { testNonExemptWalletPaysFee(t), testInsufficientBalance(t), testFeeIndependentOfRole(t), - testBatchCreationPerStreamFee(t), + testBatchCreationChargesFlatFee(t), testLeaderReceivesFees(t), }, }, testutils.GetTestOptionsWithCache()) @@ -94,7 +99,7 @@ func setupTestEnvironment(t *testing.T) func(ctx context.Context, platform *kwil } // Test 1: Wallet with network_writer role still pays the create_streams fee. -// The role no longer carries an exemption — funded callers always pay 6 TRUF/stream. +// The role no longer carries an exemption — funded callers always pay 1 TRUF per tx. func testWriterRolePaysFee(t *testing.T) func(ctx context.Context, platform *kwilTesting.Platform) error { return func(ctx context.Context, platform *kwilTesting.Platform) error { writerAddrVal := util.Unsafe_NewEthereumAddressFromString("0x1111111111111111111111111111111111111111") @@ -117,15 +122,15 @@ func testWriterRolePaysFee(t *testing.T) func(ctx context.Context, platform *kwi finalBalance, err := getBalance(ctx, platform, writerAddr.Address()) require.NoError(t, err, "failed to get final balance") - expectedBalance := new(big.Int).Sub(initialBalance, sixTRUF) + expectedBalance := new(big.Int).Sub(initialBalance, oneTRUF) require.Equal(t, 0, expectedBalance.Cmp(finalBalance), - "network_writer should pay 6 TRUF, expected %s but got %s", expectedBalance, finalBalance) + "network_writer should pay 1 TRUF, expected %s but got %s", expectedBalance, finalBalance) return nil } } -// Test 2: Non-exempt wallet pays 6 TRUF fee +// Test 2: Non-exempt wallet pays 1 TRUF fee func testNonExemptWalletPaysFee(t *testing.T) func(ctx context.Context, platform *kwilTesting.Platform) error { return func(ctx context.Context, platform *kwilTesting.Platform) error { userAddrVal := util.Unsafe_NewEthereumAddressFromString("0x3333333333333333333333333333333333333333") @@ -147,13 +152,13 @@ func testNonExemptWalletPaysFee(t *testing.T) func(ctx context.Context, platform err = createStream(ctx, platform, userAddr, "st000000000000000000000000000002", "primitive") require.NoError(t, err, "stream creation should succeed") - // Verify balance decreased by 6 TRUF + // Verify balance decreased by 1 TRUF finalBalance, err := getBalance(ctx, platform, userAddr.Address()) require.NoError(t, err, "failed to get final balance") - expectedBalance := new(big.Int).Sub(initialBalance, sixTRUF) + expectedBalance := new(big.Int).Sub(initialBalance, oneTRUF) require.Equal(t, 0, expectedBalance.Cmp(finalBalance), - "Balance should decrease by 6 TRUF, expected %s but got %s", expectedBalance, finalBalance) + "Balance should decrease by 1 TRUF, expected %s but got %s", expectedBalance, finalBalance) return nil } @@ -169,8 +174,8 @@ func testInsufficientBalance(t *testing.T) func(ctx context.Context, platform *k err := setup.CreateDataProviderWithoutRole(ctx, platform, userAddr.Address()) require.NoError(t, err, "failed to register data provider") - // Give user only 1 TRUF (insufficient) - err = giveBalance(ctx, platform, userAddr.Address(), "1000000000000000000") + // Give user 0.5 TRUF (insufficient for the flat 1 TRUF fee). + err = giveBalance(ctx, platform, userAddr.Address(), "500000000000000000") require.NoError(t, err, "failed to give balance") // Try to create stream (should fail) @@ -184,7 +189,7 @@ func testInsufficientBalance(t *testing.T) func(ctx context.Context, platform *k } // Test 4: network_writer role grant/revoke does NOT change the create_streams fee. -// Every call charges 6 TRUF regardless of role membership. +// Every call charges 1 TRUF regardless of role membership. func testFeeIndependentOfRole(t *testing.T) func(ctx context.Context, platform *kwilTesting.Platform) error { return func(ctx context.Context, platform *kwilTesting.Platform) error { userAddrVal := util.Unsafe_NewEthereumAddressFromString("0x5555555555555555555555555555555555555555") @@ -199,15 +204,15 @@ func testFeeIndependentOfRole(t *testing.T) func(ctx context.Context, platform * initialBalance, err := getBalance(ctx, platform, userAddr.Address()) require.NoError(t, err) - // Without role: charges 6 TRUF. + // Without role: charges 1 TRUF. err = createStream(ctx, platform, userAddr, "st000000000000000000000000000004", "primitive") require.NoError(t, err, "first stream creation should succeed") balanceAfterFirst, err := getBalance(ctx, platform, userAddr.Address()) require.NoError(t, err) - expectedAfterFirst := new(big.Int).Sub(initialBalance, sixTRUF) - require.Equal(t, 0, expectedAfterFirst.Cmp(balanceAfterFirst), "first create should charge 6 TRUF") + expectedAfterFirst := new(big.Int).Sub(initialBalance, oneTRUF) + require.Equal(t, 0, expectedAfterFirst.Cmp(balanceAfterFirst), "first create should charge 1 TRUF") // Grant role — must NOT exempt. err = setup.AddMemberToRoleBypass(ctx, platform, "system", "network_writer", userAddr.Address()) @@ -218,9 +223,9 @@ func testFeeIndependentOfRole(t *testing.T) func(ctx context.Context, platform * balanceAfterSecond, err := getBalance(ctx, platform, userAddr.Address()) require.NoError(t, err) - expectedAfterSecond := new(big.Int).Sub(balanceAfterFirst, sixTRUF) + expectedAfterSecond := new(big.Int).Sub(balanceAfterFirst, oneTRUF) require.Equal(t, 0, expectedAfterSecond.Cmp(balanceAfterSecond), - "network_writer must still pay the 6 TRUF fee — exemption removed") + "network_writer must still pay the 1 TRUF fee — exemption removed") // Revoke role — fee unchanged. err = revokeRoleBypass(ctx, platform, "system", "network_writer", userAddr.Address()) @@ -232,16 +237,17 @@ func testFeeIndependentOfRole(t *testing.T) func(ctx context.Context, platform * balanceAfterThird, err := getBalance(ctx, platform, userAddr.Address()) require.NoError(t, err) - expectedAfterThird := new(big.Int).Sub(balanceAfterSecond, sixTRUF) + expectedAfterThird := new(big.Int).Sub(balanceAfterSecond, oneTRUF) require.Equal(t, 0, expectedAfterThird.Cmp(balanceAfterThird), - "third create should charge 6 TRUF (role revoked, fee unchanged)") + "third create should charge 1 TRUF (role revoked, fee unchanged)") return nil } } -// Test 5: Batch stream creation charges fee per stream (not per call) -func testBatchCreationPerStreamFee(t *testing.T) func(ctx context.Context, platform *kwilTesting.Platform) error { +// Test 5: Batched create_streams charges a flat 1 TRUF (not 1 TRUF × N). +// This is the key invariant of issue #3805 — pricing is per-tx, not per-stream. +func testBatchCreationChargesFlatFee(t *testing.T) func(ctx context.Context, platform *kwilTesting.Platform) error { return func(ctx context.Context, platform *kwilTesting.Platform) error { userAddrVal := util.Unsafe_NewEthereumAddressFromString("0x6666666666666666666666666666666666666666") userAddr := &userAddrVal @@ -269,15 +275,14 @@ func testBatchCreationPerStreamFee(t *testing.T) func(ctx context.Context, platf err = createStreams(ctx, platform, userAddr, streamIds, streamTypes) require.NoError(t, err, "batch stream creation should succeed") - // Verify balance decreased by 18 TRUF (6 TRUF × 3 streams) + // Verify balance decreased by exactly 1 TRUF for the whole batch. finalBalance, err := getBalance(ctx, platform, userAddr.Address()) require.NoError(t, err) - numStreams := int64(len(streamIds)) // 3 streams - expectedFee := new(big.Int).Mul(sixTRUF, big.NewInt(numStreams)) - expectedBalance := new(big.Int).Sub(initialBalance, expectedFee) + expectedBalance := new(big.Int).Sub(initialBalance, oneTRUF) require.Equal(t, 0, expectedBalance.Cmp(finalBalance), - "Batch should charge 6 TRUF per stream (3 streams = 18 TRUF), expected %s but got %s", expectedBalance, finalBalance) + "Batch of %d streams must still charge 1 TRUF flat (per-tx, not per-stream); expected %s but got %s", + len(streamIds), expectedBalance, finalBalance) return nil } @@ -316,13 +321,13 @@ func testLeaderReceivesFees(t *testing.T) func(ctx context.Context, platform *kw err = createStreamWithLeader(ctx, platform, userAddr, pub, "st00000000000000000000000000000a", "primitive") require.NoError(t, err, "stream creation with leader should succeed") - // Verify leader balance increased by 6 TRUF + // Verify leader balance increased by 1 TRUF finalLeaderBalance, err := getBalance(ctx, platform, leaderAddr) require.NoError(t, err, "failed to get final leader balance") - expectedLeaderBalance := new(big.Int).Add(initialLeaderBalance, sixTRUF) + expectedLeaderBalance := new(big.Int).Add(initialLeaderBalance, oneTRUF) require.Equal(t, 0, expectedLeaderBalance.Cmp(finalLeaderBalance), - "Leader should receive 6 TRUF fee, expected %s but got %s", expectedLeaderBalance, finalLeaderBalance) + "Leader should receive 1 TRUF fee, expected %s but got %s", expectedLeaderBalance, finalLeaderBalance) return nil } diff --git a/tests/streams/utils/feefund/feefund_default.go b/tests/streams/utils/feefund/feefund_default.go index 2003f387..fa4a4e77 100644 --- a/tests/streams/utils/feefund/feefund_default.go +++ b/tests/streams/utils/feefund/feefund_default.go @@ -14,7 +14,7 @@ import ( // Constants kept identical to feefund_kwiltest.go so callers compile uniformly. const ( - PerStreamWei = "6000000000000000000" // 6 TRUF + WriteFeeWei = "1000000000000000000" // 1 TRUF (flat per-tx write fee) AttestationFeeWei = "40000000000000000000" // 40 TRUF ) diff --git a/tests/streams/utils/feefund/feefund_kwiltest.go b/tests/streams/utils/feefund/feefund_kwiltest.go index 01135f73..fef00278 100644 --- a/tests/streams/utils/feefund/feefund_kwiltest.go +++ b/tests/streams/utils/feefund/feefund_kwiltest.go @@ -23,9 +23,12 @@ import ( // hard-coded fee in its corresponding migration so that test funding stays // in sync with on-chain charges. If a migration changes its fee, update here. const ( - // PerStreamWei mirrors the fee in 001-common-actions.sql `create_streams` - // AND in 003-primitive-insertion.sql / 004-composed-taxonomy.sql. - PerStreamWei = "6000000000000000000" // 6 TRUF + // WriteFeeWei mirrors the flat per-transaction write fee charged by + // 001-common-actions.sql `create_streams`, 003-primitive-insertion.sql + // `insert_records`, and 004-composed-taxonomy.sql `insert_taxonomy`. + // Per issue #3805 the fee is independent of batch size — one tx charges + // 1 TRUF regardless of how many streams/records/children it touches. + WriteFeeWei = "1000000000000000000" // 1 TRUF // AttestationFeeWei mirrors the flat fee in 024-attestation-actions.sql. AttestationFeeWei = "40000000000000000000" // 40 TRUF diff --git a/tests/streams/utils/procedure/execute.go b/tests/streams/utils/procedure/execute.go index cf18c10d..265b386c 100644 --- a/tests/streams/utils/procedure/execute.go +++ b/tests/streams/utils/procedure/execute.go @@ -4,7 +4,6 @@ import ( "context" "encoding/json" "fmt" - "math/big" "strings" "github.com/pkg/errors" @@ -435,15 +434,10 @@ func SetTaxonomy(ctx context.Context, input SetTaxonomyInput) error { weightDecimals = append(weightDecimals, valueDecimal) } - // insert_taxonomy charges feefund.PerStreamWei per child stream after the - // universal-fee migration removed the network_writer exemption. - feePerChild, ok := new(big.Int).SetString(feefund.PerStreamWei, 10) - if !ok { - return errors.Errorf("invalid feefund.PerStreamWei: %s", feefund.PerStreamWei) - } - totalFee := new(big.Int).Mul(feePerChild, big.NewInt(int64(len(primitiveStreamStrings)))) - if err := feefund.EnsureWalletFunded(ctx, input.Platform, deployer.Address(), totalFee.String()); err != nil { - return errors.Wrap(err, "fund deployer for insert_taxonomy fees") + // insert_taxonomy charges a flat 1 TRUF per call regardless of child + // count, so fund a single feefund.WriteFeeWei before invoking it. + if err := feefund.EnsureWalletFunded(ctx, input.Platform, deployer.Address(), feefund.WriteFeeWei); err != nil { + return errors.Wrap(err, "fund deployer for insert_taxonomy fee") } engineContext := testctx.NewEngineContext(ctx, input.Platform, deployer, input.Height) diff --git a/tests/streams/utils/setup/common.go b/tests/streams/utils/setup/common.go index e84a7d7b..fb553d2c 100644 --- a/tests/streams/utils/setup/common.go +++ b/tests/streams/utils/setup/common.go @@ -2,7 +2,6 @@ package setup import ( "context" - "math/big" "strings" "github.com/pkg/errors" @@ -75,9 +74,9 @@ func UntypedCreateStream(ctx context.Context, platform *kwilTesting.Platform, st return errors.Wrap(err, "invalid data provider address") } - // Universal write-fee enforcement (001-common-actions.sql) charges 6 TRUF per - // stream from every caller — top the wallet up so setup helpers stay neutral. - if err := feefund.EnsureWalletFunded(ctx, platform, addr.Address(), feefund.PerStreamWei); err != nil { + // Universal write-fee enforcement (001-common-actions.sql) charges a flat + // 1 TRUF per transaction — top the wallet up so setup helpers stay neutral. + if err := feefund.EnsureWalletFunded(ctx, platform, addr.Address(), feefund.WriteFeeWei); err != nil { return errors.Wrap(err, "fund wallet for stream creation fee") } @@ -132,11 +131,9 @@ func CreateStreamsWithOptions(ctx context.Context, platform *kwilTesting.Platfor return errors.Wrap(err, "error creating composed dataset") } - // Fund the deployer with 6 TRUF × N streams so the universal write fee passes. - feePerStream := new(big.Int) - feePerStream.SetString(feefund.PerStreamWei, 10) - totalFee := new(big.Int).Mul(feePerStream, big.NewInt(int64(len(streamInfos)))) - if err := feefund.EnsureWalletFunded(ctx, platform, deployer.Address(), totalFee.String()); err != nil { + // Fund the deployer with a flat 1 TRUF for the single create_streams call. + // The write fee is per-tx, not per-stream, so batch size doesn't multiply it. + if err := feefund.EnsureWalletFunded(ctx, platform, deployer.Address(), feefund.WriteFeeWei); err != nil { return errors.Wrap(err, "fund deployer for batch stream creation fee") } diff --git a/tests/streams/utils/setup/primitive.go b/tests/streams/utils/setup/primitive.go index 43bbed34..c2a26fc1 100644 --- a/tests/streams/utils/setup/primitive.go +++ b/tests/streams/utils/setup/primitive.go @@ -15,18 +15,19 @@ import ( "github.com/trufnetwork/sdk-go/core/util" ) -// fundForRecordWrites tops up `wallet` with the fee required for `numRecords` -// insert_record/insert_records calls (each charges feefund.PerStreamWei). The -// universal-fee migration removed the network_writer exemption, so test setup -// helpers must pre-fund the signer or every insert reverts with "Insufficient -// balance for write fee". -func fundForRecordWrites(ctx context.Context, platform *kwilTesting.Platform, wallet string, numRecords int) error { - if numRecords <= 0 { +// fundForInsertCalls tops up `wallet` with the flat 1 TRUF fee × `numCalls` +// for that many insert_record / insert_records transactions. The migration +// charges a flat per-tx fee regardless of how many records are in each batch, +// so callers pass the *number of engine calls* they plan to make, not the +// number of records. Single-record loops pass len(rows); batched single-call +// helpers pass 1. +func fundForInsertCalls(ctx context.Context, platform *kwilTesting.Platform, wallet string, numCalls int) error { + if numCalls <= 0 { return nil } - feePerRecord := new(big.Int) - feePerRecord.SetString(feefund.PerStreamWei, 10) - totalFee := new(big.Int).Mul(feePerRecord, big.NewInt(int64(numRecords))) + feePerCall := new(big.Int) + feePerCall.SetString(feefund.WriteFeeWei, 10) + totalFee := new(big.Int).Mul(feePerCall, big.NewInt(int64(numCalls))) return feefund.EnsureWalletFunded(ctx, platform, wallet, totalFee.String()) } @@ -172,7 +173,9 @@ func InsertMarkdownPrimitiveData(ctx context.Context, input InsertMarkdownDataIn nonEmpty++ } } - if err := fundForRecordWrites(ctx, input.Platform, signer.Address(), nonEmpty); err != nil { + // InsertMarkdownPrimitiveData calls insert_record once per non-empty row, + // so the signer needs `nonEmpty` × 1-TRUF write fee. + if err := fundForInsertCalls(ctx, input.Platform, signer.Address(), nonEmpty); err != nil { return errors.Wrap(err, "fund signer for insert_record fees") } @@ -232,7 +235,9 @@ func insertPrimitiveData(ctx context.Context, input InsertPrimitiveDataInput) er return errors.Wrap(err, "error in insertPrimitiveData") } - if err := fundForRecordWrites(ctx, input.Platform, deployer.Address(), len(args)); err != nil { + // insertPrimitiveData loops over args making one insert_record call per + // record, so each call needs its own 1-TRUF write fee. + if err := fundForInsertCalls(ctx, input.Platform, deployer.Address(), len(args)); err != nil { return errors.Wrap(err, "fund deployer for insert_record fees") } @@ -336,7 +341,10 @@ func InsertPrimitiveDataMultiBatch(ctx context.Context, input InsertMultiPrimiti } signerAddr := util.Unsafe_NewEthereumAddressFromString(provider) - if err := fundForRecordWrites(ctx, input.Platform, signerAddr.Address(), len(g.dataProviders)); err != nil { + // InsertPrimitiveDataMultiBatch issues exactly one insert_records call + // per provider group, regardless of how many records it contains. The + // write fee is per-tx, so fund a single 1 TRUF (not len(records) × 1). + if err := fundForInsertCalls(ctx, input.Platform, signerAddr.Address(), 1); err != nil { return errors.Wrapf(err, "fund %s for insert_records batch", provider) }