diff --git a/pkg/compiler/syscall.go b/pkg/compiler/syscall.go index e6da7eb70..e93d2f484 100644 --- a/pkg/compiler/syscall.go +++ b/pkg/compiler/syscall.go @@ -29,6 +29,7 @@ var syscalls = map[string]map[string]string{ "GetEntryScriptHash": "System.Runtime.GetEntryScriptHash", "GetExecutingScriptHash": "System.Runtime.GetExecutingScriptHash", "GetNotifications": "System.Runtime.GetNotifications", + "GetInvocationCounter": "System.Runtime.GetInvocationCounter", "GasLeft": "System.Runtime.GasLeft", "GetTrigger": "System.Runtime.GetTrigger", diff --git a/pkg/core/interop/context.go b/pkg/core/interop/context.go index 10d2c376e..b0b59c586 100644 --- a/pkg/core/interop/context.go +++ b/pkg/core/interop/context.go @@ -30,6 +30,7 @@ type Context struct { DAO *dao.Cached Notifications []state.NotificationEvent Log *zap.Logger + Invocations map[util.Uint160]int } // NewContext returns new interop context. @@ -45,6 +46,7 @@ func NewContext(trigger trigger.Type, bc blockchainer.Blockchainer, d dao.DAO, n DAO: dao, Notifications: nes, Log: log, + Invocations: make(map[util.Uint160]int), } } diff --git a/pkg/core/interop/runtime/util.go b/pkg/core/interop/runtime/util.go index 48cab46c0..e9a122507 100644 --- a/pkg/core/interop/runtime/util.go +++ b/pkg/core/interop/runtime/util.go @@ -49,3 +49,13 @@ func GetNotifications(ic *interop.Context, v *vm.VM) error { v.Estack().PushVal(arr) return nil } + +// GetInvocationCounter returns how many times current contract was invoked during current tx execution. +func GetInvocationCounter(ic *interop.Context, v *vm.VM) error { + count, ok := ic.Invocations[v.GetCurrentScriptHash()] + if !ok { + return errors.New("current contract wasn't invoked from others") + } + v.Estack().PushVal(count) + return nil +} diff --git a/pkg/core/interop_system.go b/pkg/core/interop_system.go index b4c6ed2c7..c0a66ed06 100644 --- a/pkg/core/interop_system.go +++ b/pkg/core/interop_system.go @@ -443,6 +443,7 @@ func contractCallExInternal(ic *interop.Context, v *vm.VM, h []byte, method stac return errors.New("disallowed method call") } } + ic.Invocations[u]++ v.LoadScriptWithHash(cs.Script, u, v.Context().GetCallFlags()&f) v.Estack().PushVal(args) v.Estack().PushVal(method) diff --git a/pkg/core/interop_system_test.go b/pkg/core/interop_system_test.go index bd87315e4..a47894946 100644 --- a/pkg/core/interop_system_test.go +++ b/pkg/core/interop_system_test.go @@ -7,6 +7,7 @@ import ( "github.com/nspcc-dev/dbft/crypto" "github.com/nspcc-dev/neo-go/pkg/core/interop/runtime" "github.com/nspcc-dev/neo-go/pkg/core/state" + "github.com/nspcc-dev/neo-go/pkg/crypto/hash" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/vm/opcode" @@ -244,3 +245,20 @@ func TestRuntimeGetNotifications(t *testing.T) { require.Equal(t, ic.Notifications[1].Item, elem[1]) }) } + +func TestRuntimeGetInvocationCounter(t *testing.T) { + v, ic, chain := createVM(t) + defer chain.Close() + + ic.Invocations[hash.Hash160([]byte{2})] = 42 + + t.Run("Zero", func(t *testing.T) { + v.LoadScript([]byte{1}) + require.Error(t, runtime.GetInvocationCounter(ic, v)) + }) + t.Run("NonZero", func(t *testing.T) { + v.LoadScript([]byte{2}) + require.NoError(t, runtime.GetInvocationCounter(ic, v)) + require.EqualValues(t, 42, v.Estack().Pop().BigInt().Int64()) + }) +} diff --git a/pkg/core/interops.go b/pkg/core/interops.go index 3ad59985a..7f8629061 100644 --- a/pkg/core/interops.go +++ b/pkg/core/interops.go @@ -111,6 +111,7 @@ var systemInterops = []interop.Function{ {Name: "System.Runtime.GetCallingScriptHash", Func: engineGetCallingScriptHash, Price: 400}, {Name: "System.Runtime.GetEntryScriptHash", Func: engineGetEntryScriptHash, Price: 400}, {Name: "System.Runtime.GetExecutingScriptHash", Func: engineGetExecutingScriptHash, Price: 400}, + {Name: "System.Runtime.GetInvocationCounter", Func: runtime.GetInvocationCounter, Price: 400}, {Name: "System.Runtime.GetScriptContainer", Func: engineGetScriptContainer, Price: 250}, {Name: "System.Runtime.GetTime", Func: runtimeGetTime, Price: 250, AllowedTriggers: trigger.Application}, diff --git a/pkg/interop/runtime/runtime.go b/pkg/interop/runtime/runtime.go index 0306e6743..5695fa780 100644 --- a/pkg/interop/runtime/runtime.go +++ b/pkg/interop/runtime/runtime.go @@ -68,3 +68,9 @@ func GasLeft() int64 { func GetNotifications(h []byte) [][]interface{} { return nil } + +// GetInvocationCounter returns how many times current contract was invoked during current tx execution. +// This function uses `System.Runtime.GetInvocationCounter` syscall. +func GetInvocationCounter() int { + return 0 +}