diff --git a/x/vm/keeper/call_evm.go b/x/vm/keeper/call_evm.go index 727f83a9b0..9f1314ddf6 100644 --- a/x/vm/keeper/call_evm.go +++ b/x/vm/keeper/call_evm.go @@ -260,8 +260,9 @@ func (k Keeper) DerivedEVMCallWithData( sdk.NewAttribute(sdk.AttributeKeyAmount, value.String()), // add event for ethereum transaction hash format; sdk.NewAttribute(types.AttributeKeyEthereumTxHash, ethTxHash), - // add event for index of valid ethereum tx; NOTE: default txindex for derivedTx - sdk.NewAttribute(types.AttributeKeyTxIndex, strconv.FormatUint(types.DerivedTxIndex, 10)), + // unique, monotonic eth tx index for this derived tx — drawn from the same + // block-level counter as standard MsgEthereumTx (advanced below). + sdk.NewAttribute(types.AttributeKeyTxIndex, strconv.FormatUint(uint64(txConfig.TxIndex), 10)), // add event for eth tx gas used, we can't get it from cosmos tx result when it contains multiple eth tx msgs. sdk.NewAttribute(types.AttributeKeyTxGasUsed, strconv.FormatUint(gasUsed, 10)), }...) @@ -325,6 +326,10 @@ func (k Keeper) DerivedEVMCallWithData( k.SetLogSizeTransient(ctx, (k.GetLogSizeTransient(ctx))+uint64(len(logs))) } } + + // Advance the block-level eth tx index so the next eth tx (derived or a + // standard MsgEthereumTx) gets a fresh, unique index. Mirrors ApplyTransaction. + k.SetTxIndexTransient(ctx, uint64(txConfig.TxIndex)+1) } if res.Failed() { diff --git a/x/vm/keeper/call_evm_test.go b/x/vm/keeper/call_evm_test.go index 40ac08070e..73da98598d 100644 --- a/x/vm/keeper/call_evm_test.go +++ b/x/vm/keeper/call_evm_test.go @@ -3,6 +3,7 @@ package keeper_test import ( "fmt" "math/big" + "strconv" "github.com/ethereum/go-ethereum/common" @@ -337,3 +338,43 @@ func (suite *KeeperTestSuite) TestDerivedEVMCallCommitFlag() { }) } } + +// TestDerivedEVMCallAssignsUniqueTxIndex is a regression test for F-2026-17745: +// each derived tx in a block must get a unique, monotonically increasing eth tx +// index (not the legacy constant 9999 shared by all derived txs). +func (suite *KeeperTestSuite) TestDerivedEVMCallAssignsUniqueTxIndex() { + suite.SetupTest() + + owner := suite.keyring.GetAddr(0) + recipient := utiltx.GenerateAddress() + + contractAddr := suite.DeployTestContract(suite.T(), suite.network.GetContext(), owner, big.NewInt(1_000_000)) + ctx := suite.network.GetContext().WithEventManager(sdk.NewEventManager()) + + const n = 3 + for i := 0; i < n; i++ { + suite.Require().NoError(suite.derivedTransfer(ctx, owner, contractAddr, recipient)) + } + + // Collect the txIndex attribute emitted on each ethereum_tx event. + var indices []uint64 + for _, e := range ctx.EventManager().Events() { + if e.Type != evmtypes.EventTypeEthereumTx { + continue + } + for _, a := range e.Attributes { + if a.Key == evmtypes.AttributeKeyTxIndex { + v, err := strconv.ParseUint(a.Value, 10, 64) + suite.Require().NoError(err) + indices = append(indices, v) + } + } + } + + suite.Require().Len(indices, n, "one txIndex per derived tx") + suite.Require().NotEqual(uint64(9999), indices[0], "must not use the legacy constant DerivedTxIndex") + for i := 1; i < len(indices); i++ { + suite.Require().Equal(indices[0]+uint64(i), indices[i], + "derived txs must get unique, monotonically increasing indices") + } +} diff --git a/x/vm/types/events.go b/x/vm/types/events.go index 489635e41d..15e5ddf046 100644 --- a/x/vm/types/events.go +++ b/x/vm/types/events.go @@ -30,6 +30,5 @@ const ( ) const ( - DerivedTxIndex = 9999 - DerivedTxType = 99 + DerivedTxType = 99 )