Skip to content

Commit c29ac9f

Browse files
committed
fix: svm event gas fee used report
1 parent 8cbd6c1 commit c29ac9f

File tree

3 files changed

+47
-52
lines changed

3 files changed

+47
-52
lines changed

universalClient/chains/svm/event_confirmer.go

Lines changed: 2 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package svm
22

33
import (
44
"context"
5-
"encoding/json"
65
"fmt"
76
"strings"
87
"sync"
@@ -170,34 +169,8 @@ func (ec *EventConfirmer) processPendingEvents(ctx context.Context) error {
170169
confirmations := latestSlot - txSlot + 1
171170

172171
if confirmations >= requiredConfirmations {
173-
var rowsAffected int64
174-
175-
// For outbound events, enrich with gas fee from tx metadata
176-
if event.Type == chaincommon.EventTypeOutbound {
177-
var outboundEvent chaincommon.OutboundEvent
178-
if unmarshalErr := json.Unmarshal(event.EventData, &outboundEvent); unmarshalErr != nil {
179-
ec.logger.Error().
180-
Err(unmarshalErr).
181-
Str("event_id", event.EventID).
182-
Msg("failed to unmarshal outbound event data")
183-
continue
184-
}
185-
outboundEvent.GasFeeUsed = "0"
186-
187-
updatedData, marshalErr := json.Marshal(outboundEvent)
188-
if marshalErr != nil {
189-
ec.logger.Error().
190-
Err(marshalErr).
191-
Str("event_id", event.EventID).
192-
Msg("failed to marshal enriched outbound event data")
193-
continue
194-
}
195-
196-
rowsAffected, err = ec.chainStore.UpdateStatusAndEventData(event.EventID, "PENDING", "CONFIRMED", updatedData)
197-
} else {
198-
rowsAffected, err = ec.chainStore.UpdateEventStatus(event.EventID, "PENDING", "CONFIRMED")
199-
}
200-
172+
// GasFeeUsed for outbound events is already set by the event parser from the on-chain event data
173+
rowsAffected, err := ec.chainStore.UpdateEventStatus(event.EventID, "PENDING", "CONFIRMED")
201174
if err != nil {
202175
ec.logger.Error().
203176
Err(err).

universalClient/chains/svm/event_parser.go

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -101,11 +101,17 @@ func parseSendFundsEvent(log string, signature string, slot uint64, logIndex uin
101101
return event
102102
}
103103

104-
// parseOutboundObservationEvent parses an outboundObservation event
104+
// parseOutboundObservationEvent parses an outboundObservation event (UniversalTxFinalized)
105105
// Event structure (Borsh serialized):
106106
// - discriminator (8 bytes)
107-
// - sub_tx_id (32 bytes - bytes32)
108-
// - universal_tx_id (32 bytes - bytes32)
107+
// - sub_tx_id (32 bytes)
108+
// - universal_tx_id (32 bytes)
109+
// - gas_fee (8 bytes, u64 lamports)
110+
// - push_account (20 bytes)
111+
// - target (32 bytes, Pubkey)
112+
// - token (32 bytes, Pubkey)
113+
// - amount (8 bytes, u64)
114+
// - payload (4 bytes length + data, Vec<u8>)
109115
func parseOutboundObservationEvent(log string, signature string, slot uint64, logIndex uint, chainID string, logger zerolog.Logger) *store.Event {
110116
if !strings.HasPrefix(log, "Program data: ") {
111117
return nil
@@ -117,11 +123,11 @@ func parseOutboundObservationEvent(log string, signature string, slot uint64, lo
117123
return nil
118124
}
119125

120-
// Need at least: 8 bytes discriminator + 32 bytes txID + 32 bytes universalTxID = 72 bytes
121-
if len(decoded) < 72 {
126+
// Minimum: 8 disc + 32 sub_tx_id + 32 universal_tx_id + 8 gas_fee = 80 bytes
127+
if len(decoded) < 80 {
122128
logger.Warn().
123129
Int("data_len", len(decoded)).
124-
Msg("data too short for outboundObservation event; need at least 72 bytes")
130+
Msg("data too short for outboundObservation event; need at least 80 bytes")
125131
return nil
126132
}
127133

@@ -144,11 +150,16 @@ func parseOutboundObservationEvent(log string, signature string, slot uint64, lo
144150

145151
// Extract universalTxID (32 bytes)
146152
universalTxID := "0x" + hex.EncodeToString(decoded[offset:offset+32])
153+
offset += 32
154+
155+
// Extract gas_fee (8 bytes, u64 little-endian lamports)
156+
gasFee := binary.LittleEndian.Uint64(decoded[offset : offset+8])
147157

148158
// Create OutboundEvent payload
149159
payload := common.OutboundEvent{
150160
TxID: txID,
151161
UniversalTxID: universalTxID,
162+
GasFeeUsed: fmt.Sprintf("%d", gasFee),
152163
}
153164

154165
// Marshal payload to JSON
@@ -176,6 +187,7 @@ func parseOutboundObservationEvent(log string, signature string, slot uint64, lo
176187
Str("event_id", eventID).
177188
Str("tx_id", txID).
178189
Str("universal_tx_id", universalTxID).
190+
Str("gas_fee", fmt.Sprintf("%d", gasFee)).
179191
Msg("parsed outboundObservation event")
180192

181193
return event

universalClient/chains/svm/event_parser_test.go

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package svm
22

33
import (
44
"encoding/base64"
5+
"encoding/binary"
56
"encoding/hex"
67
"encoding/json"
78
"testing"
@@ -19,12 +20,15 @@ func TestParseOutboundObservationEvent(t *testing.T) {
1920
chainID := "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp"
2021
signature := "5wHu1qwD7q5xMkZxq6z2S3r4y5N7m8P9kL0jH1gF2dE3cB4aA5b6C7d8E9f0G1h2"
2122

22-
// Helper to create base64-encoded log data
23-
createLogData := func(discriminator []byte, txID []byte, universalTxID []byte) string {
24-
data := make([]byte, 0, 72)
23+
// Helper to create base64-encoded log data with gas_fee
24+
createLogData := func(discriminator []byte, txID []byte, universalTxID []byte, gasFee uint64) string {
25+
data := make([]byte, 0, 80)
2526
data = append(data, discriminator...)
2627
data = append(data, txID...)
2728
data = append(data, universalTxID...)
29+
gasFeeBytes := make([]byte, 8)
30+
binary.LittleEndian.PutUint64(gasFeeBytes, gasFee)
31+
data = append(data, gasFeeBytes...)
2832
return "Program data: " + base64.StdEncoding.EncodeToString(data)
2933
}
3034

@@ -54,7 +58,7 @@ func TestParseOutboundObservationEvent(t *testing.T) {
5458
}{
5559
{
5660
name: "parses valid outbound observation event",
57-
log: createLogData(discriminator, txID, universalTxID),
61+
log: createLogData(discriminator, txID, universalTxID, 5000),
5862
wantEvent: true,
5963
validate: func(t *testing.T, event *store.Event) {
6064
assert.Contains(t, event.EventID, signature)
@@ -63,7 +67,7 @@ func TestParseOutboundObservationEvent(t *testing.T) {
6367
assert.Equal(t, "PENDING", event.Status)
6468
assert.Equal(t, "STANDARD", event.ConfirmationType)
6569

66-
// Verify event data contains tx_id and universal_tx_id
70+
// Verify event data contains tx_id, universal_tx_id, and gas_fee_used
6771
assert.NotNil(t, event.EventData)
6872
var outboundData map[string]any
6973
err := json.Unmarshal(event.EventData, &outboundData)
@@ -74,6 +78,7 @@ func TestParseOutboundObservationEvent(t *testing.T) {
7478

7579
assert.Equal(t, expectedTxID, outboundData["tx_id"])
7680
assert.Equal(t, expectedUniversalTxID, outboundData["universal_tx_id"])
81+
assert.Equal(t, "5000", outboundData["gas_fee_used"])
7782
},
7883
},
7984
{
@@ -92,27 +97,29 @@ func TestParseOutboundObservationEvent(t *testing.T) {
9297
wantEvent: false,
9398
},
9499
{
95-
name: "returns nil for data too short (less than 72 bytes)",
100+
name: "returns nil for data too short (less than 80 bytes)",
96101
log: func() string {
97-
// Only 64 bytes (8 discriminator + 32 txID, missing universalTxID)
98-
shortData := make([]byte, 64)
102+
// Only 72 bytes (8 discriminator + 32 txID + 32 universalTxID, missing gas_fee)
103+
shortData := make([]byte, 72)
99104
copy(shortData[:8], discriminator)
100105
copy(shortData[8:40], txID)
106+
copy(shortData[40:72], universalTxID)
101107
return "Program data: " + base64.StdEncoding.EncodeToString(shortData)
102108
}(),
103109
wantEvent: false,
104110
},
105111
{
106-
name: "correctly parses minimum valid data (exactly 72 bytes)",
112+
name: "correctly parses minimum valid data (exactly 80 bytes)",
107113
log: func() string {
108-
exactData := make([]byte, 72)
114+
exactData := make([]byte, 80)
109115
copy(exactData[:8], discriminator)
110116
for i := 8; i < 40; i++ {
111117
exactData[i] = 0x11 // txID
112118
}
113119
for i := 40; i < 72; i++ {
114120
exactData[i] = 0x22 // universalTxID
115121
}
122+
binary.LittleEndian.PutUint64(exactData[72:80], 12345) // gas_fee
116123
return "Program data: " + base64.StdEncoding.EncodeToString(exactData)
117124
}(),
118125
wantEvent: true,
@@ -121,21 +128,22 @@ func TestParseOutboundObservationEvent(t *testing.T) {
121128
err := json.Unmarshal(event.EventData, &outboundData)
122129
require.NoError(t, err)
123130

124-
// Verify the values are correctly parsed
125131
assert.Contains(t, outboundData["tx_id"], "0x1111")
126132
assert.Contains(t, outboundData["universal_tx_id"], "0x2222")
133+
assert.Equal(t, "12345", outboundData["gas_fee_used"])
127134
},
128135
},
129136
{
130-
name: "handles data longer than 72 bytes",
137+
name: "handles data longer than 80 bytes",
131138
log: func() string {
132-
// 100 bytes - extra data after the required fields should be ignored
133-
longData := make([]byte, 100)
139+
// 120 bytes - extra data after the required fields should be ignored
140+
longData := make([]byte, 120)
134141
copy(longData[:8], discriminator)
135142
copy(longData[8:40], txID)
136143
copy(longData[40:72], universalTxID)
144+
binary.LittleEndian.PutUint64(longData[72:80], 9999) // gas_fee
137145
// Extra bytes at the end
138-
for i := 72; i < 100; i++ {
146+
for i := 80; i < 120; i++ {
139147
longData[i] = 0xFF
140148
}
141149
return "Program data: " + base64.StdEncoding.EncodeToString(longData)
@@ -151,6 +159,7 @@ func TestParseOutboundObservationEvent(t *testing.T) {
151159

152160
assert.Equal(t, expectedTxID, outboundData["tx_id"])
153161
assert.Equal(t, expectedUniversalTxID, outboundData["universal_tx_id"])
162+
assert.Equal(t, "9999", outboundData["gas_fee_used"])
154163
},
155164
},
156165
}
@@ -193,14 +202,15 @@ func TestParseOutboundObservationEvent_EventIDFormat(t *testing.T) {
193202
logger := zerolog.New(nil).Level(zerolog.Disabled)
194203
chainID := "solana:devnet"
195204

196-
// Create valid outbound data
197-
data := make([]byte, 72)
205+
// Create valid outbound data (80 bytes minimum: 8 disc + 32 txID + 32 utxID + 8 gas_fee)
206+
data := make([]byte, 80)
198207
for i := 0; i < 8; i++ {
199208
data[i] = byte(i) // discriminator
200209
}
201210
for i := 8; i < 72; i++ {
202211
data[i] = byte(i % 256) // txID and universalTxID
203212
}
213+
binary.LittleEndian.PutUint64(data[72:80], 0) // gas_fee
204214
log := "Program data: " + base64.StdEncoding.EncodeToString(data)
205215

206216
tests := []struct {

0 commit comments

Comments
 (0)