diff --git a/pkg/core/interop/context.go b/pkg/core/interop/context.go index 61ab10961..7528f61f6 100644 --- a/pkg/core/interop/context.go +++ b/pkg/core/interop/context.go @@ -35,7 +35,6 @@ type Context struct { DAO *dao.Cached Notifications []state.NotificationEvent Log *zap.Logger - Invocations map[util.Uint160]int VM *vm.VM Functions [][]Function } @@ -53,7 +52,6 @@ func NewContext(trigger trigger.Type, bc blockchainer.Blockchainer, d dao.DAO, n DAO: dao, Notifications: nes, Log: log, - Invocations: make(map[util.Uint160]int), // Functions is a slice of slices of interops sorted by ID. Functions: [][]Function{}, } diff --git a/pkg/core/interop/contract/call.go b/pkg/core/interop/contract/call.go index cb0b4a333..e27a54894 100644 --- a/pkg/core/interop/contract/call.go +++ b/pkg/core/interop/contract/call.go @@ -67,7 +67,7 @@ func CallExInternal(ic *interop.Context, cs *state.Contract, } u := cs.ScriptHash() - ic.Invocations[u]++ + ic.VM.Invocations[u]++ ic.VM.LoadScriptWithHash(cs.Script, u, ic.VM.Context().GetCallFlags()&f) var isNative bool for i := range ic.Natives { diff --git a/pkg/core/interop/runtime/util.go b/pkg/core/interop/runtime/util.go index f9037887f..de17a0b59 100644 --- a/pkg/core/interop/runtime/util.go +++ b/pkg/core/interop/runtime/util.go @@ -58,9 +58,11 @@ func GetNotifications(ic *interop.Context) error { // GetInvocationCounter returns how many times current contract was invoked during current tx execution. func GetInvocationCounter(ic *interop.Context) error { - count, ok := ic.Invocations[ic.VM.GetCurrentScriptHash()] + currentScriptHash := ic.VM.GetCurrentScriptHash() + count, ok := ic.VM.Invocations[currentScriptHash] if !ok { - return errors.New("current contract wasn't invoked from others") + count = 1 + ic.VM.Invocations[currentScriptHash] = count } ic.VM.Estack().PushVal(count) return nil diff --git a/pkg/core/interop_system_test.go b/pkg/core/interop_system_test.go index d947fb08e..7abdfcbd7 100644 --- a/pkg/core/interop_system_test.go +++ b/pkg/core/interop_system_test.go @@ -296,11 +296,13 @@ func TestRuntimeGetInvocationCounter(t *testing.T) { v, ic, chain := createVM(t) defer chain.Close() - ic.Invocations[hash.Hash160([]byte{2})] = 42 + ic.VM.Invocations[hash.Hash160([]byte{2})] = 42 - t.Run("Zero", func(t *testing.T) { + t.Run("No invocations", func(t *testing.T) { v.LoadScript([]byte{1}) - require.Error(t, runtime.GetInvocationCounter(ic)) + // do not return an error in this case. + require.NoError(t, runtime.GetInvocationCounter(ic)) + require.EqualValues(t, 1, v.Estack().Pop().BigInt().Int64()) }) t.Run("NonZero", func(t *testing.T) { v.LoadScript([]byte{2}) diff --git a/pkg/vm/vm.go b/pkg/vm/vm.go index ffc9332e6..0a18f3b59 100644 --- a/pkg/vm/vm.go +++ b/pkg/vm/vm.go @@ -80,6 +80,9 @@ type VM struct { SyscallHandler func(v *VM, id uint32) error trigger trigger.Type + + // Invocations is a script invocation counter. + Invocations map[util.Uint160]int } // New returns a new VM object ready to load AVM bytecode scripts. @@ -96,6 +99,7 @@ func NewWithTrigger(t trigger.Type) *VM { trigger: t, SyscallHandler: defaultSyscallHandler, + Invocations: make(map[util.Uint160]int), } vm.estack = vm.newItemStack("evaluation") @@ -1225,7 +1229,7 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro v.checkInvocationStackSize() // Note: jump offset must be calculated regarding to new context, // but it is cloned and thus has the same script and instruction pointer. - v.Call(ctx, v.getJumpOffset(ctx, parameter)) + v.call(ctx, v.getJumpOffset(ctx, parameter)) case opcode.CALLA: ptr := v.estack.Pop().Item().(*stackitem.Pointer) @@ -1233,7 +1237,7 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro panic("invalid script in pointer") } - v.Call(ctx, ptr.Position()) + v.call(ctx, ptr.Position()) case opcode.SYSCALL: interopID := GetInteropID(parameter) @@ -1455,8 +1459,17 @@ func (v *VM) Jump(ctx *Context, offset int) { } // Call calls method by offset. It is similar to Jump but also -// pushes new context to the invocation state +// pushes new context to the invocation stack and increments +// invocation counter for the corresponding context script hash. func (v *VM) Call(ctx *Context, offset int) { + v.call(ctx, offset) + v.Invocations[ctx.ScriptHash()]++ +} + +// call is an internal representation of Call, which does not +// affect the invocation counter and is only being used by vm +// package. +func (v *VM) call(ctx *Context, offset int) { newCtx := ctx.Copy() newCtx.CheckReturn = false newCtx.local = nil