Skip to content

Commit

Permalink
contract/vm: only push NULL after call in dynamic contexts
Browse files Browse the repository at this point in the history
And determine the need for Null dynamically. For some reason the only dynamic
context is Contract.Call. CALLT is not dynamic and neither is a call from
native contract, go figure...
  • Loading branch information
roman-khimov committed Aug 5, 2022
1 parent 99e2681 commit e8d2277
Show file tree
Hide file tree
Showing 3 changed files with 20 additions and 9 deletions.
20 changes: 13 additions & 7 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 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
7 changes: 6 additions & 1 deletion pkg/vm/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ type Context struct {
}

// ContextUnloadCallback is a callback method used on context unloading from istack.
type ContextUnloadCallback func(commit bool) error
type ContextUnloadCallback func(ctx *Context, commit bool) error

var errNoInstParam = errors.New("failed to read instruction parameter")

Expand Down Expand Up @@ -239,6 +239,11 @@ func (c *Context) GetNEF() *nef.File {
return c.sc.NEF
}

// NumOfReturnVals returns the number of return values expected from this context.
func (c *Context) NumOfReturnVals() int {
return c.retCount
}

// Value implements the stackitem.Item interface.
func (c *Context) Value() interface{} {
return c
Expand Down
2 changes: 1 addition & 1 deletion pkg/vm/vm.go
Original file line number Diff line number Diff line change
Expand Up @@ -1634,7 +1634,7 @@ func (v *VM) unloadContext(ctx *Context) {
ctx.sc.static.ClearRefs(&v.refs)
}
if ctx.sc.onUnload != nil {
err := ctx.sc.onUnload(v.uncaughtException == nil)
err := ctx.sc.onUnload(ctx, v.uncaughtException == nil)
if err != nil {
errMessage := fmt.Sprintf("context unload callback failed: %s", err)
if v.uncaughtException != nil {
Expand Down

0 comments on commit e8d2277

Please sign in to comment.