Skip to content

Commit

Permalink
Merge pull request #2613 from nspcc-dev/exec-changes-3.4.0
Browse files Browse the repository at this point in the history
Exec changes for 3.4.0
  • Loading branch information
roman-khimov committed Aug 5, 2022
2 parents ab7743d + e8d2277 commit b8a4a6d
Show file tree
Hide file tree
Showing 12 changed files with 170 additions and 124 deletions.
22 changes: 14 additions & 8 deletions pkg/core/interop/contract/call.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
)

Expand All @@ -27,7 +28,7 @@ func LoadToken(ic *interop.Context, id int32) error {
if !ctx.GetCallFlags().Has(callflag.ReadStates | callflag.AllowCall) {
return errors.New("invalid call flags")
}
tok := ctx.NEF.Tokens[id]
tok := ctx.GetNEF().Tokens[id]
if int(tok.ParamCount) > ctx.Estack().Len() {
return errors.New("stack is too small")
}
Expand Down Expand Up @@ -67,11 +68,11 @@ func Call(ic *interop.Context) error {
return fmt.Errorf("method not found: %s/%d", method, len(args))
}
hasReturn := md.ReturnType != smartcontract.VoidType
return callInternal(ic, cs, method, fs, hasReturn, args, !hasReturn)
return callInternal(ic, cs, method, fs, hasReturn, args, true)
}

func callInternal(ic *interop.Context, cs *state.Contract, name string, f callflag.CallFlag,
hasReturn bool, args []stackitem.Item, pushNullOnUnloading bool) error {
hasReturn bool, args []stackitem.Item, isDynamic bool) error {
md := cs.Manifest.ABI.GetMethod(name, len(args))
if md.Safe {
f &^= (callflag.WriteStates | callflag.AllowNotify)
Expand All @@ -83,12 +84,12 @@ func callInternal(ic *interop.Context, cs *state.Contract, name string, f callfl
}
}
}
return callExFromNative(ic, ic.VM.GetCurrentScriptHash(), cs, name, args, f, hasReturn, pushNullOnUnloading, false)
return callExFromNative(ic, ic.VM.GetCurrentScriptHash(), cs, name, args, f, hasReturn, isDynamic, false)
}

// callExFromNative calls a contract with flags using the provided calling hash.
func callExFromNative(ic *interop.Context, caller util.Uint160, cs *state.Contract,
name string, args []stackitem.Item, f callflag.CallFlag, hasReturn bool, pushNullOnUnloading bool, callFromNative bool) error {
name string, args []stackitem.Item, f callflag.CallFlag, hasReturn bool, isDynamic bool, callFromNative bool) error {
for _, nc := range ic.Natives {
if nc.Metadata().Name == nativenames.Policy {
var pch = nc.(policyChecker)
Expand Down Expand Up @@ -123,7 +124,7 @@ func callExFromNative(ic *interop.Context, caller util.Uint160, cs *state.Contra
if wrapped {
ic.DAO = ic.DAO.GetPrivate()
}
onUnload := func(commit bool) error {
onUnload := func(ctx *vm.Context, commit bool) error {
if wrapped {
if commit {
_, err := ic.DAO.Persist()
Expand All @@ -135,8 +136,13 @@ func callExFromNative(ic *interop.Context, caller util.Uint160, cs *state.Contra
}
ic.DAO = baseDAO
}
if pushNullOnUnloading && commit {
ic.VM.Context().Estack().PushItem(stackitem.Null{}) // Must use current context stack.
if isDynamic && commit {
eLen := ctx.Estack().Len()
if eLen == 0 && ctx.NumOfReturnVals() == 0 { // No return value and none expected.
ic.VM.Context().Estack().PushItem(stackitem.Null{}) // Must use current context stack.
} else if eLen > 1 { // 1 or -1 (all) retrun values expected, but only one can be returned.
return errors.New("multiple return values in a cross-contract call")
} // All other rvcount/stack length mismatches are checked by the VM.
}
if callFromNative && !commit {
return fmt.Errorf("unhandled exception")
Expand Down
4 changes: 4 additions & 0 deletions pkg/core/interop/runtime/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,10 @@ func Notify(ic *interop.Context) error {
if len(name) > MaxEventNameLen {
return fmt.Errorf("event name must be less than %d", MaxEventNameLen)
}
if !ic.VM.Context().IsDeployed() {
return errors.New("notifications are not allowed in dynamic scripts")
}

// But it has to be serializable, otherwise we either have some broken
// (recursive) structure inside or an interop item that can't be used
// outside of the interop subsystem anyway.
Expand Down
13 changes: 12 additions & 1 deletion pkg/core/interop/runtime/engine_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/nspcc-dev/neo-go/pkg/core/interop"
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/nef"
"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"
Expand Down Expand Up @@ -133,9 +134,12 @@ func TestLog(t *testing.T) {

func TestNotify(t *testing.T) {
h := random.Uint160()
caller := random.Uint160()
exe, err := nef.NewFile([]byte{1})
require.NoError(t, err)
newIC := func(name string, args interface{}) *interop.Context {
ic := &interop.Context{VM: vm.New(), DAO: &dao.Simple{}}
ic.VM.LoadScriptWithHash([]byte{1}, h, callflag.NoneFlag)
ic.VM.LoadNEFMethod(exe, caller, h, callflag.NoneFlag, true, 0, -1, nil)
ic.VM.Estack().PushVal(args)
ic.VM.Estack().PushVal(name)
return ic
Expand All @@ -144,6 +148,13 @@ func TestNotify(t *testing.T) {
ic := newIC(string(make([]byte, MaxEventNameLen+1)), stackitem.NewArray([]stackitem.Item{stackitem.Null{}}))
require.Error(t, Notify(ic))
})
t.Run("dynamic script", func(t *testing.T) {
ic := &interop.Context{VM: vm.New(), DAO: &dao.Simple{}}
ic.VM.LoadScriptWithHash([]byte{1}, h, callflag.NoneFlag)
ic.VM.Estack().PushVal(stackitem.NewArray([]stackitem.Item{stackitem.Make(42)}))
ic.VM.Estack().PushVal("event")
require.Error(t, Notify(ic))
})
t.Run("recursive struct", func(t *testing.T) {
arr := stackitem.NewArray([]stackitem.Item{stackitem.Null{}})
arr.Append(arr)
Expand Down
8 changes: 5 additions & 3 deletions pkg/core/interop/runtime/witness.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ func getContractGroups(v *vm.VM, ic *interop.Context, h util.Uint160) (manifest.
return manifest.Groups(cs.Manifest.Groups), nil
}

func (sc scopeContext) IsCalledByEntry() bool {
return sc.VM.Context().IsCalledByEntry()
}

func (sc scopeContext) checkScriptGroups(h util.Uint160, k *keys.PublicKey) (bool, error) {
groups, err := getContractGroups(sc.VM, sc.ic, h)
if err != nil {
Expand Down Expand Up @@ -69,9 +73,7 @@ func checkScope(ic *interop.Context, hash util.Uint160) (bool, error) {
return true, nil
}
if c.Scopes&transaction.CalledByEntry != 0 {
callingScriptHash := ic.VM.GetCallingScriptHash()
entryScriptHash := ic.VM.GetEntryScriptHash()
if callingScriptHash.Equals(util.Uint160{}) || callingScriptHash == entryScriptHash {
if ic.VM.Context().IsCalledByEntry() {
return true, nil
}
}
Expand Down
5 changes: 2 additions & 3 deletions pkg/core/transaction/witness_condition.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,9 @@ type WitnessCondition interface {
type MatchContext interface {
GetCallingScriptHash() util.Uint160
GetCurrentScriptHash() util.Uint160
GetEntryScriptHash() util.Uint160
CallingScriptHasGroup(*keys.PublicKey) (bool, error)
CurrentScriptHasGroup(*keys.PublicKey) (bool, error)
IsCalledByEntry() bool
}

type (
Expand Down Expand Up @@ -394,8 +394,7 @@ func (c ConditionCalledByEntry) Type() WitnessConditionType {
// Match implements the WitnessCondition interface checking whether this condition
// matches given context.
func (c ConditionCalledByEntry) Match(ctx MatchContext) (bool, error) {
entry := ctx.GetEntryScriptHash()
return entry.Equals(ctx.GetCallingScriptHash()) || entry.Equals(ctx.GetCurrentScriptHash()), nil
return ctx.IsCalledByEntry(), nil
}

// EncodeBinary implements the WitnessCondition interface allowing to serialize condition.
Expand Down
3 changes: 3 additions & 0 deletions pkg/core/transaction/witness_condition_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,9 @@ func (t *TestMC) GetCurrentScriptHash() util.Uint160 {
func (t *TestMC) GetEntryScriptHash() util.Uint160 {
return t.entry
}
func (t *TestMC) IsCalledByEntry() bool {
return t.entry.Equals(t.calling) || t.calling.Equals(util.Uint160{})
}
func (t *TestMC) CallingScriptHasGroup(k *keys.PublicKey) (bool, error) {
res, err := t.CurrentScriptHasGroup(k)
return !res, err // To differentiate from current we invert the logic value.
Expand Down
2 changes: 1 addition & 1 deletion pkg/core/transaction/witness_scope.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ type WitnessScope byte
const (
// None specifies that no contract was witnessed. Only sign the transaction.
None WitnessScope = 0
// CalledByEntry means that this condition must hold: EntryScriptHash == CallingScriptHash.
// CalledByEntry witness is only valid in entry script and ones directly called by it.
// No params is needed, as the witness/permission/signature given on first invocation will
// automatically expire if entering deeper internal invokes. This can be default safe
// choice for native NEO/GAS (previously used on Neo 2 as "attach" mode).
Expand Down
Loading

0 comments on commit b8a4a6d

Please sign in to comment.