neoneo-go/pkg/core/interop/contract/call.go
Evgenii Stratonikov 1c0c331e25 core: update System.Contract.Call syscall
1. Remove `System.Contract.CallEx`.
2. Extend number of parameters.
3. Add return value count to `VM.Context`.
2021-01-14 18:23:36 +03:00

126 lines
3.7 KiB
Go

package contract
import (
"errors"
"fmt"
"strings"
"github.com/nspcc-dev/neo-go/pkg/core/interop"
"github.com/nspcc-dev/neo-go/pkg/core/state"
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
"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"
)
// Call calls a contract with flags.
func Call(ic *interop.Context) error {
h := ic.VM.Estack().Pop().Bytes()
method := ic.VM.Estack().Pop().String()
fs := callflag.CallFlag(int32(ic.VM.Estack().Pop().BigInt().Int64()))
if fs&^callflag.All != 0 {
return errors.New("call flags out of range")
}
args := ic.VM.Estack().Pop().Array()
return callInternal(ic, h, method, fs, args)
}
func callInternal(ic *interop.Context, h []byte, name string, f callflag.CallFlag, args []stackitem.Item) error {
u, err := util.Uint160DecodeBytesBE(h)
if err != nil {
return errors.New("invalid contract hash")
}
cs, err := ic.GetContract(u)
if err != nil {
return fmt.Errorf("contract not found: %w", err)
}
if strings.HasPrefix(name, "_") {
return errors.New("invalid method name (starts with '_')")
}
md := cs.Manifest.ABI.GetMethod(name)
if md == nil {
return errors.New("method not found")
}
hasReturn := md.ReturnType != smartcontract.VoidType
if !hasReturn {
ic.VM.Estack().PushVal(stackitem.Null{})
}
if md.Safe {
f &^= callflag.WriteStates
} else if ctx := ic.VM.Context(); ctx != nil && ctx.IsDeployed() {
curr, err := ic.GetContract(ic.VM.GetCurrentScriptHash())
if err == nil {
if !curr.Manifest.CanCall(u, &cs.Manifest, name) {
return errors.New("disallowed method call")
}
}
}
return callExFromNative(ic, ic.VM.GetCurrentScriptHash(), cs, name, args, f, hasReturn)
}
// callExFromNative calls a contract with flags using provided calling hash.
func callExFromNative(ic *interop.Context, caller util.Uint160, cs *state.Contract,
name string, args []stackitem.Item, f callflag.CallFlag, hasReturn bool) error {
md := cs.Manifest.ABI.GetMethod(name)
if md == nil {
return fmt.Errorf("method '%s' not found", name)
}
if len(args) != len(md.Parameters) {
return fmt.Errorf("invalid argument count: %d (expected %d)", len(args), len(md.Parameters))
}
ic.VM.Invocations[cs.Hash]++
ic.VM.LoadScriptWithCallingHash(caller, cs.NEF.Script, cs.Hash, ic.VM.Context().GetCallFlags()&f, true, uint16(len(args)))
var isNative bool
for i := range ic.Natives {
if ic.Natives[i].Metadata().Hash.Equals(cs.Hash) {
isNative = true
break
}
}
for i := len(args) - 1; i >= 0; i-- {
ic.VM.Estack().PushVal(args[i])
}
if isNative {
ic.VM.Estack().PushVal(name)
} else {
// use Jump not Call here because context was loaded in LoadScript above.
ic.VM.Jump(ic.VM.Context(), md.Offset)
}
if hasReturn {
ic.VM.Context().RetCount = 1
} else {
ic.VM.Context().RetCount = 0
}
md = cs.Manifest.ABI.GetMethod(manifest.MethodInit)
if md != nil {
ic.VM.Call(ic.VM.Context(), md.Offset)
}
return nil
}
// ErrNativeCall is returned for failed calls from native.
var ErrNativeCall = errors.New("error during call from native")
// CallFromNative performs synchronous call from native contract.
func CallFromNative(ic *interop.Context, caller util.Uint160, cs *state.Contract, method string, args []stackitem.Item, hasReturn bool) error {
startSize := ic.VM.Istack().Len()
if err := callExFromNative(ic, caller, cs, method, args, callflag.All, hasReturn); err != nil {
return err
}
for !ic.VM.HasStopped() && ic.VM.Istack().Len() > startSize {
if err := ic.VM.Step(); err != nil {
return fmt.Errorf("%w: %v", ErrNativeCall, err)
}
}
if ic.VM.State() == vm.FaultState {
return ErrNativeCall
}
return nil
}