diff --git a/pkg/core/blockchain_neotest_test.go b/pkg/core/blockchain_neotest_test.go index 22523882b3..00bb5bf925 100644 --- a/pkg/core/blockchain_neotest_test.go +++ b/pkg/core/blockchain_neotest_test.go @@ -43,6 +43,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag" "github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger" "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/nspcc-dev/neo-go/pkg/vm" "github.com/nspcc-dev/neo-go/pkg/vm/emit" "github.com/nspcc-dev/neo-go/pkg/vm/opcode" "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" @@ -2697,3 +2698,62 @@ func TestEngineLimits(t *testing.T) { // ProduceLargeObject: hit the limit. cInv.InvokeFail(t, "stack is too big", "produceLargeObject", 500) } + +// TestRuntimeNotifyRefcounting tries to emit more than MaxStackSize notifications. +func TestRuntimeNotifyRefcounting(t *testing.T) { + bc, acc := chain.NewSingle(t) + e := neotest.NewExecutor(t, bc, acc, acc) + + src := `package test + import ( + "github.com/nspcc-dev/neo-go/pkg/interop/runtime" + ) + // args is an array of LargeEvent parameters containing 500 empty strings. + var args = []any{"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "" }; + func ProduceNumerousNotifications(count int) { + for i := 0; i < count; i++ { + runtime.Notify("LargeEvent", args...) + } + }` + const eArgsCount = 500 + eParams := make([]compiler.HybridParameter, eArgsCount) + for i := range eParams { + eParams[i].Name = fmt.Sprintf("str%d", i) + eParams[i].Type = smartcontract.ByteArrayType + } + c := neotest.CompileSource(t, acc.ScriptHash(), strings.NewReader(src), &compiler.Options{ + Name: "test_contract", + ContractEvents: []compiler.HybridEvent{ + { + Name: "LargeEvent", + Parameters: eParams, + }, + }, + }) + e.DeployContract(t, c, nil) + + var args = make([]stackitem.Item, eArgsCount) + for i := range args { + args[i] = stackitem.Make("") + } + cInv := e.NewInvoker(c.Hash, acc) + expected := state.NotificationEvent{ + ScriptHash: c.Hash, + Name: "LargeEvent", + Item: stackitem.NewArray(args), + } + + // ProduceNumerousNotifications: 1 iteration, no limits are hit. + h := cInv.Invoke(t, stackitem.Null{}, "produceNumerousNotifications", 1) + cInv.CheckTxNotificationEvent(t, h, 0, expected) + + // ProduceNumerousNotifications: vm.MaxStackSize + 1 iterations. + count := vm.MaxStackSize + 1 + h = cInv.Invoke(t, stackitem.Null{}, "produceNumerousNotifications", count) + aer, err := e.Chain.GetAppExecResults(h, trigger.Application) + require.NoError(t, err) + require.Equal(t, count, len(aer[0].Events)) + for i := range count { + require.Equal(t, expected, aer[0].Events[i]) + } +}