diff --git a/node/pkg/watchers/solana/shim.go b/node/pkg/watchers/solana/shim.go index f0795fe684..f5ce158a99 100644 --- a/node/pkg/watchers/solana/shim.go +++ b/node/pkg/watchers/solana/shim.go @@ -29,6 +29,7 @@ package solana import ( "bytes" "encoding/hex" + "errors" "fmt" "time" @@ -175,6 +176,9 @@ func (s *SolanaWatcher) shimProcessTopLevelInstruction( isReobservation bool, ) (bool, error) { topLevelIdx := uint16(topLevelIndex) + if topLevelIdx >= uint16(len(tx.Message.Instructions)) { + return false, fmt.Errorf("topLevelIndex %d is greater than %d", topLevelIdx, len(tx.Message.Instructions)) + } inst := tx.Message.Instructions[topLevelIdx] // The only top-level instruction generated by the shim contract is the PostMessage event. Parse that to get @@ -192,7 +196,7 @@ func (s *SolanaWatcher) shimProcessTopLevelInstruction( outerIdx := -1 for idx, inner := range innerInstructions { if inner.Index == topLevelIdx { - outerIdx = int(idx) + outerIdx = idx break } } @@ -202,7 +206,7 @@ func (s *SolanaWatcher) shimProcessTopLevelInstruction( } // Process the inner instructions associated with this shim top-level instruction and produce an observation event. - err = s.shimProcessRest(logger, whProgramIndex, shimProgramIndex, tx, innerInstructions[outerIdx].Instructions, outerIdx, 0, postMessage, alreadyProcessed, isReobservation) + err = s.shimProcessRest(logger, whProgramIndex, shimProgramIndex, tx, innerInstructions[outerIdx].Instructions, outerIdx, 0, postMessage, alreadyProcessed, isReobservation, true) if err != nil { return false, fmt.Errorf("failed to process inner instructions for top-level shim instruction %d: %w", topLevelIdx, err) } @@ -226,6 +230,10 @@ func (s *SolanaWatcher) shimProcessInnerInstruction( alreadyProcessed ShimAlreadyProcessed, isReobservation bool, ) (bool, error) { + if startIdx >= len(innerInstructions) { + return false, fmt.Errorf("startIdx %d is greater than or equal to %d", startIdx, len(innerInstructions)) + } + // See if this is a PostMessage event from the shim contract. If so, parse it. If not, bail out now. postMessage, err := shimParsePostMessage(s.shimPostMessageDiscriminator, innerInstructions[startIdx].Data) if err != nil { @@ -238,7 +246,7 @@ func (s *SolanaWatcher) shimProcessInnerInstruction( alreadyProcessed.add(outerIdx, startIdx) - err = s.shimProcessRest(logger, whProgramIndex, shimProgramIndex, tx, innerInstructions, outerIdx, startIdx+1, postMessage, alreadyProcessed, isReobservation) + err = s.shimProcessRest(logger, whProgramIndex, shimProgramIndex, tx, innerInstructions, outerIdx, startIdx+1, postMessage, alreadyProcessed, isReobservation, false) if err != nil { return false, fmt.Errorf("failed to process inner instructions for inner shim instruction %d: %w", outerIdx, err) } @@ -260,7 +268,12 @@ func (s *SolanaWatcher) shimProcessRest( postMessage *ShimPostMessageData, alreadyProcessed ShimAlreadyProcessed, isReobservation bool, + isTopLevel bool, ) error { + if postMessage == nil { + return errors.New("postMessage is nil") + } + // Loop through the inner instructions after the shim PostMessage and do the following: // 1) Find the core event and verify it is unreliable with an empty payload. // 2) Find the shim MessageEvent to get the rest of the fields we need for the observation. @@ -269,17 +282,25 @@ func (s *SolanaWatcher) shimProcessRest( var verifiedCoreEvent bool var messageEvent *ShimMessageEventData var err error - coreEventFound := false for idx := startIdx; idx < len(innerInstructions); idx++ { inst := innerInstructions[idx] if inst.ProgramIDIndex == whProgramIndex { - if verifiedCoreEvent, err = shimVerifyCoreMessage(inst.Data); err != nil { + if verifiedCoreEvent { + return fmt.Errorf("detected multiple inner core instructions when there should not be at instruction %d, %d", outerIdx, idx) + } + foundIt, err := shimVerifyCoreMessage(inst.Data) + if err != nil { return fmt.Errorf("failed to verify inner core instruction for shim instruction %d, %d: %w", outerIdx, idx, err) } - alreadyProcessed.add(outerIdx, idx) - coreEventFound = true + if foundIt { + alreadyProcessed.add(outerIdx, idx) + verifiedCoreEvent = true + } } else if inst.ProgramIDIndex == shimProgramIndex { - if !coreEventFound { + if messageEvent != nil { + return fmt.Errorf("detected multiple shim message event instructions when there should not be at instruction %d, %d", outerIdx, idx) + } + if !verifiedCoreEvent { return fmt.Errorf("detected an inner shim message event instruction before the core event for shim instruction %d, %d: %w", outerIdx, idx, err) } messageEvent, err = shimParseMessageEvent(s.shimMessageEventDiscriminator, inst.Data) @@ -290,16 +311,21 @@ func (s *SolanaWatcher) shimProcessRest( } if verifiedCoreEvent && messageEvent != nil { - break + if !isTopLevel { + // We found what we are looking for, and additional shim events in the inner instruction case are allowed, so we can stop looking. + break + } + + // In the top level instruction case, we should not have any additional shim events in this instruction set. } } if !verifiedCoreEvent { - return fmt.Errorf("failed to find inner core instruction for shim instruction %d", outerIdx) + return fmt.Errorf("failed to find inner core instruction for shim instruction %d, %d", outerIdx, startIdx) } if messageEvent == nil { - return fmt.Errorf("failed to find inner shim message event instruction for shim instruction %d", outerIdx) + return fmt.Errorf("failed to find inner shim message event instruction for shim instruction %d, %d", outerIdx, startIdx) } commitment, err := postMessage.ConsistencyLevel.Commitment() @@ -328,7 +354,9 @@ func (s *SolanaWatcher) shimProcessRest( Payload: postMessage.Payload, ConsistencyLevel: uint8(postMessage.ConsistencyLevel), IsReobservation: isReobservation, - Unreliable: false, + + // Shim messages are always reliable. + Unreliable: false, } solanaMessagesConfirmed.WithLabelValues(s.networkName).Inc() diff --git a/node/pkg/watchers/solana/shim_test.go b/node/pkg/watchers/solana/shim_test.go index 5017fe949c..b8dc894f5f 100644 --- a/node/pkg/watchers/solana/shim_test.go +++ b/node/pkg/watchers/solana/shim_test.go @@ -330,14 +330,13 @@ func TestShimDirect(t *testing.T) { msg := <-msgC require.NotNil(t, msg) - // TODO: Can't check this until we switch MessagePublication.TxHash to be a byte array rather than a hash. - // expectedTxHash, err := vaa.StringToHash("7647cd98fd14c6e3cdfe35bc64bbc476abcdb5ab12e8d31e3151d132ed1e0eeb4595fda4779f69dbe00ff14aadad3fdcf537b88a22f48f3acb7b31f340670506") - // require.NoError(t, err) + expectedTxID, err := hex.DecodeString("7647cd98fd14c6e3cdfe35bc64bbc476abcdb5ab12e8d31e3151d132ed1e0eeb4595fda4779f69dbe00ff14aadad3fdcf537b88a22f48f3acb7b31f340670506") + require.NoError(t, err) expectedEmitterAddress, err := vaa.StringToAddress("041c657e845d65d009d59ceeb1dda172bd6bc9e7ee5a19e56573197cf7fdffde") require.NoError(t, err) - // assert.Equal(t, expectedTxHash, msg.TxHash) + assert.Equal(t, expectedTxID, msg.TxID) assert.Equal(t, time.Unix(int64(1736530812), 0), msg.Timestamp) assert.Equal(t, uint32(42), msg.Nonce) assert.Equal(t, uint64(0), msg.Sequence) @@ -507,14 +506,13 @@ func TestShimFromIntegrator(t *testing.T) { msg := <-msgC require.NotNil(t, msg) - // TODO: Can't check this until we switch MessagePublication.TxHash to be a byte array rather than a hash. - // expectedTxHash, err := vaa.StringToHash("0cfdad68fdee85b49aea65e48c0d8def74f0968e7e1cf2c33305cfc33fec02a4742895c1d32f7c4093f75133104e70bd126fbbf8b71e5d8cb723a390cd976305") - // require.NoError(t, err) + expectedTxID, err := hex.DecodeString("0cfdad68fdee85b49aea65e48c0d8def74f0968e7e1cf2c33305cfc33fec02a4742895c1d32f7c4093f75133104e70bd126fbbf8b71e5d8cb723a390cd976305") + require.NoError(t, err) expectedEmitterAddress, err := vaa.StringToAddress("0726d66bf942e942332ddf34a2edb7b83c4cdfd25b15d4247e2e15057cdfc3cf") require.NoError(t, err) - // assert.Equal(t, expectedTxHash, msg.TxHash) + assert.Equal(t, expectedTxID, msg.TxID) assert.Equal(t, time.Unix(int64(1736542615), 0), msg.Timestamp) assert.Equal(t, uint32(0), msg.Nonce) assert.Equal(t, uint64(1), msg.Sequence) @@ -739,9 +737,8 @@ func TestShimDirectWithMultipleShimTransactions(t *testing.T) { require.True(t, shimFound) require.Equal(t, uint16(6), shimProgramIndex) - // TODO: Can't check this until we switch MessagePublication.TxHash to be a byte array rather than a hash. - // expectedTxHash, err := vaa.StringToHash("7647cd98fd14c6e3cdfe35bc64bbc476abcdb5ab12e8d31e3151d132ed1e0eeb4595fda4779f69dbe00ff14aadad3fdcf537b88a22f48f3acb7b31f340670506") - // require.NoError(t, err) + expectedTxID, err := hex.DecodeString("7647cd98fd14c6e3cdfe35bc64bbc476abcdb5ab12e8d31e3151d132ed1e0eeb4595fda4779f69dbe00ff14aadad3fdcf537b88a22f48f3acb7b31f340670506") + require.NoError(t, err) expectedEmitterAddress, err := vaa.StringToAddress("041c657e845d65d009d59ceeb1dda172bd6bc9e7ee5a19e56573197cf7fdffde") require.NoError(t, err) @@ -757,7 +754,7 @@ func TestShimDirectWithMultipleShimTransactions(t *testing.T) { msg := <-msgC require.NotNil(t, msg) - // assert.Equal(t, expectedTxHash, msg.TxHash) + assert.Equal(t, expectedTxID, msg.TxID) assert.Equal(t, time.Unix(int64(1736530812), 0), msg.Timestamp) assert.Equal(t, uint32(42), msg.Nonce) assert.Equal(t, uint64(0), msg.Sequence) @@ -778,7 +775,7 @@ func TestShimDirectWithMultipleShimTransactions(t *testing.T) { msg = <-msgC require.NotNil(t, msg) - // assert.Equal(t, expectedTxHash, msg.TxHash) + assert.Equal(t, expectedTxID, msg.TxID) assert.Equal(t, time.Unix(int64(1736530813), 0), msg.Timestamp) assert.Equal(t, uint32(43), msg.Nonce) assert.Equal(t, uint64(1), msg.Sequence) @@ -957,9 +954,8 @@ func TestShimFromIntegratorWithMultipleShimTransactions(t *testing.T) { require.True(t, shimFound) require.Equal(t, uint16(7), shimProgramIndex) - // TODO: Can't check this until we switch MessagePublication.TxHash to be a byte array rather than a hash. - // expectedTxHash, err := vaa.StringToHash("0cfdad68fdee85b49aea65e48c0d8def74f0968e7e1cf2c33305cfc33fec02a4742895c1d32f7c4093f75133104e70bd126fbbf8b71e5d8cb723a390cd976305") - // require.NoError(t, err) + expectedTxID, err := hex.DecodeString("0cfdad68fdee85b49aea65e48c0d8def74f0968e7e1cf2c33305cfc33fec02a4742895c1d32f7c4093f75133104e70bd126fbbf8b71e5d8cb723a390cd976305") + require.NoError(t, err) expectedEmitterAddress, err := vaa.StringToAddress("0726d66bf942e942332ddf34a2edb7b83c4cdfd25b15d4247e2e15057cdfc3cf") require.NoError(t, err) @@ -975,7 +971,7 @@ func TestShimFromIntegratorWithMultipleShimTransactions(t *testing.T) { msg := <-msgC require.NotNil(t, msg) - // assert.Equal(t, expectedTxHash, msg.TxHash) + assert.Equal(t, expectedTxID, msg.TxID) assert.Equal(t, time.Unix(int64(1736542615), 0), msg.Timestamp) assert.Equal(t, uint32(0), msg.Nonce) assert.Equal(t, uint64(1), msg.Sequence) @@ -996,7 +992,7 @@ func TestShimFromIntegratorWithMultipleShimTransactions(t *testing.T) { msg = <-msgC require.NotNil(t, msg) - // assert.Equal(t, expectedTxHash, msg.TxHash) + assert.Equal(t, expectedTxID, msg.TxID) assert.Equal(t, time.Unix(int64(1736542616), 0), msg.Timestamp) assert.Equal(t, uint32(42), msg.Nonce) assert.Equal(t, uint64(2), msg.Sequence) @@ -1007,3 +1003,1284 @@ func TestShimFromIntegratorWithMultipleShimTransactions(t *testing.T) { assert.False(t, msg.IsReobservation) assert.False(t, msg.Unreliable) } + +func TestShimDirectWithExtraWhEventBeforeShimEventShouldFails(t *testing.T) { + eventJson := ` + { + "blockTime": 1736530812, + "meta": { + "computeUnitsConsumed": 84252, + "err": null, + "fee": 5000, + "innerInstructions": [ + { + "index": 1, + "instructions": [ + { + "accounts": [1, 3, 0, 4, 0, 2, 8, 5, 9], + "data": "TbyPDfUoyRxsr", + "programIdIndex": 10, + "stackHeight": 2 + }, + { + "accounts": [0, 4], + "data": "3Bxs4NLhqXb3ofom", + "programIdIndex": 5, + "stackHeight": 3 + }, + { + "accounts": [4], + "data": "9krTD1mFP1husSVM", + "programIdIndex": 5, + "stackHeight": 3 + }, + { + "accounts": [4], + "data": "SYXsBvR59WTsF4KEVN8LCQ1X9MekXCGPPNo3Af36taxCQBED", + "programIdIndex": 5, + "stackHeight": 3 + }, + { + "accounts": [0, 3], + "data": "3Bxs4bm7oSCPMeKR", + "programIdIndex": 5, + "stackHeight": 3 + }, + { + "accounts": [3], + "data": "9krTDGKFuDw9nLmM", + "programIdIndex": 5, + "stackHeight": 3 + }, + { + "accounts": [3], + "data": "SYXsBvR59WTsF4KEVN8LCQ1X9MekXCGPPNo3Af36taxCQBED", + "programIdIndex": 5, + "stackHeight": 3 + }, + { + "accounts": [1, 3, 0, 4, 0, 2, 8, 5, 9], + "data": "TbyPDfUoyRxsr", + "programIdIndex": 10, + "stackHeight": 2 + }, + { + "accounts": [7], + "data": "hTEY7jEqBPdDRkTWweeDPgyCUykRXEQVCUwrYmn4HZo84DdQrTJT2nBMiJFB3jXUVxHVd9mGq7BX9htuAN", + "programIdIndex": 6, + "stackHeight": 2 + } + ] + } + ], + "loadedAddresses": { + "readonly": [], + "writable": [] + }, + "logMessages": [ + "Program 11111111111111111111111111111111 invoke [1]", + "Program 11111111111111111111111111111111 success", + "Program EtZMZM22ViKMo4r5y4Anovs3wKQ2owUmDpjygnMMcdEX invoke [1]", + "Program log: Instruction: PostMessage", + "Program worm2ZoG2kUd4vFXhvjh93UUH596ayRfgQ2MgjNMTth invoke [2]", + "Program 11111111111111111111111111111111 invoke [3]", + "Program 11111111111111111111111111111111 success", + "Program 11111111111111111111111111111111 invoke [3]", + "Program 11111111111111111111111111111111 success", + "Program 11111111111111111111111111111111 invoke [3]", + "Program 11111111111111111111111111111111 success", + "Program log: Sequence: 0", + "Program 11111111111111111111111111111111 invoke [3]", + "Program 11111111111111111111111111111111 success", + "Program 11111111111111111111111111111111 invoke [3]", + "Program 11111111111111111111111111111111 success", + "Program 11111111111111111111111111111111 invoke [3]", + "Program 11111111111111111111111111111111 success", + "Program worm2ZoG2kUd4vFXhvjh93UUH596ayRfgQ2MgjNMTth consumed 60384 of 380989 compute units", + "Program worm2ZoG2kUd4vFXhvjh93UUH596ayRfgQ2MgjNMTth success", + "Program EtZMZM22ViKMo4r5y4Anovs3wKQ2owUmDpjygnMMcdEX invoke [2]", + "Program EtZMZM22ViKMo4r5y4Anovs3wKQ2owUmDpjygnMMcdEX consumed 2000 of 318068 compute units", + "Program EtZMZM22ViKMo4r5y4Anovs3wKQ2owUmDpjygnMMcdEX success", + "Program EtZMZM22ViKMo4r5y4Anovs3wKQ2owUmDpjygnMMcdEX consumed 84102 of 399850 compute units", + "Program EtZMZM22ViKMo4r5y4Anovs3wKQ2owUmDpjygnMMcdEX success" + ], + "postBalances": [ + 499999999997496260, 1057920, 2350640170, 1552080, 946560, 1, 1141440, 0, + 1169280, 1009200, 1141440 + ], + "postTokenBalances": [], + "preBalances": [ + 500000000000000000, 1057920, 2350640070, 0, 0, 1, 1141440, 0, 1169280, + 1009200, 1141440 + ], + "preTokenBalances": [], + "rewards": [], + "status": { + "Ok": null + } + }, + "slot": 3, + "transaction": { + "message": { + "header": { + "numReadonlySignedAccounts": 0, + "numReadonlyUnsignedAccounts": 6, + "numRequiredSignatures": 1 + }, + "accountKeys": [ + "H3kCPjpQDT4hgwWHr9E9pC99rZT2yHAwiwSwku6Bne9", + "2yVjuQwpsvdsrywzsJJVs9Ueh4zayyo5DYJbBNc3DDpn", + "9bFNrXNb2WTx8fMHXCheaZqkLZ3YCCaiqTftHxeintHy", + "9vohBn118ZEctRmuTRvoUZg1B1HGfSH8C5QX6twtUFrJ", + "HeccUHmoyMi5S6nuTcyUBh4w4me3FP541a52ErYJRT8a", + "11111111111111111111111111111111", + "EtZMZM22ViKMo4r5y4Anovs3wKQ2owUmDpjygnMMcdEX", + "HQS31aApX3DDkuXgSpV9XyDUNtFgQ31pUn5BNWHG2PSp", + "SysvarC1ock11111111111111111111111111111111", + "SysvarRent111111111111111111111111111111111", + "worm2ZoG2kUd4vFXhvjh93UUH596ayRfgQ2MgjNMTth" + ], + "recentBlockhash": "CMqPGm4icRdNuHsWJUK4Kgu4Cbe2nDQkYNqugQkKPa4Y", + "instructions": [ + { + "accounts": [0, 2], + "data": "3Bxs4HanWsHUZCbH", + "programIdIndex": 5, + "stackHeight": null + }, + { + "accounts": [1, 3, 0, 4, 0, 2, 8, 5, 9, 10, 7, 6], + "data": "3Cn8VBJReY7Bku3RduhBfYpk7tiw1R6pKcTWv9R", + "programIdIndex": 6, + "stackHeight": null + } + ], + "indexToProgramIds": {} + }, + "signatures": [ + "3NACxoZLehbdKGjTWZKTTXJPuovyqAih1AD1BrkYj8nzDAtjiQUEaNmhkoU1jcFfoPTAjrvnaLFgTafNWr3fBrdB" + ] + }, + "version": "legacy" + } + ` + + ///////// A bunch of checks to verify we parsed the JSON correctly. + var txRpc rpc.TransactionWithMeta + err := json.Unmarshal([]byte(eventJson), &txRpc) + require.NoError(t, err) + + tx, err := txRpc.GetParsedTransaction() + require.NoError(t, err) + + require.Equal(t, 2, len(tx.Message.Instructions)) + require.Equal(t, 1, len(txRpc.Meta.InnerInstructions)) + + ///////// Now we start the real test. + + logger := zap.NewNop() + msgC := make(chan *common.MessagePublication, 10) + s := shimNewWatcherForTest(t, msgC) + require.True(t, s.shimEnabled) + + var whProgramIndex uint16 + var shimProgramIndex uint16 + var shimFound bool + for n, key := range tx.Message.AccountKeys { + if key.Equals(s.contract) { + whProgramIndex = uint16(n) + } + if key.Equals(s.shimContractAddr) { + shimProgramIndex = uint16(n) + shimFound = true + } + } + + require.Equal(t, uint16(10), whProgramIndex) + require.True(t, shimFound) + require.Equal(t, uint16(6), shimProgramIndex) + + alreadyProcessed := ShimAlreadyProcessed{} + found, err := s.shimProcessTopLevelInstruction(logger, whProgramIndex, shimProgramIndex, tx, txRpc.Meta.InnerInstructions, 1, alreadyProcessed, false) + require.ErrorContains(t, err, "detected multiple inner core instructions when there should not be") + require.False(t, found) + require.Equal(t, 0, len(s.msgC)) + require.Equal(t, 1, len(alreadyProcessed)) // The first core event will have been added. +} + +func TestShimDirectWithExtraShimEventsShouldFail(t *testing.T) { + eventJson := ` + { + "blockTime": 1736530812, + "meta": { + "computeUnitsConsumed": 84252, + "err": null, + "fee": 5000, + "innerInstructions": [ + { + "index": 1, + "instructions": [ + { + "accounts": [1, 3, 0, 4, 0, 2, 8, 5, 9], + "data": "TbyPDfUoyRxsr", + "programIdIndex": 10, + "stackHeight": 2 + }, + { + "accounts": [0, 4], + "data": "3Bxs4NLhqXb3ofom", + "programIdIndex": 5, + "stackHeight": 3 + }, + { + "accounts": [4], + "data": "9krTD1mFP1husSVM", + "programIdIndex": 5, + "stackHeight": 3 + }, + { + "accounts": [4], + "data": "SYXsBvR59WTsF4KEVN8LCQ1X9MekXCGPPNo3Af36taxCQBED", + "programIdIndex": 5, + "stackHeight": 3 + }, + { + "accounts": [0, 3], + "data": "3Bxs4bm7oSCPMeKR", + "programIdIndex": 5, + "stackHeight": 3 + }, + { + "accounts": [3], + "data": "9krTDGKFuDw9nLmM", + "programIdIndex": 5, + "stackHeight": 3 + }, + { + "accounts": [3], + "data": "SYXsBvR59WTsF4KEVN8LCQ1X9MekXCGPPNo3Af36taxCQBED", + "programIdIndex": 5, + "stackHeight": 3 + }, + { + "accounts": [7], + "data": "hTEY7jEqBPdDRkTWweeDPgyCUykRXEQVCUwrYmn4HZo84DdQrTJT2nBMiJFB3jXUVxHVd9mGq7BX9htuAN", + "programIdIndex": 6, + "stackHeight": 2 + }, + { + "accounts": [7], + "data": "hTEY7jEqBPdDRkTWweeDPgyCUykRXEQVCUwrYmn4HZo84DdQrTJT2nBMiJFB3jXUVxHVd9mGq7BX9htuAN", + "programIdIndex": 6, + "stackHeight": 2 + } + ] + } + ], + "loadedAddresses": { + "readonly": [], + "writable": [] + }, + "logMessages": [ + "Program 11111111111111111111111111111111 invoke [1]", + "Program 11111111111111111111111111111111 success", + "Program EtZMZM22ViKMo4r5y4Anovs3wKQ2owUmDpjygnMMcdEX invoke [1]", + "Program log: Instruction: PostMessage", + "Program worm2ZoG2kUd4vFXhvjh93UUH596ayRfgQ2MgjNMTth invoke [2]", + "Program 11111111111111111111111111111111 invoke [3]", + "Program 11111111111111111111111111111111 success", + "Program 11111111111111111111111111111111 invoke [3]", + "Program 11111111111111111111111111111111 success", + "Program 11111111111111111111111111111111 invoke [3]", + "Program 11111111111111111111111111111111 success", + "Program log: Sequence: 0", + "Program 11111111111111111111111111111111 invoke [3]", + "Program 11111111111111111111111111111111 success", + "Program 11111111111111111111111111111111 invoke [3]", + "Program 11111111111111111111111111111111 success", + "Program 11111111111111111111111111111111 invoke [3]", + "Program 11111111111111111111111111111111 success", + "Program worm2ZoG2kUd4vFXhvjh93UUH596ayRfgQ2MgjNMTth consumed 60384 of 380989 compute units", + "Program worm2ZoG2kUd4vFXhvjh93UUH596ayRfgQ2MgjNMTth success", + "Program EtZMZM22ViKMo4r5y4Anovs3wKQ2owUmDpjygnMMcdEX invoke [2]", + "Program EtZMZM22ViKMo4r5y4Anovs3wKQ2owUmDpjygnMMcdEX consumed 2000 of 318068 compute units", + "Program EtZMZM22ViKMo4r5y4Anovs3wKQ2owUmDpjygnMMcdEX success", + "Program EtZMZM22ViKMo4r5y4Anovs3wKQ2owUmDpjygnMMcdEX consumed 84102 of 399850 compute units", + "Program EtZMZM22ViKMo4r5y4Anovs3wKQ2owUmDpjygnMMcdEX success" + ], + "postBalances": [ + 499999999997496260, 1057920, 2350640170, 1552080, 946560, 1, 1141440, 0, + 1169280, 1009200, 1141440 + ], + "postTokenBalances": [], + "preBalances": [ + 500000000000000000, 1057920, 2350640070, 0, 0, 1, 1141440, 0, 1169280, + 1009200, 1141440 + ], + "preTokenBalances": [], + "rewards": [], + "status": { + "Ok": null + } + }, + "slot": 3, + "transaction": { + "message": { + "header": { + "numReadonlySignedAccounts": 0, + "numReadonlyUnsignedAccounts": 6, + "numRequiredSignatures": 1 + }, + "accountKeys": [ + "H3kCPjpQDT4hgwWHr9E9pC99rZT2yHAwiwSwku6Bne9", + "2yVjuQwpsvdsrywzsJJVs9Ueh4zayyo5DYJbBNc3DDpn", + "9bFNrXNb2WTx8fMHXCheaZqkLZ3YCCaiqTftHxeintHy", + "9vohBn118ZEctRmuTRvoUZg1B1HGfSH8C5QX6twtUFrJ", + "HeccUHmoyMi5S6nuTcyUBh4w4me3FP541a52ErYJRT8a", + "11111111111111111111111111111111", + "EtZMZM22ViKMo4r5y4Anovs3wKQ2owUmDpjygnMMcdEX", + "HQS31aApX3DDkuXgSpV9XyDUNtFgQ31pUn5BNWHG2PSp", + "SysvarC1ock11111111111111111111111111111111", + "SysvarRent111111111111111111111111111111111", + "worm2ZoG2kUd4vFXhvjh93UUH596ayRfgQ2MgjNMTth" + ], + "recentBlockhash": "CMqPGm4icRdNuHsWJUK4Kgu4Cbe2nDQkYNqugQkKPa4Y", + "instructions": [ + { + "accounts": [0, 2], + "data": "3Bxs4HanWsHUZCbH", + "programIdIndex": 5, + "stackHeight": null + }, + { + "accounts": [1, 3, 0, 4, 0, 2, 8, 5, 9, 10, 7, 6], + "data": "3Cn8VBJReY7Bku3RduhBfYpk7tiw1R6pKcTWv9R", + "programIdIndex": 6, + "stackHeight": null + } + ], + "indexToProgramIds": {} + }, + "signatures": [ + "3NACxoZLehbdKGjTWZKTTXJPuovyqAih1AD1BrkYj8nzDAtjiQUEaNmhkoU1jcFfoPTAjrvnaLFgTafNWr3fBrdB" + ] + }, + "version": "legacy" + } + ` + + ///////// A bunch of checks to verify we parsed the JSON correctly. + var txRpc rpc.TransactionWithMeta + err := json.Unmarshal([]byte(eventJson), &txRpc) + require.NoError(t, err) + + tx, err := txRpc.GetParsedTransaction() + require.NoError(t, err) + + require.Equal(t, 2, len(tx.Message.Instructions)) + require.Equal(t, 1, len(txRpc.Meta.InnerInstructions)) + + ///////// Now we start the real test. + + logger := zap.NewNop() + msgC := make(chan *common.MessagePublication, 10) + s := shimNewWatcherForTest(t, msgC) + require.True(t, s.shimEnabled) + + var whProgramIndex uint16 + var shimProgramIndex uint16 + var shimFound bool + for n, key := range tx.Message.AccountKeys { + if key.Equals(s.contract) { + whProgramIndex = uint16(n) + } + if key.Equals(s.shimContractAddr) { + shimProgramIndex = uint16(n) + shimFound = true + } + } + + require.Equal(t, uint16(10), whProgramIndex) + require.True(t, shimFound) + require.Equal(t, uint16(6), shimProgramIndex) + + alreadyProcessed := ShimAlreadyProcessed{} + found, err := s.shimProcessTopLevelInstruction(logger, whProgramIndex, shimProgramIndex, tx, txRpc.Meta.InnerInstructions, 1, alreadyProcessed, false) + require.ErrorContains(t, err, "detected multiple shim message event instructions when there should not be") + require.False(t, found) + require.Equal(t, 0, len(s.msgC)) + require.Equal(t, 2, len(alreadyProcessed)) // The first core and shim events will have been added. +} + +func TestShimDirectWithExtraCoreEventShouldFail(t *testing.T) { + eventJson := ` + { + "blockTime": 1736530812, + "meta": { + "computeUnitsConsumed": 84252, + "err": null, + "fee": 5000, + "innerInstructions": [ + { + "index": 1, + "instructions": [ + { + "accounts": [1, 3, 0, 4, 0, 2, 8, 5, 9], + "data": "TbyPDfUoyRxsr", + "programIdIndex": 10, + "stackHeight": 2 + }, + { + "accounts": [0, 4], + "data": "3Bxs4NLhqXb3ofom", + "programIdIndex": 5, + "stackHeight": 3 + }, + { + "accounts": [4], + "data": "9krTD1mFP1husSVM", + "programIdIndex": 5, + "stackHeight": 3 + }, + { + "accounts": [4], + "data": "SYXsBvR59WTsF4KEVN8LCQ1X9MekXCGPPNo3Af36taxCQBED", + "programIdIndex": 5, + "stackHeight": 3 + }, + { + "accounts": [0, 3], + "data": "3Bxs4bm7oSCPMeKR", + "programIdIndex": 5, + "stackHeight": 3 + }, + { + "accounts": [3], + "data": "9krTDGKFuDw9nLmM", + "programIdIndex": 5, + "stackHeight": 3 + }, + { + "accounts": [3], + "data": "SYXsBvR59WTsF4KEVN8LCQ1X9MekXCGPPNo3Af36taxCQBED", + "programIdIndex": 5, + "stackHeight": 3 + }, + { + "accounts": [7], + "data": "hTEY7jEqBPdDRkTWweeDPgyCUykRXEQVCUwrYmn4HZo84DdQrTJT2nBMiJFB3jXUVxHVd9mGq7BX9htuAN", + "programIdIndex": 6, + "stackHeight": 2 + }, + { + "accounts": [1, 3, 0, 4, 0, 2, 8, 5, 9], + "data": "TbyPDfUoyRxsr", + "programIdIndex": 10, + "stackHeight": 2 + } + ] + } + ], + "loadedAddresses": { + "readonly": [], + "writable": [] + }, + "logMessages": [ + "Program 11111111111111111111111111111111 invoke [1]", + "Program 11111111111111111111111111111111 success", + "Program EtZMZM22ViKMo4r5y4Anovs3wKQ2owUmDpjygnMMcdEX invoke [1]", + "Program log: Instruction: PostMessage", + "Program worm2ZoG2kUd4vFXhvjh93UUH596ayRfgQ2MgjNMTth invoke [2]", + "Program 11111111111111111111111111111111 invoke [3]", + "Program 11111111111111111111111111111111 success", + "Program 11111111111111111111111111111111 invoke [3]", + "Program 11111111111111111111111111111111 success", + "Program 11111111111111111111111111111111 invoke [3]", + "Program 11111111111111111111111111111111 success", + "Program log: Sequence: 0", + "Program 11111111111111111111111111111111 invoke [3]", + "Program 11111111111111111111111111111111 success", + "Program 11111111111111111111111111111111 invoke [3]", + "Program 11111111111111111111111111111111 success", + "Program 11111111111111111111111111111111 invoke [3]", + "Program 11111111111111111111111111111111 success", + "Program worm2ZoG2kUd4vFXhvjh93UUH596ayRfgQ2MgjNMTth consumed 60384 of 380989 compute units", + "Program worm2ZoG2kUd4vFXhvjh93UUH596ayRfgQ2MgjNMTth success", + "Program EtZMZM22ViKMo4r5y4Anovs3wKQ2owUmDpjygnMMcdEX invoke [2]", + "Program EtZMZM22ViKMo4r5y4Anovs3wKQ2owUmDpjygnMMcdEX consumed 2000 of 318068 compute units", + "Program EtZMZM22ViKMo4r5y4Anovs3wKQ2owUmDpjygnMMcdEX success", + "Program EtZMZM22ViKMo4r5y4Anovs3wKQ2owUmDpjygnMMcdEX consumed 84102 of 399850 compute units", + "Program EtZMZM22ViKMo4r5y4Anovs3wKQ2owUmDpjygnMMcdEX success" + ], + "postBalances": [ + 499999999997496260, 1057920, 2350640170, 1552080, 946560, 1, 1141440, 0, + 1169280, 1009200, 1141440 + ], + "postTokenBalances": [], + "preBalances": [ + 500000000000000000, 1057920, 2350640070, 0, 0, 1, 1141440, 0, 1169280, + 1009200, 1141440 + ], + "preTokenBalances": [], + "rewards": [], + "status": { + "Ok": null + } + }, + "slot": 3, + "transaction": { + "message": { + "header": { + "numReadonlySignedAccounts": 0, + "numReadonlyUnsignedAccounts": 6, + "numRequiredSignatures": 1 + }, + "accountKeys": [ + "H3kCPjpQDT4hgwWHr9E9pC99rZT2yHAwiwSwku6Bne9", + "2yVjuQwpsvdsrywzsJJVs9Ueh4zayyo5DYJbBNc3DDpn", + "9bFNrXNb2WTx8fMHXCheaZqkLZ3YCCaiqTftHxeintHy", + "9vohBn118ZEctRmuTRvoUZg1B1HGfSH8C5QX6twtUFrJ", + "HeccUHmoyMi5S6nuTcyUBh4w4me3FP541a52ErYJRT8a", + "11111111111111111111111111111111", + "EtZMZM22ViKMo4r5y4Anovs3wKQ2owUmDpjygnMMcdEX", + "HQS31aApX3DDkuXgSpV9XyDUNtFgQ31pUn5BNWHG2PSp", + "SysvarC1ock11111111111111111111111111111111", + "SysvarRent111111111111111111111111111111111", + "worm2ZoG2kUd4vFXhvjh93UUH596ayRfgQ2MgjNMTth" + ], + "recentBlockhash": "CMqPGm4icRdNuHsWJUK4Kgu4Cbe2nDQkYNqugQkKPa4Y", + "instructions": [ + { + "accounts": [0, 2], + "data": "3Bxs4HanWsHUZCbH", + "programIdIndex": 5, + "stackHeight": null + }, + { + "accounts": [1, 3, 0, 4, 0, 2, 8, 5, 9, 10, 7, 6], + "data": "3Cn8VBJReY7Bku3RduhBfYpk7tiw1R6pKcTWv9R", + "programIdIndex": 6, + "stackHeight": null + } + ], + "indexToProgramIds": {} + }, + "signatures": [ + "3NACxoZLehbdKGjTWZKTTXJPuovyqAih1AD1BrkYj8nzDAtjiQUEaNmhkoU1jcFfoPTAjrvnaLFgTafNWr3fBrdB" + ] + }, + "version": "legacy" + } + ` + + ///////// A bunch of checks to verify we parsed the JSON correctly. + var txRpc rpc.TransactionWithMeta + err := json.Unmarshal([]byte(eventJson), &txRpc) + require.NoError(t, err) + + tx, err := txRpc.GetParsedTransaction() + require.NoError(t, err) + + require.Equal(t, 2, len(tx.Message.Instructions)) + require.Equal(t, 1, len(txRpc.Meta.InnerInstructions)) + + ///////// Now we start the real test. + + logger := zap.NewNop() + msgC := make(chan *common.MessagePublication, 10) + s := shimNewWatcherForTest(t, msgC) + require.True(t, s.shimEnabled) + + var whProgramIndex uint16 + var shimProgramIndex uint16 + var shimFound bool + for n, key := range tx.Message.AccountKeys { + if key.Equals(s.contract) { + whProgramIndex = uint16(n) + } + if key.Equals(s.shimContractAddr) { + shimProgramIndex = uint16(n) + shimFound = true + } + } + + require.Equal(t, uint16(10), whProgramIndex) + require.True(t, shimFound) + require.Equal(t, uint16(6), shimProgramIndex) + + alreadyProcessed := ShimAlreadyProcessed{} + found, err := s.shimProcessTopLevelInstruction(logger, whProgramIndex, shimProgramIndex, tx, txRpc.Meta.InnerInstructions, 1, alreadyProcessed, false) + require.ErrorContains(t, err, "detected multiple inner core instructions when there should not be") + require.False(t, found) + require.Equal(t, 0, len(s.msgC)) + require.Equal(t, 2, len(alreadyProcessed)) // The first core and shim events will have been added. +} + +func TestShimTopLevelShouldFailIfNoInstructionsShouldFail(t *testing.T) { + eventJson := ` + { + "blockTime": 1736530812, + "meta": { + "computeUnitsConsumed": 84252, + "err": null, + "fee": 5000, + "innerInstructions": [ + { + "index": 1, + "instructions": [ + { + "accounts": [1, 3, 0, 4, 0, 2, 8, 5, 9], + "data": "TbyPDfUoyRxsr", + "programIdIndex": 10, + "stackHeight": 2 + }, + { + "accounts": [0, 4], + "data": "3Bxs4NLhqXb3ofom", + "programIdIndex": 5, + "stackHeight": 3 + }, + { + "accounts": [4], + "data": "9krTD1mFP1husSVM", + "programIdIndex": 5, + "stackHeight": 3 + }, + { + "accounts": [4], + "data": "SYXsBvR59WTsF4KEVN8LCQ1X9MekXCGPPNo3Af36taxCQBED", + "programIdIndex": 5, + "stackHeight": 3 + }, + { + "accounts": [0, 3], + "data": "3Bxs4bm7oSCPMeKR", + "programIdIndex": 5, + "stackHeight": 3 + }, + { + "accounts": [3], + "data": "9krTDGKFuDw9nLmM", + "programIdIndex": 5, + "stackHeight": 3 + }, + { + "accounts": [3], + "data": "SYXsBvR59WTsF4KEVN8LCQ1X9MekXCGPPNo3Af36taxCQBED", + "programIdIndex": 5, + "stackHeight": 3 + }, + { + "accounts": [7], + "data": "hTEY7jEqBPdDRkTWweeDPgyCUykRXEQVCUwrYmn4HZo84DdQrTJT2nBMiJFB3jXUVxHVd9mGq7BX9htuAN", + "programIdIndex": 6, + "stackHeight": 2 + }, + { + "accounts": [1, 3, 0, 4, 0, 2, 8, 5, 9], + "data": "TbyPDfUoyRxsr", + "programIdIndex": 10, + "stackHeight": 2 + } + ] + } + ], + "loadedAddresses": { + "readonly": [], + "writable": [] + }, + "logMessages": [ + "Program 11111111111111111111111111111111 invoke [1]", + "Program 11111111111111111111111111111111 success", + "Program EtZMZM22ViKMo4r5y4Anovs3wKQ2owUmDpjygnMMcdEX invoke [1]", + "Program log: Instruction: PostMessage", + "Program worm2ZoG2kUd4vFXhvjh93UUH596ayRfgQ2MgjNMTth invoke [2]", + "Program 11111111111111111111111111111111 invoke [3]", + "Program 11111111111111111111111111111111 success", + "Program 11111111111111111111111111111111 invoke [3]", + "Program 11111111111111111111111111111111 success", + "Program 11111111111111111111111111111111 invoke [3]", + "Program 11111111111111111111111111111111 success", + "Program log: Sequence: 0", + "Program 11111111111111111111111111111111 invoke [3]", + "Program 11111111111111111111111111111111 success", + "Program 11111111111111111111111111111111 invoke [3]", + "Program 11111111111111111111111111111111 success", + "Program 11111111111111111111111111111111 invoke [3]", + "Program 11111111111111111111111111111111 success", + "Program worm2ZoG2kUd4vFXhvjh93UUH596ayRfgQ2MgjNMTth consumed 60384 of 380989 compute units", + "Program worm2ZoG2kUd4vFXhvjh93UUH596ayRfgQ2MgjNMTth success", + "Program EtZMZM22ViKMo4r5y4Anovs3wKQ2owUmDpjygnMMcdEX invoke [2]", + "Program EtZMZM22ViKMo4r5y4Anovs3wKQ2owUmDpjygnMMcdEX consumed 2000 of 318068 compute units", + "Program EtZMZM22ViKMo4r5y4Anovs3wKQ2owUmDpjygnMMcdEX success", + "Program EtZMZM22ViKMo4r5y4Anovs3wKQ2owUmDpjygnMMcdEX consumed 84102 of 399850 compute units", + "Program EtZMZM22ViKMo4r5y4Anovs3wKQ2owUmDpjygnMMcdEX success" + ], + "postBalances": [ + 499999999997496260, 1057920, 2350640170, 1552080, 946560, 1, 1141440, 0, + 1169280, 1009200, 1141440 + ], + "postTokenBalances": [], + "preBalances": [ + 500000000000000000, 1057920, 2350640070, 0, 0, 1, 1141440, 0, 1169280, + 1009200, 1141440 + ], + "preTokenBalances": [], + "rewards": [], + "status": { + "Ok": null + } + }, + "slot": 3, + "transaction": { + "message": { + "header": { + "numReadonlySignedAccounts": 0, + "numReadonlyUnsignedAccounts": 6, + "numRequiredSignatures": 1 + }, + "accountKeys": [ + "H3kCPjpQDT4hgwWHr9E9pC99rZT2yHAwiwSwku6Bne9", + "2yVjuQwpsvdsrywzsJJVs9Ueh4zayyo5DYJbBNc3DDpn", + "9bFNrXNb2WTx8fMHXCheaZqkLZ3YCCaiqTftHxeintHy", + "9vohBn118ZEctRmuTRvoUZg1B1HGfSH8C5QX6twtUFrJ", + "HeccUHmoyMi5S6nuTcyUBh4w4me3FP541a52ErYJRT8a", + "11111111111111111111111111111111", + "EtZMZM22ViKMo4r5y4Anovs3wKQ2owUmDpjygnMMcdEX", + "HQS31aApX3DDkuXgSpV9XyDUNtFgQ31pUn5BNWHG2PSp", + "SysvarC1ock11111111111111111111111111111111", + "SysvarRent111111111111111111111111111111111", + "worm2ZoG2kUd4vFXhvjh93UUH596ayRfgQ2MgjNMTth" + ], + "recentBlockhash": "CMqPGm4icRdNuHsWJUK4Kgu4Cbe2nDQkYNqugQkKPa4Y", + "instructions": [ + ], + "indexToProgramIds": {} + }, + "signatures": [ + "3NACxoZLehbdKGjTWZKTTXJPuovyqAih1AD1BrkYj8nzDAtjiQUEaNmhkoU1jcFfoPTAjrvnaLFgTafNWr3fBrdB" + ] + }, + "version": "legacy" + } + ` + + ///////// A bunch of checks to verify we parsed the JSON correctly. + var txRpc rpc.TransactionWithMeta + err := json.Unmarshal([]byte(eventJson), &txRpc) + require.NoError(t, err) + + tx, err := txRpc.GetParsedTransaction() + require.NoError(t, err) + + require.Equal(t, 0, len(tx.Message.Instructions)) + require.Equal(t, 1, len(txRpc.Meta.InnerInstructions)) + + ///////// Now we start the real test. + + logger := zap.NewNop() + msgC := make(chan *common.MessagePublication, 10) + s := shimNewWatcherForTest(t, msgC) + require.True(t, s.shimEnabled) + + var whProgramIndex uint16 + var shimProgramIndex uint16 + var shimFound bool + for n, key := range tx.Message.AccountKeys { + if key.Equals(s.contract) { + whProgramIndex = uint16(n) + } + if key.Equals(s.shimContractAddr) { + shimProgramIndex = uint16(n) + shimFound = true + } + } + + require.Equal(t, uint16(10), whProgramIndex) + require.True(t, shimFound) + require.Equal(t, uint16(6), shimProgramIndex) + + alreadyProcessed := ShimAlreadyProcessed{} + found, err := s.shimProcessTopLevelInstruction(logger, whProgramIndex, shimProgramIndex, tx, txRpc.Meta.InnerInstructions, 1, alreadyProcessed, false) + require.ErrorContains(t, err, "topLevelIndex 1 is greater than 0") + require.False(t, found) + require.Equal(t, 0, len(s.msgC)) + require.Equal(t, 0, len(alreadyProcessed)) +} + +func TestShimInnerShouldFailIfNoInstructionsShouldFail(t *testing.T) { + eventJson := ` + { + "blockTime": 1736542615, + "meta": { + "computeUnitsConsumed": 48958, + "err": null, + "fee": 5000, + "innerInstructions": [ + { + "index": 1, + "instructions": [ + { + "accounts": [1, 4, 11, 3, 0, 2, 9, 5, 10, 12, 8, 7], + "data": "BeHixXyfSZ8dzFJzxTYRV18L6KSgTuqcTjaqeXgDVbXHC7mCjAgSyhz", + "programIdIndex": 7, + "stackHeight": 2 + }, + { + "accounts": [1, 4, 11, 3, 0, 2, 9, 5, 10], + "data": "T4xyMHqZi66JU", + "programIdIndex": 12, + "stackHeight": 3 + }, + { + "accounts": [8], + "data": "hTEY7jEqBPdDRkTWweeDPgzBpsiybJCHnVTVt8aCDem8p58yeQcQLJWk7hgGHrX79qZyKmCM89vCgPY7SE", + "programIdIndex": 7, + "stackHeight": 3 + } + ] + } + ], + "loadedAddresses": { "readonly": [], "writable": [] }, + "logMessages": [ + "Program 11111111111111111111111111111111 invoke [1]", + "Program 11111111111111111111111111111111 success", + "Program AEwubmehHNvkMXoH2C5MgDSemZgQ3HUSYpeaF3UrNZdQ invoke [1]", + "Program log: Instruction: PostMessage", + "Program EtZMZM22ViKMo4r5y4Anovs3wKQ2owUmDpjygnMMcdEX invoke [2]", + "Program log: Instruction: PostMessage", + "Program worm2ZoG2kUd4vFXhvjh93UUH596ayRfgQ2MgjNMTth invoke [3]", + "Program log: Sequence: 1", + "Program worm2ZoG2kUd4vFXhvjh93UUH596ayRfgQ2MgjNMTth consumed 18679 of 375180 compute units", + "Program worm2ZoG2kUd4vFXhvjh93UUH596ayRfgQ2MgjNMTth success", + "Program EtZMZM22ViKMo4r5y4Anovs3wKQ2owUmDpjygnMMcdEX invoke [3]", + "Program EtZMZM22ViKMo4r5y4Anovs3wKQ2owUmDpjygnMMcdEX consumed 2000 of 353964 compute units", + "Program EtZMZM22ViKMo4r5y4Anovs3wKQ2owUmDpjygnMMcdEX success", + "Program EtZMZM22ViKMo4r5y4Anovs3wKQ2owUmDpjygnMMcdEX consumed 33649 of 385286 compute units", + "Program EtZMZM22ViKMo4r5y4Anovs3wKQ2owUmDpjygnMMcdEX success", + "Program AEwubmehHNvkMXoH2C5MgDSemZgQ3HUSYpeaF3UrNZdQ consumed 48808 of 399850 compute units", + "Program AEwubmehHNvkMXoH2C5MgDSemZgQ3HUSYpeaF3UrNZdQ success" + ], + "postBalances": [ + 499999999997491140, 1057920, 2350640270, 946560, 1552080, 1, 1141440, + 1141440, 0, 1169280, 1009200, 0, 1141440 + ], + "postTokenBalances": [], + "preBalances": [ + 499999999997496260, 1057920, 2350640170, 946560, 1552080, 1, 1141440, + 1141440, 0, 1169280, 1009200, 0, 1141440 + ], + "preTokenBalances": [], + "rewards": [], + "status": { "Ok": null } + }, + "slot": 5, + "transaction": { + "message": { + "header": { + "numReadonlySignedAccounts": 0, + "numReadonlyUnsignedAccounts": 8, + "numRequiredSignatures": 1 + }, + "accountKeys": [ + "H3kCPjpQDT4hgwWHr9E9pC99rZT2yHAwiwSwku6Bne9", + "2yVjuQwpsvdsrywzsJJVs9Ueh4zayyo5DYJbBNc3DDpn", + "9bFNrXNb2WTx8fMHXCheaZqkLZ3YCCaiqTftHxeintHy", + "G4zDzQLktwvU4rn6A4dSAy9eU76cJxppCaumZhjjhXjv", + "GXUAWs1h6Nh1KLByvfeEyig9yn92LmKMjXDNxHGddyXR", + "11111111111111111111111111111111", + "AEwubmehHNvkMXoH2C5MgDSemZgQ3HUSYpeaF3UrNZdQ", + "EtZMZM22ViKMo4r5y4Anovs3wKQ2owUmDpjygnMMcdEX", + "HQS31aApX3DDkuXgSpV9XyDUNtFgQ31pUn5BNWHG2PSp", + "SysvarC1ock11111111111111111111111111111111", + "SysvarRent111111111111111111111111111111111", + "UvCifi1D8qj5FSJQdWL3KENnmaZjm62XUMa7NReceer", + "worm2ZoG2kUd4vFXhvjh93UUH596ayRfgQ2MgjNMTth" + ], + "recentBlockhash": "EqNQXbHebHwD1Vs4BSStmUVh2y6GjMxF3NBsDXsYuvRh", + "instructions": [ + { + "accounts": [0, 2], + "data": "3Bxs4HanWsHUZCbH", + "programIdIndex": 5, + "stackHeight": null + }, + { + "accounts": [0, 7, 1, 4, 11, 3, 2, 9, 5, 10, 12, 8], + "data": "cpyiD6CEaBD", + "programIdIndex": 6, + "stackHeight": null + } + ], + "indexToProgramIds": {} + }, + "signatures": [ + "G4jVHcH6F4Np1NRvYC6ridv5jGfPSVGgiEVZrjprpMdBFhJH7eVxUuxsvkDF2rkx4JseUftz3HnWoSomGt3czSY" + ] + }, + "version": "legacy" + } + ` + + ///////// A bunch of checks to verify we parsed the JSON correctly. + var txRpc rpc.TransactionWithMeta + err := json.Unmarshal([]byte(eventJson), &txRpc) + require.NoError(t, err) + + tx, err := txRpc.GetParsedTransaction() + require.NoError(t, err) + + require.Equal(t, 2, len(tx.Message.Instructions)) + require.Equal(t, 1, len(txRpc.Meta.InnerInstructions)) + + ///////// Now we start the real test. + + logger := zap.NewNop() + msgC := make(chan *common.MessagePublication, 10) + s := shimNewWatcherForTest(t, msgC) + require.True(t, s.shimEnabled) + + var whProgramIndex uint16 + var shimProgramIndex uint16 + var shimFound bool + for n, key := range tx.Message.AccountKeys { + if key.Equals(s.contract) { + whProgramIndex = uint16(n) + } + if key.Equals(s.shimContractAddr) { + shimProgramIndex = uint16(n) + shimFound = true + } + } + + require.Equal(t, uint16(12), whProgramIndex) + require.True(t, shimFound) + require.Equal(t, uint16(7), shimProgramIndex) + + alreadyProcessed := ShimAlreadyProcessed{} + found, err := s.shimProcessInnerInstruction(logger, whProgramIndex, shimProgramIndex, tx, txRpc.Meta.InnerInstructions[0].Instructions, 0, len(txRpc.Meta.InnerInstructions[0].Instructions), alreadyProcessed, false) + require.ErrorContains(t, err, "startIdx 3 is greater than or equal to 3") + require.False(t, found) + require.Equal(t, 0, len(s.msgC)) + require.Equal(t, 0, len(alreadyProcessed)) +} + +func TestShimWhPostMessageInUnexpectedFormatShouldNotBeCountedAsShimMessage(t *testing.T) { + // The WH instruction in the first slot is `012a0000000000000001` which is a reliable with no payload. + // The WH instruction for a shim event should be `082a0000000000000001` which is unreliable with no payload. + // So this instruction should not be counted as part of a shim event. + eventJson := ` + { + "meta": { + "innerInstructions": [ + { + "index": 1, + "instructions": [ + { + "accounts": [1, 3, 0, 4, 0, 2, 8, 5, 9], + "data": "4o1AAQkKMUkzL", + "programIdIndex": 10, + "stackHeight": 2 + }, + { + "accounts": [7], + "data": "hTEY7jEqBPdDRkTWweeDPgyCUykRXEQVCUwrYmn4HZo84DdQrTJT2nBMiJFB3jXUVxHVd9mGq7BX9htuAN", + "programIdIndex": 6, + "stackHeight": 2 + }, + { + "accounts": [1, 3, 0, 4, 0, 2, 8, 5, 9], + "data": "TbyPDfUoyRxsr", + "programIdIndex": 10, + "stackHeight": 2 + } + ] + } + ] + }, + "transaction": { + "message": { + "accountKeys": [ + "H3kCPjpQDT4hgwWHr9E9pC99rZT2yHAwiwSwku6Bne9", + "2yVjuQwpsvdsrywzsJJVs9Ueh4zayyo5DYJbBNc3DDpn", + "9bFNrXNb2WTx8fMHXCheaZqkLZ3YCCaiqTftHxeintHy", + "9vohBn118ZEctRmuTRvoUZg1B1HGfSH8C5QX6twtUFrJ", + "HeccUHmoyMi5S6nuTcyUBh4w4me3FP541a52ErYJRT8a", + "11111111111111111111111111111111", + "EtZMZM22ViKMo4r5y4Anovs3wKQ2owUmDpjygnMMcdEX", + "HQS31aApX3DDkuXgSpV9XyDUNtFgQ31pUn5BNWHG2PSp", + "SysvarC1ock11111111111111111111111111111111", + "SysvarRent111111111111111111111111111111111", + "worm2ZoG2kUd4vFXhvjh93UUH596ayRfgQ2MgjNMTth" + ], + "instructions": [ + { + "accounts": [0, 2], + "data": "3Bxs4HanWsHUZCbH", + "programIdIndex": 5, + "stackHeight": null + }, + { + "accounts": [1, 3, 0, 4, 0, 2, 8, 5, 9, 10, 7, 6], + "data": "3Cn8VBJReY7Bku3RduhBfYpk7tiw1R6pKcTWv9R", + "programIdIndex": 6, + "stackHeight": null + } + ] + }, + "signatures": [ + "3NACxoZLehbdKGjTWZKTTXJPuovyqAih1AD1BrkYj8nzDAtjiQUEaNmhkoU1jcFfoPTAjrvnaLFgTafNWr3fBrdB" + ] + } + } + ` + + ///////// A bunch of checks to verify we parsed the JSON correctly. + var txRpc rpc.TransactionWithMeta + err := json.Unmarshal([]byte(eventJson), &txRpc) + require.NoError(t, err) + + tx, err := txRpc.GetParsedTransaction() + require.NoError(t, err) + + require.Equal(t, 2, len(tx.Message.Instructions)) + require.Equal(t, 1, len(txRpc.Meta.InnerInstructions)) + + ///////// Set up the watcher and do the one-time transaction processing. + + logger := zap.NewNop() + msgC := make(chan *common.MessagePublication, 10) + s := shimNewWatcherForTest(t, msgC) + require.True(t, s.shimEnabled) + + var whProgramIndex uint16 + var shimProgramIndex uint16 + var shimFound bool + for n, key := range tx.Message.AccountKeys { + if key.Equals(s.contract) { + whProgramIndex = uint16(n) + } + if key.Equals(s.shimContractAddr) { + shimProgramIndex = uint16(n) + shimFound = true + } + } + + require.Equal(t, uint16(10), whProgramIndex) + require.True(t, shimFound) + require.Equal(t, uint16(6), shimProgramIndex) + + alreadyProcessed := ShimAlreadyProcessed{} + found, err := s.shimProcessTopLevelInstruction( + logger, + whProgramIndex, + shimProgramIndex, + tx, + txRpc.Meta.InnerInstructions, + 1, + alreadyProcessed, + false, + ) + + require.ErrorContains(t, err, "detected an inner shim message event instruction before the core event for shim instruction") + require.False(t, found) + require.Equal(t, 0, len(s.msgC)) + require.Equal(t, 0, len(alreadyProcessed)) +} + +func TestShimProcessRestWithNullEventShouldFail(t *testing.T) { + eventJson := ` + { + "blockTime": 1736530812, + "meta": { + "computeUnitsConsumed": 84252, + "err": null, + "fee": 5000, + "innerInstructions": [ + { + "index": 1, + "instructions": [ + { + "accounts": [1, 3, 0, 4, 0, 2, 8, 5, 9], + "data": "TbyPDfUoyRxsr", + "programIdIndex": 10, + "stackHeight": 2 + }, + { + "accounts": [0, 4], + "data": "3Bxs4NLhqXb3ofom", + "programIdIndex": 5, + "stackHeight": 3 + }, + { + "accounts": [4], + "data": "9krTD1mFP1husSVM", + "programIdIndex": 5, + "stackHeight": 3 + }, + { + "accounts": [4], + "data": "SYXsBvR59WTsF4KEVN8LCQ1X9MekXCGPPNo3Af36taxCQBED", + "programIdIndex": 5, + "stackHeight": 3 + }, + { + "accounts": [0, 3], + "data": "3Bxs4bm7oSCPMeKR", + "programIdIndex": 5, + "stackHeight": 3 + }, + { + "accounts": [3], + "data": "9krTDGKFuDw9nLmM", + "programIdIndex": 5, + "stackHeight": 3 + }, + { + "accounts": [3], + "data": "SYXsBvR59WTsF4KEVN8LCQ1X9MekXCGPPNo3Af36taxCQBED", + "programIdIndex": 5, + "stackHeight": 3 + }, + { + "accounts": [7], + "data": "hTEY7jEqBPdDRkTWweeDPgyCUykRXEQVCUwrYmn4HZo84DdQrTJT2nBMiJFB3jXUVxHVd9mGq7BX9htuAN", + "programIdIndex": 6, + "stackHeight": 2 + } + ] + } + ], + "loadedAddresses": { + "readonly": [], + "writable": [] + }, + "logMessages": [ + "Program 11111111111111111111111111111111 invoke [1]", + "Program 11111111111111111111111111111111 success", + "Program EtZMZM22ViKMo4r5y4Anovs3wKQ2owUmDpjygnMMcdEX invoke [1]", + "Program log: Instruction: PostMessage", + "Program worm2ZoG2kUd4vFXhvjh93UUH596ayRfgQ2MgjNMTth invoke [2]", + "Program 11111111111111111111111111111111 invoke [3]", + "Program 11111111111111111111111111111111 success", + "Program 11111111111111111111111111111111 invoke [3]", + "Program 11111111111111111111111111111111 success", + "Program 11111111111111111111111111111111 invoke [3]", + "Program 11111111111111111111111111111111 success", + "Program log: Sequence: 0", + "Program 11111111111111111111111111111111 invoke [3]", + "Program 11111111111111111111111111111111 success", + "Program 11111111111111111111111111111111 invoke [3]", + "Program 11111111111111111111111111111111 success", + "Program 11111111111111111111111111111111 invoke [3]", + "Program 11111111111111111111111111111111 success", + "Program worm2ZoG2kUd4vFXhvjh93UUH596ayRfgQ2MgjNMTth consumed 60384 of 380989 compute units", + "Program worm2ZoG2kUd4vFXhvjh93UUH596ayRfgQ2MgjNMTth success", + "Program EtZMZM22ViKMo4r5y4Anovs3wKQ2owUmDpjygnMMcdEX invoke [2]", + "Program EtZMZM22ViKMo4r5y4Anovs3wKQ2owUmDpjygnMMcdEX consumed 2000 of 318068 compute units", + "Program EtZMZM22ViKMo4r5y4Anovs3wKQ2owUmDpjygnMMcdEX success", + "Program EtZMZM22ViKMo4r5y4Anovs3wKQ2owUmDpjygnMMcdEX consumed 84102 of 399850 compute units", + "Program EtZMZM22ViKMo4r5y4Anovs3wKQ2owUmDpjygnMMcdEX success" + ], + "postBalances": [ + 499999999997496260, 1057920, 2350640170, 1552080, 946560, 1, 1141440, 0, + 1169280, 1009200, 1141440 + ], + "postTokenBalances": [], + "preBalances": [ + 500000000000000000, 1057920, 2350640070, 0, 0, 1, 1141440, 0, 1169280, + 1009200, 1141440 + ], + "preTokenBalances": [], + "rewards": [], + "status": { + "Ok": null + } + }, + "slot": 3, + "transaction": { + "message": { + "header": { + "numReadonlySignedAccounts": 0, + "numReadonlyUnsignedAccounts": 6, + "numRequiredSignatures": 1 + }, + "accountKeys": [ + "H3kCPjpQDT4hgwWHr9E9pC99rZT2yHAwiwSwku6Bne9", + "2yVjuQwpsvdsrywzsJJVs9Ueh4zayyo5DYJbBNc3DDpn", + "9bFNrXNb2WTx8fMHXCheaZqkLZ3YCCaiqTftHxeintHy", + "9vohBn118ZEctRmuTRvoUZg1B1HGfSH8C5QX6twtUFrJ", + "HeccUHmoyMi5S6nuTcyUBh4w4me3FP541a52ErYJRT8a", + "11111111111111111111111111111111", + "EtZMZM22ViKMo4r5y4Anovs3wKQ2owUmDpjygnMMcdEX", + "HQS31aApX3DDkuXgSpV9XyDUNtFgQ31pUn5BNWHG2PSp", + "SysvarC1ock11111111111111111111111111111111", + "SysvarRent111111111111111111111111111111111", + "worm2ZoG2kUd4vFXhvjh93UUH596ayRfgQ2MgjNMTth" + ], + "recentBlockhash": "CMqPGm4icRdNuHsWJUK4Kgu4Cbe2nDQkYNqugQkKPa4Y", + "instructions": [ + { + "accounts": [0, 2], + "data": "3Bxs4HanWsHUZCbH", + "programIdIndex": 5, + "stackHeight": null + }, + { + "accounts": [1, 3, 0, 4, 0, 2, 8, 5, 9, 10, 7, 6], + "data": "3Cn8VBJReY7Bku3RduhBfYpk7tiw1R6pKcTWv9R", + "programIdIndex": 6, + "stackHeight": null + } + ], + "indexToProgramIds": {} + }, + "signatures": [ + "3NACxoZLehbdKGjTWZKTTXJPuovyqAih1AD1BrkYj8nzDAtjiQUEaNmhkoU1jcFfoPTAjrvnaLFgTafNWr3fBrdB" + ] + }, + "version": "legacy" + } + ` + + ///////// A bunch of checks to verify we parsed the JSON correctly. + var txRpc rpc.TransactionWithMeta + err := json.Unmarshal([]byte(eventJson), &txRpc) + require.NoError(t, err) + + tx, err := txRpc.GetParsedTransaction() + require.NoError(t, err) + + require.Equal(t, 2, len(tx.Message.Instructions)) + require.Equal(t, 1, len(txRpc.Meta.InnerInstructions)) + + ///////// Now we start the real test. + + logger := zap.NewNop() + msgC := make(chan *common.MessagePublication, 10) + s := shimNewWatcherForTest(t, msgC) + require.True(t, s.shimEnabled) + + var whProgramIndex uint16 + var shimProgramIndex uint16 + var shimFound bool + for n, key := range tx.Message.AccountKeys { + if key.Equals(s.contract) { + whProgramIndex = uint16(n) + } + if key.Equals(s.shimContractAddr) { + shimProgramIndex = uint16(n) + shimFound = true + } + } + + require.Equal(t, uint16(10), whProgramIndex) + require.True(t, shimFound) + require.Equal(t, uint16(6), shimProgramIndex) + + alreadyProcessed := ShimAlreadyProcessed{} + err = s.shimProcessRest(logger, whProgramIndex, shimProgramIndex, tx, txRpc.Meta.InnerInstructions[0].Instructions, 0, 10, nil, alreadyProcessed, false, true) + require.ErrorContains(t, err, "postMessage is nil") + require.Equal(t, 0, len(s.msgC)) + require.Equal(t, 0, len(alreadyProcessed)) +}