@@ -74,6 +74,11 @@ func (m *mockTxBuilder) BroadcastFundMigrationTx(ctx context.Context, req *commo
7474 return args .String (0 ), args .Error (1 )
7575}
7676
77+ func (m * mockTxBuilder ) CleanupOutboundArtifacts (ctx context.Context , data * uexecutortypes.OutboundCreatedEvent ) error {
78+ args := m .Called (ctx , data )
79+ return args .Error (0 )
80+ }
81+
7782type mockChainClient struct { builder * mockTxBuilder }
7883
7984func (m * mockChainClient ) Start (context.Context ) error { return nil }
@@ -270,7 +275,8 @@ func TestSVM_PDAExists_MarksCompleted(t *testing.T) {
270275}
271276
272277func TestSVM_PDANotFound_VotesFailureAndReverts (t * testing.T ) {
273- // PDA not found → vote failure → REVERTED.
278+ // PDA not found → vote failure → REVERTED, plus cleanup of any orphaned
279+ // StoredIxData PDA from the ref-finalize route.
274280 evtStore , db := setupTestDB (t )
275281 builder := & mockTxBuilder {}
276282 client := & mockChainClient {builder : builder }
@@ -280,6 +286,7 @@ func TestSVM_PDANotFound_VotesFailureAndReverts(t *testing.T) {
280286 insertBroadcastedEvent (t , db , "ev-1" , "solana:mainnet" , "solana:mainnet:" , eventData )
281287
282288 builder .On ("IsAlreadyExecuted" , mock .Anything , "tx-123" ).Return (false , nil )
289+ builder .On ("CleanupOutboundArtifacts" , mock .Anything , mock .Anything ).Return (nil ).Once ()
283290
284291 // No PushSigner — voteFailure will log warning and return nil, but won't mark REVERTED
285292 // (because pushSigner is nil, it returns early). This validates the code path.
@@ -291,6 +298,38 @@ func TestSVM_PDANotFound_VotesFailureAndReverts(t *testing.T) {
291298 // The event stays BROADCASTED because the vote+revert is skipped.
292299 updated := getEvent (t , db , "ev-1" )
293300 require .Equal (t , store .StatusBroadcasted , updated .Status )
301+ // Explicit assertion: cleanup must have been invoked once for the failure path.
302+ builder .AssertExpectations (t )
303+ }
304+
305+ func TestSVM_PDANotFound_CleanupErrorIsBestEffort (t * testing.T ) {
306+ // A failure from CleanupOutboundArtifacts must NOT crash the resolver or
307+ // roll back the failure-vote transition. Cleanup is opex-only.
308+ evtStore , db := setupTestDB (t )
309+ builder := & mockTxBuilder {}
310+ client := & mockChainClient {builder : builder }
311+ ch := newTestChains (t , "solana:mainnet" , uregistrytypes .VmType_SVM , client )
312+
313+ eventData := makeOutboundEventData ("tx-123" , "utx-456" , "solana:mainnet" )
314+ insertBroadcastedEvent (t , db , "ev-1" , "solana:mainnet" , "solana:mainnet:" , eventData )
315+
316+ builder .On ("IsAlreadyExecuted" , mock .Anything , "tx-123" ).Return (false , nil )
317+ builder .On ("CleanupOutboundArtifacts" , mock .Anything , mock .Anything ).
318+ Return (assert .AnError ).Once ()
319+
320+ resolver := newResolver (evtStore , ch )
321+ ev := getEvent (t , db , "ev-1" )
322+
323+ // Should not panic even though cleanup returned an error.
324+ require .NotPanics (t , func () {
325+ resolver .resolveSVM (context .Background (), & ev , "solana:mainnet" )
326+ })
327+
328+ // State transition behaves identically to the success-cleanup case
329+ // (BROADCASTED here because no push signer is configured).
330+ updated := getEvent (t , db , "ev-1" )
331+ require .Equal (t , store .StatusBroadcasted , updated .Status )
332+ builder .AssertExpectations (t )
294333}
295334
296335func TestSVM_PDACheckFails_StaysBroadcasted (t * testing.T ) {
0 commit comments