core: move contractCall* to a separate package

This commit is contained in:
Evgenii Stratonikov 2020-09-21 17:00:33 +03:00
parent 4b0008708b
commit 877b8ece63
5 changed files with 103 additions and 90 deletions

View file

@ -0,0 +1,89 @@
package contract
import (
"errors"
"fmt"
"strings"
"github.com/nspcc-dev/neo-go/pkg/core/interop"
"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"
"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 '_')")
}
md := cs.Manifest.ABI.GetMethod(name)
if md == nil {
return fmt.Errorf("method '%s' not found", name)
}
curr, err := ic.DAO.GetContractState(ic.VM.GetCurrentScriptHash())
if err == nil {
if !curr.Manifest.CanCall(&cs.Manifest, name) {
return errors.New("disallowed method call")
}
}
if len(args) != len(md.Parameters) {
return fmt.Errorf("invalid argument count: %d (expected %d)", len(args), len(md.Parameters))
}
ic.Invocations[u]++
ic.VM.LoadScriptWithHash(cs.Script, u, ic.VM.Context().GetCallFlags()&f)
var isNative bool
for i := range ic.Natives {
if ic.Natives[i].Metadata().Hash.Equals(u) {
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)
ic.VM.Context().CheckReturn = true
}
md = cs.Manifest.ABI.GetMethod(manifest.MethodInit)
if md != nil {
ic.VM.Call(ic.VM.Context(), md.Offset)
}
return nil
}

View file

@ -6,7 +6,6 @@ import (
"fmt" "fmt"
"math" "math"
"math/big" "math/big"
"strings"
"github.com/nspcc-dev/neo-go/pkg/core/block" "github.com/nspcc-dev/neo-go/pkg/core/block"
"github.com/nspcc-dev/neo-go/pkg/core/blockchainer" "github.com/nspcc-dev/neo-go/pkg/core/blockchainer"
@ -15,8 +14,6 @@ import (
"github.com/nspcc-dev/neo-go/pkg/core/state" "github.com/nspcc-dev/neo-go/pkg/core/state"
"github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"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" "github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm" "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"
@ -454,82 +451,6 @@ func storageContextAsReadOnly(ic *interop.Context) error {
return nil return nil
} }
// contractCall calls a contract.
func contractCall(ic *interop.Context) error {
h := ic.VM.Estack().Pop().Bytes()
method := ic.VM.Estack().Pop().String()
args := ic.VM.Estack().Pop().Array()
return contractCallExInternal(ic, h, method, args, smartcontract.All)
}
// contractCallEx calls a contract with flags.
func contractCallEx(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 contractCallExInternal(ic, h, method, args, flags)
}
func contractCallExInternal(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 '_')")
}
md := cs.Manifest.ABI.GetMethod(name)
if md == nil {
return fmt.Errorf("method '%s' not found", name)
}
curr, err := ic.DAO.GetContractState(ic.VM.GetCurrentScriptHash())
if err == nil {
if !curr.Manifest.CanCall(&cs.Manifest, name) {
return errors.New("disallowed method call")
}
}
if len(args) != len(md.Parameters) {
return fmt.Errorf("invalid argument count: %d (expected %d)", len(args), len(md.Parameters))
}
ic.Invocations[u]++
ic.VM.LoadScriptWithHash(cs.Script, u, ic.VM.Context().GetCallFlags()&f)
var isNative bool
for i := range ic.Natives {
if ic.Natives[i].Metadata().Hash.Equals(u) {
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)
ic.VM.Context().CheckReturn = true
}
md = cs.Manifest.ABI.GetMethod(manifest.MethodInit)
if md != nil {
ic.VM.Call(ic.VM.Context(), md.Offset)
}
return nil
}
// contractDestroy destroys a contract. // contractDestroy destroys a contract.
func contractDestroy(ic *interop.Context) error { func contractDestroy(ic *interop.Context) error {
hash := ic.VM.GetCurrentScriptHash() hash := ic.VM.GetCurrentScriptHash()

View file

@ -8,6 +8,7 @@ import (
"github.com/nspcc-dev/neo-go/pkg/config/netmode" "github.com/nspcc-dev/neo-go/pkg/config/netmode"
"github.com/nspcc-dev/neo-go/pkg/core/interop" "github.com/nspcc-dev/neo-go/pkg/core/interop"
"github.com/nspcc-dev/neo-go/pkg/core/interop/callback" "github.com/nspcc-dev/neo-go/pkg/core/interop/callback"
"github.com/nspcc-dev/neo-go/pkg/core/interop/contract"
"github.com/nspcc-dev/neo-go/pkg/core/interop/runtime" "github.com/nspcc-dev/neo-go/pkg/core/interop/runtime"
"github.com/nspcc-dev/neo-go/pkg/core/state" "github.com/nspcc-dev/neo-go/pkg/core/state"
"github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/core/transaction"
@ -441,7 +442,7 @@ func TestContractCall(t *testing.T) {
ic.VM.Estack().PushVal(addArgs) ic.VM.Estack().PushVal(addArgs)
ic.VM.Estack().PushVal("add") ic.VM.Estack().PushVal("add")
ic.VM.Estack().PushVal(h.BytesBE()) ic.VM.Estack().PushVal(h.BytesBE())
require.NoError(t, contractCall(ic)) require.NoError(t, contract.Call(ic))
require.NoError(t, ic.VM.Run()) require.NoError(t, ic.VM.Run())
require.Equal(t, 2, ic.VM.Estack().Len()) require.Equal(t, 2, ic.VM.Estack().Len())
require.Equal(t, big.NewInt(3), ic.VM.Estack().Pop().Value()) require.Equal(t, big.NewInt(3), ic.VM.Estack().Pop().Value())
@ -454,7 +455,7 @@ func TestContractCall(t *testing.T) {
ic.VM.Estack().PushVal(addArgs) ic.VM.Estack().PushVal(addArgs)
ic.VM.Estack().PushVal("add") ic.VM.Estack().PushVal("add")
ic.VM.Estack().PushVal(h.BytesBE()) ic.VM.Estack().PushVal(h.BytesBE())
require.Error(t, contractCallEx(ic)) require.Error(t, contract.CallEx(ic))
}) })
runInvalid := func(args ...interface{}) func(t *testing.T) { runInvalid := func(args ...interface{}) func(t *testing.T) {
@ -466,7 +467,7 @@ func TestContractCall(t *testing.T) {
// interops can both return error and panic, // interops can both return error and panic,
// we don't care which kind of error has occurred // we don't care which kind of error has occurred
require.Panics(t, func() { require.Panics(t, func() {
err := contractCall(ic) err := contract.Call(ic)
if err != nil { if err != nil {
panic(err) panic(err)
} }
@ -491,7 +492,7 @@ func TestContractCall(t *testing.T) {
ic.VM.Estack().PushVal(stackitem.NewArray(nil)) ic.VM.Estack().PushVal(stackitem.NewArray(nil))
ic.VM.Estack().PushVal("invalidReturn") ic.VM.Estack().PushVal("invalidReturn")
ic.VM.Estack().PushVal(h.BytesBE()) ic.VM.Estack().PushVal(h.BytesBE())
require.NoError(t, contractCall(ic)) require.NoError(t, contract.Call(ic))
require.Error(t, ic.VM.Run()) require.Error(t, ic.VM.Run())
}) })
t.Run("Void", func(t *testing.T) { t.Run("Void", func(t *testing.T) {
@ -499,7 +500,7 @@ func TestContractCall(t *testing.T) {
ic.VM.Estack().PushVal(stackitem.NewArray(nil)) ic.VM.Estack().PushVal(stackitem.NewArray(nil))
ic.VM.Estack().PushVal("justReturn") ic.VM.Estack().PushVal("justReturn")
ic.VM.Estack().PushVal(h.BytesBE()) ic.VM.Estack().PushVal(h.BytesBE())
require.NoError(t, contractCall(ic)) require.NoError(t, contract.Call(ic))
require.NoError(t, ic.VM.Run()) require.NoError(t, ic.VM.Run())
require.Equal(t, 2, ic.VM.Estack().Len()) require.Equal(t, 2, ic.VM.Estack().Len())
require.Equal(t, stackitem.Null{}, ic.VM.Estack().Pop().Item()) require.Equal(t, stackitem.Null{}, ic.VM.Estack().Pop().Item())
@ -512,7 +513,7 @@ func TestContractCall(t *testing.T) {
ic.VM.Estack().PushVal(stackitem.NewArray(nil)) ic.VM.Estack().PushVal(stackitem.NewArray(nil))
ic.VM.Estack().PushVal("drop") ic.VM.Estack().PushVal("drop")
ic.VM.Estack().PushVal(h.BytesBE()) ic.VM.Estack().PushVal(h.BytesBE())
require.NoError(t, contractCall(ic)) require.NoError(t, contract.Call(ic))
require.Error(t, ic.VM.Run()) require.Error(t, ic.VM.Run())
}) })
@ -523,7 +524,7 @@ func TestContractCall(t *testing.T) {
ic.VM.Estack().PushVal(stackitem.NewArray([]stackitem.Item{stackitem.Make(5)})) ic.VM.Estack().PushVal(stackitem.NewArray([]stackitem.Item{stackitem.Make(5)}))
ic.VM.Estack().PushVal("add3") ic.VM.Estack().PushVal("add3")
ic.VM.Estack().PushVal(h.BytesBE()) ic.VM.Estack().PushVal(h.BytesBE())
require.NoError(t, contractCall(ic)) require.NoError(t, contract.Call(ic))
require.NoError(t, ic.VM.Run()) require.NoError(t, ic.VM.Run())
require.Equal(t, 2, ic.VM.Estack().Len()) require.Equal(t, 2, ic.VM.Estack().Len())
require.Equal(t, big.NewInt(8), ic.VM.Estack().Pop().Value()) require.Equal(t, big.NewInt(8), ic.VM.Estack().Pop().Value())

View file

@ -10,6 +10,7 @@ package core
import ( import (
"github.com/nspcc-dev/neo-go/pkg/core/interop" "github.com/nspcc-dev/neo-go/pkg/core/interop"
"github.com/nspcc-dev/neo-go/pkg/core/interop/callback" "github.com/nspcc-dev/neo-go/pkg/core/interop/callback"
"github.com/nspcc-dev/neo-go/pkg/core/interop/contract"
"github.com/nspcc-dev/neo-go/pkg/core/interop/crypto" "github.com/nspcc-dev/neo-go/pkg/core/interop/crypto"
"github.com/nspcc-dev/neo-go/pkg/core/interop/enumerator" "github.com/nspcc-dev/neo-go/pkg/core/interop/enumerator"
"github.com/nspcc-dev/neo-go/pkg/core/interop/interopnames" "github.com/nspcc-dev/neo-go/pkg/core/interop/interopnames"
@ -53,9 +54,9 @@ var systemInterops = []interop.Function{
{Name: interopnames.SystemCallbackCreateFromMethod, Func: callback.CreateFromMethod, Price: 1000000, ParamCount: 2, DisallowCallback: true}, {Name: interopnames.SystemCallbackCreateFromMethod, Func: callback.CreateFromMethod, Price: 1000000, ParamCount: 2, DisallowCallback: true},
{Name: interopnames.SystemCallbackCreateFromSyscall, Func: callback.CreateFromSyscall, Price: 400, ParamCount: 1, DisallowCallback: true}, {Name: interopnames.SystemCallbackCreateFromSyscall, Func: callback.CreateFromSyscall, Price: 400, ParamCount: 1, DisallowCallback: true},
{Name: interopnames.SystemCallbackInvoke, Func: callback.Invoke, Price: 1000000, ParamCount: 2, DisallowCallback: true}, {Name: interopnames.SystemCallbackInvoke, Func: callback.Invoke, Price: 1000000, ParamCount: 2, DisallowCallback: true},
{Name: interopnames.SystemContractCall, Func: contractCall, Price: 1000000, {Name: interopnames.SystemContractCall, Func: contract.Call, Price: 1000000,
RequiredFlags: smartcontract.AllowCall, ParamCount: 3, DisallowCallback: true}, RequiredFlags: smartcontract.AllowCall, ParamCount: 3, DisallowCallback: true},
{Name: interopnames.SystemContractCallEx, Func: contractCallEx, Price: 1000000, {Name: interopnames.SystemContractCallEx, Func: contract.CallEx, Price: 1000000,
RequiredFlags: smartcontract.AllowCall, ParamCount: 4, DisallowCallback: true}, RequiredFlags: smartcontract.AllowCall, ParamCount: 4, DisallowCallback: true},
{Name: interopnames.SystemContractCreate, Func: contractCreate, Price: 0, {Name: interopnames.SystemContractCreate, Func: contractCreate, Price: 0,
RequiredFlags: smartcontract.AllowModifyStates, ParamCount: 2, DisallowCallback: true}, RequiredFlags: smartcontract.AllowModifyStates, ParamCount: 2, DisallowCallback: true},

View file

@ -7,6 +7,7 @@ import (
"github.com/nspcc-dev/neo-go/pkg/config/netmode" "github.com/nspcc-dev/neo-go/pkg/config/netmode"
"github.com/nspcc-dev/neo-go/pkg/core/dao" "github.com/nspcc-dev/neo-go/pkg/core/dao"
"github.com/nspcc-dev/neo-go/pkg/core/interop" "github.com/nspcc-dev/neo-go/pkg/core/interop"
"github.com/nspcc-dev/neo-go/pkg/core/interop/contract"
"github.com/nspcc-dev/neo-go/pkg/core/native" "github.com/nspcc-dev/neo-go/pkg/core/native"
"github.com/nspcc-dev/neo-go/pkg/core/state" "github.com/nspcc-dev/neo-go/pkg/core/state"
"github.com/nspcc-dev/neo-go/pkg/core/storage" "github.com/nspcc-dev/neo-go/pkg/core/storage"
@ -131,7 +132,7 @@ func (tn *testNative) callOtherContractWithoutArgs(ic *interop.Context, args []s
vm.Estack().PushVal(stackitem.NewArray([]stackitem.Item{})) // no args vm.Estack().PushVal(stackitem.NewArray([]stackitem.Item{})) // no args
vm.Estack().PushVal(args[1]) // method vm.Estack().PushVal(args[1]) // method
vm.Estack().PushVal(args[0]) // contract hash vm.Estack().PushVal(args[0]) // contract hash
err := contractCall(ic) err := contract.Call(ic)
if err != nil { if err != nil {
return stackitem.NewBigInteger(big.NewInt(-1)) return stackitem.NewBigInteger(big.NewInt(-1))
} }
@ -147,7 +148,7 @@ func (tn *testNative) callOtherContractWithArg(ic *interop.Context, args []stack
vm.Estack().PushVal(stackitem.NewArray([]stackitem.Item{args[2]})) // arg vm.Estack().PushVal(stackitem.NewArray([]stackitem.Item{args[2]})) // arg
vm.Estack().PushVal(args[1]) // method vm.Estack().PushVal(args[1]) // method
vm.Estack().PushVal(args[0]) // contract hash vm.Estack().PushVal(args[0]) // contract hash
err := contractCall(ic) err := contract.Call(ic)
if err != nil { if err != nil {
return stackitem.NewBigInteger(big.NewInt(-1)) return stackitem.NewBigInteger(big.NewInt(-1))
} }