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
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@
-- fee is paid in the SAME bridge as the transfer (not always in TRUF).
-- This avoids forcing USDC senders to also hold TRUF.
--
-- Because of that, and because transaction_events carries no token column, both
-- actions record the bridge in the ledger event's metadata. Without it the two
-- fees are indistinguishable in the ledger and a reader has to infer the token
-- from the amount, which only works while the two fees stay different constants.
--
-- Manual-apply mainnet override. The embedded migration loader skips
-- *.prod.sql, so apply via:
--
Expand Down Expand Up @@ -58,11 +63,14 @@ CREATE OR REPLACE ACTION eth_truf_transfer($to_address TEXT, $amount TEXT) PUBLI
-- Execute transfer using the bridge extension
eth_truf.transfer($to_address, $amount::NUMERIC(78, 0));

-- The fee is paid in the bridge that moved, not always in $TRUF, and
-- transaction_events has no token column. Naming the bridge here is what lets
-- a reader tell this row's 1 TRUF from eth_usdc_transfer's 1 USDC.
record_transaction_event(
4,
$fee,
'0x' || $leader_hex,
NULL
'{"bridge":"eth_truf"}'
);
};

Expand Down Expand Up @@ -100,10 +108,12 @@ CREATE OR REPLACE ACTION eth_usdc_transfer($to_address TEXT, $amount TEXT) PUBLI
-- Execute transfer using the bridge extension
eth_usdc.transfer($to_address, $amount::NUMERIC(78, 0));

-- See eth_truf_transfer above: without this the 6-decimal fee is
-- indistinguishable from an 18-decimal one in the ledger.
record_transaction_event(
4,
$fee,
'0x' || $leader_hex,
NULL
'{"bridge":"eth_usdc"}'
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ CREATE OR REPLACE ACTION sepolia_transfer($to_address TEXT, $amount TEXT) PUBLIC
4,
$fee,
'0x' || $leader_hex,
NULL
'{"bridge":"sepolia_bridge"}'
);
};

Expand Down Expand Up @@ -78,7 +78,7 @@ CREATE OR REPLACE ACTION ethereum_transfer($to_address TEXT, $amount TEXT) PUBLI
4,
$fee,
'0x' || $leader_hex,
NULL
'{"bridge":"ethereum_bridge"}'
);
};

Expand Down Expand Up @@ -119,7 +119,7 @@ CREATE OR REPLACE ACTION hoodi_tt_transfer($to_address TEXT, $amount TEXT) PUBLI
4,
$fee,
'0x' || $leader_hex,
NULL
'{"bridge":"hoodi_tt"}'
);
};

Expand Down Expand Up @@ -160,6 +160,6 @@ CREATE OR REPLACE ACTION hoodi_tt2_transfer($to_address TEXT, $amount TEXT) PUBL
4,
$fee,
'0x' || $leader_hex,
NULL
'{"bridge":"hoodi_tt2"}'
);
};
35 changes: 34 additions & 1 deletion tests/streams/transaction_events_ledger_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,19 @@ func runTransactionEventsLedgerScenario(t *testing.T) func(ctx context.Context,
require.NoError(t, err)
height++

// A second transfer on a different bridge. Two transfer actions booking
// two different bridges under the same method is the whole point of
// recording one: the amount alone cannot tell them apart once a bridge
// with different decimals exists. The actor is already funded on
// hoodi_tt for the write fees above, so this needs no extra setup.
ttTransferLeaderPub, ttTransferLeaderAddr := newLeader(t)
ttTransferTx, err := callActionWithLeader(ctx, platform, actor, ttTransferLeaderPub, height, "hoodi_tt_transfer", []any{
receiver.Address(),
transferAmount,
})
require.NoError(t, err)
height++

withdrawLeaderPub, withdrawLeaderAddr := newLeader(t)
withdrawTx, err := callActionWithLeader(ctx, platform, actor, withdrawLeaderPub, height, "sepolia_bridge_tokens", []any{
actor.Address(),
Expand Down Expand Up @@ -238,6 +251,17 @@ func runTransactionEventsLedgerScenario(t *testing.T) func(ctx context.Context,
require.Empty(t, meta, "expected no metadata for ledger event")
}

// A transfer is the one method that does not always charge its fee in
// $TRUF: it charges in the bridge it moves. The ledger has no token
// column, so unless each action names its own bridge a reader cannot
// tell a 1 TRUF fee from a 1 USDC one, and the two differ by 1e12.
assertBridge := func(want string) func(meta metadataMap) {
return func(meta metadataMap) {
require.Equal(t, want, meta.String("bridge"),
"a transfer must name the bridge its fee was charged in")
}
}

expected := map[string]ledgerExpectation{
createTx: {
method: "deployStream",
Expand Down Expand Up @@ -274,7 +298,16 @@ func runTransactionEventsLedgerScenario(t *testing.T) func(ctx context.Context,
feeDistributions: []string{
buildDistribution(transferLeaderAddr, feeOneTRUF),
},
assertMetadata: assertNoMetadata,
assertMetadata: assertBridge("sepolia_bridge"),
},
ttTransferTx: {
method: "transferTN",
fee: feeOneTRUF,
feeRecipient: ttTransferLeaderAddr,
feeDistributions: []string{
buildDistribution(ttTransferLeaderAddr, feeOneTRUF),
},
assertMetadata: assertBridge("hoodi_tt"),
},
withdrawTx: {
method: "withdrawTN",
Expand Down
Loading