contract/vm: only push NULL after call in dynamic contexts
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...
This commit is contained in:
parent
99e2681d3a
commit
e8d2277fe5
3 changed files with 20 additions and 9 deletions
|
@ -14,6 +14,7 @@ import (
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
"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/smartcontract/manifest"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
"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"
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -67,11 +68,11 @@ func Call(ic *interop.Context) error {
|
||||||
return fmt.Errorf("method not found: %s/%d", method, len(args))
|
return fmt.Errorf("method not found: %s/%d", method, len(args))
|
||||||
}
|
}
|
||||||
hasReturn := md.ReturnType != smartcontract.VoidType
|
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,
|
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))
|
md := cs.Manifest.ABI.GetMethod(name, len(args))
|
||||||
if md.Safe {
|
if md.Safe {
|
||||||
f &^= (callflag.WriteStates | callflag.AllowNotify)
|
f &^= (callflag.WriteStates | callflag.AllowNotify)
|
||||||
|
@ -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.
|
// callExFromNative calls a contract with flags using the provided calling hash.
|
||||||
func callExFromNative(ic *interop.Context, caller util.Uint160, cs *state.Contract,
|
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 {
|
for _, nc := range ic.Natives {
|
||||||
if nc.Metadata().Name == nativenames.Policy {
|
if nc.Metadata().Name == nativenames.Policy {
|
||||||
var pch = nc.(policyChecker)
|
var pch = nc.(policyChecker)
|
||||||
|
@ -123,7 +124,7 @@ func callExFromNative(ic *interop.Context, caller util.Uint160, cs *state.Contra
|
||||||
if wrapped {
|
if wrapped {
|
||||||
ic.DAO = ic.DAO.GetPrivate()
|
ic.DAO = ic.DAO.GetPrivate()
|
||||||
}
|
}
|
||||||
onUnload := func(commit bool) error {
|
onUnload := func(ctx *vm.Context, commit bool) error {
|
||||||
if wrapped {
|
if wrapped {
|
||||||
if commit {
|
if commit {
|
||||||
_, err := ic.DAO.Persist()
|
_, err := ic.DAO.Persist()
|
||||||
|
@ -135,8 +136,13 @@ func callExFromNative(ic *interop.Context, caller util.Uint160, cs *state.Contra
|
||||||
}
|
}
|
||||||
ic.DAO = baseDAO
|
ic.DAO = baseDAO
|
||||||
}
|
}
|
||||||
if pushNullOnUnloading && commit {
|
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.
|
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 {
|
if callFromNative && !commit {
|
||||||
return fmt.Errorf("unhandled exception")
|
return fmt.Errorf("unhandled exception")
|
||||||
|
|
|
@ -73,7 +73,7 @@ type Context struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ContextUnloadCallback is a callback method used on context unloading from istack.
|
// 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")
|
var errNoInstParam = errors.New("failed to read instruction parameter")
|
||||||
|
|
||||||
|
@ -239,6 +239,11 @@ func (c *Context) GetNEF() *nef.File {
|
||||||
return c.sc.NEF
|
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.
|
// Value implements the stackitem.Item interface.
|
||||||
func (c *Context) Value() interface{} {
|
func (c *Context) Value() interface{} {
|
||||||
return c
|
return c
|
||||||
|
|
|
@ -1634,7 +1634,7 @@ func (v *VM) unloadContext(ctx *Context) {
|
||||||
ctx.sc.static.ClearRefs(&v.refs)
|
ctx.sc.static.ClearRefs(&v.refs)
|
||||||
}
|
}
|
||||||
if ctx.sc.onUnload != nil {
|
if ctx.sc.onUnload != nil {
|
||||||
err := ctx.sc.onUnload(v.uncaughtException == nil)
|
err := ctx.sc.onUnload(ctx, v.uncaughtException == nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errMessage := fmt.Sprintf("context unload callback failed: %s", err)
|
errMessage := fmt.Sprintf("context unload callback failed: %s", err)
|
||||||
if v.uncaughtException != nil {
|
if v.uncaughtException != nil {
|
||||||
|
|
Loading…
Reference in a new issue