2020-09-21 14:00:33 +00:00
|
|
|
package contract
|
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/nspcc-dev/neo-go/pkg/core/interop"
|
2020-10-02 09:56:51 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
2020-09-21 14:00:33 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
|
|
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
|
|
|
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
2020-10-12 11:32:27 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/vm"
|
2020-09-21 14:00:33 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Call calls a contract.
|
|
|
|
func Call(ic *interop.Context) error {
|
|
|
|
h := ic.VM.Estack().Pop().Bytes()
|
|
|
|
method := ic.VM.Estack().Pop().String()
|
|
|
|
args := ic.VM.Estack().Pop().Array()
|
|
|
|
return callExInternal(ic, h, method, args, smartcontract.All)
|
|
|
|
}
|
|
|
|
|
|
|
|
// CallEx calls a contract with flags.
|
|
|
|
func CallEx(ic *interop.Context) error {
|
|
|
|
h := ic.VM.Estack().Pop().Bytes()
|
|
|
|
method := ic.VM.Estack().Pop().String()
|
|
|
|
args := ic.VM.Estack().Pop().Array()
|
|
|
|
flags := smartcontract.CallFlag(int32(ic.VM.Estack().Pop().BigInt().Int64()))
|
|
|
|
if flags&^smartcontract.All != 0 {
|
|
|
|
return errors.New("call flags out of range")
|
|
|
|
}
|
|
|
|
return callExInternal(ic, h, method, args, flags)
|
|
|
|
}
|
|
|
|
|
|
|
|
func callExInternal(ic *interop.Context, h []byte, name string, args []stackitem.Item, f smartcontract.CallFlag) error {
|
|
|
|
u, err := util.Uint160DecodeBytesBE(h)
|
|
|
|
if err != nil {
|
|
|
|
return errors.New("invalid contract hash")
|
|
|
|
}
|
|
|
|
cs, err := ic.DAO.GetContractState(u)
|
|
|
|
if err != nil {
|
|
|
|
return errors.New("contract not found")
|
|
|
|
}
|
|
|
|
if strings.HasPrefix(name, "_") {
|
|
|
|
return errors.New("invalid method name (starts with '_')")
|
|
|
|
}
|
2020-12-08 10:27:41 +00:00
|
|
|
md := cs.Manifest.ABI.GetMethod(name)
|
|
|
|
if md == nil {
|
|
|
|
return errors.New("method not found")
|
|
|
|
}
|
|
|
|
if md.Safe {
|
2020-12-11 07:34:01 +00:00
|
|
|
f &^= smartcontract.WriteStates
|
2020-12-08 10:27:41 +00:00
|
|
|
} else if ctx := ic.VM.Context(); ctx != nil && ctx.IsDeployed() {
|
2020-11-26 20:02:00 +00:00
|
|
|
curr, err := ic.DAO.GetContractState(ic.VM.GetCurrentScriptHash())
|
|
|
|
if err == nil {
|
|
|
|
if !curr.Manifest.CanCall(u, &cs.Manifest, name) {
|
|
|
|
return errors.New("disallowed method call")
|
|
|
|
}
|
2020-09-21 14:00:33 +00:00
|
|
|
}
|
|
|
|
}
|
2020-12-09 12:16:49 +00:00
|
|
|
return CallExInternal(ic, cs, name, args, f, vm.EnsureNotEmpty)
|
2020-10-02 09:56:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// CallExInternal calls a contract with flags and can't be invoked directly by user.
|
|
|
|
func CallExInternal(ic *interop.Context, cs *state.Contract,
|
2020-12-09 12:16:49 +00:00
|
|
|
name string, args []stackitem.Item, f smartcontract.CallFlag, checkReturn vm.CheckReturnState) error {
|
|
|
|
return callExFromNative(ic, ic.VM.GetCurrentScriptHash(), cs, name, args, f, checkReturn)
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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 smartcontract.CallFlag, checkReturn vm.CheckReturnState) error {
|
2020-10-02 09:56:51 +00:00
|
|
|
md := cs.Manifest.ABI.GetMethod(name)
|
|
|
|
if md == nil {
|
|
|
|
return fmt.Errorf("method '%s' not found", name)
|
|
|
|
}
|
2020-09-21 14:00:33 +00:00
|
|
|
|
|
|
|
if len(args) != len(md.Parameters) {
|
|
|
|
return fmt.Errorf("invalid argument count: %d (expected %d)", len(args), len(md.Parameters))
|
|
|
|
}
|
|
|
|
|
2020-11-18 20:10:48 +00:00
|
|
|
ic.VM.Invocations[cs.Hash]++
|
2020-12-09 12:16:49 +00:00
|
|
|
ic.VM.LoadScriptWithCallingHash(caller, cs.Script, cs.Hash, ic.VM.Context().GetCallFlags()&f)
|
2020-09-21 14:00:33 +00:00
|
|
|
var isNative bool
|
|
|
|
for i := range ic.Natives {
|
2020-11-18 20:10:48 +00:00
|
|
|
if ic.Natives[i].Metadata().Hash.Equals(cs.Hash) {
|
2020-09-21 14:00:33 +00:00
|
|
|
isNative = true
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if isNative {
|
|
|
|
ic.VM.Estack().PushVal(args)
|
|
|
|
ic.VM.Estack().PushVal(name)
|
|
|
|
} else {
|
|
|
|
for i := len(args) - 1; i >= 0; i-- {
|
|
|
|
ic.VM.Estack().PushVal(args[i])
|
|
|
|
}
|
|
|
|
// use Jump not Call here because context was loaded in LoadScript above.
|
|
|
|
ic.VM.Jump(ic.VM.Context(), md.Offset)
|
|
|
|
}
|
2020-10-12 11:32:27 +00:00
|
|
|
ic.VM.Context().CheckReturn = checkReturn
|
2020-09-21 14:00:33 +00:00
|
|
|
|
|
|
|
md = cs.Manifest.ABI.GetMethod(manifest.MethodInit)
|
|
|
|
if md != nil {
|
|
|
|
ic.VM.Call(ic.VM.Context(), md.Offset)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
2020-12-09 12:16:49 +00:00
|
|
|
|
|
|
|
// 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, checkReturn vm.CheckReturnState) error {
|
|
|
|
startSize := ic.VM.Istack().Len()
|
|
|
|
if err := callExFromNative(ic, caller, cs, method, args, smartcontract.All, checkReturn); 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
|
|
|
|
}
|