diff --git a/pkg/core/interop/contract/call.go b/pkg/core/interop/contract/call.go new file mode 100644 index 000000000..86adf6df8 --- /dev/null +++ b/pkg/core/interop/contract/call.go @@ -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 +} diff --git a/pkg/core/interop_system.go b/pkg/core/interop_system.go index 300f59556..efffc3fdb 100644 --- a/pkg/core/interop_system.go +++ b/pkg/core/interop_system.go @@ -6,7 +6,6 @@ import ( "fmt" "math" "math/big" - "strings" "github.com/nspcc-dev/neo-go/pkg/core/block" "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/transaction" "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/vm" "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" @@ -454,82 +451,6 @@ func storageContextAsReadOnly(ic *interop.Context) error { 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. func contractDestroy(ic *interop.Context) error { hash := ic.VM.GetCurrentScriptHash() diff --git a/pkg/core/interop_system_test.go b/pkg/core/interop_system_test.go index 56eb18ade..eac5a680c 100644 --- a/pkg/core/interop_system_test.go +++ b/pkg/core/interop_system_test.go @@ -8,6 +8,7 @@ import ( "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/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/state" "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("add") ic.VM.Estack().PushVal(h.BytesBE()) - require.NoError(t, contractCall(ic)) + require.NoError(t, contract.Call(ic)) require.NoError(t, ic.VM.Run()) require.Equal(t, 2, ic.VM.Estack().Len()) 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("add") 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) { @@ -466,7 +467,7 @@ func TestContractCall(t *testing.T) { // interops can both return error and panic, // we don't care which kind of error has occurred require.Panics(t, func() { - err := contractCall(ic) + err := contract.Call(ic) if err != nil { panic(err) } @@ -491,7 +492,7 @@ func TestContractCall(t *testing.T) { ic.VM.Estack().PushVal(stackitem.NewArray(nil)) ic.VM.Estack().PushVal("invalidReturn") ic.VM.Estack().PushVal(h.BytesBE()) - require.NoError(t, contractCall(ic)) + require.NoError(t, contract.Call(ic)) require.Error(t, ic.VM.Run()) }) 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("justReturn") ic.VM.Estack().PushVal(h.BytesBE()) - require.NoError(t, contractCall(ic)) + require.NoError(t, contract.Call(ic)) require.NoError(t, ic.VM.Run()) require.Equal(t, 2, ic.VM.Estack().Len()) 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("drop") ic.VM.Estack().PushVal(h.BytesBE()) - require.NoError(t, contractCall(ic)) + require.NoError(t, contract.Call(ic)) 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("add3") ic.VM.Estack().PushVal(h.BytesBE()) - require.NoError(t, contractCall(ic)) + require.NoError(t, contract.Call(ic)) require.NoError(t, ic.VM.Run()) require.Equal(t, 2, ic.VM.Estack().Len()) require.Equal(t, big.NewInt(8), ic.VM.Estack().Pop().Value()) diff --git a/pkg/core/interops.go b/pkg/core/interops.go index 4c6ab5af8..0d44457cf 100644 --- a/pkg/core/interops.go +++ b/pkg/core/interops.go @@ -10,6 +10,7 @@ package core import ( "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/contract" "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/interopnames" @@ -53,9 +54,9 @@ var systemInterops = []interop.Function{ {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.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}, - {Name: interopnames.SystemContractCallEx, Func: contractCallEx, Price: 1000000, + {Name: interopnames.SystemContractCallEx, Func: contract.CallEx, Price: 1000000, RequiredFlags: smartcontract.AllowCall, ParamCount: 4, DisallowCallback: true}, {Name: interopnames.SystemContractCreate, Func: contractCreate, Price: 0, RequiredFlags: smartcontract.AllowModifyStates, ParamCount: 2, DisallowCallback: true}, diff --git a/pkg/core/native_contract_test.go b/pkg/core/native_contract_test.go index 222af4c4d..b3c5d545d 100644 --- a/pkg/core/native_contract_test.go +++ b/pkg/core/native_contract_test.go @@ -7,6 +7,7 @@ import ( "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/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/state" "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(args[1]) // method vm.Estack().PushVal(args[0]) // contract hash - err := contractCall(ic) + err := contract.Call(ic) if err != nil { 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(args[1]) // method vm.Estack().PushVal(args[0]) // contract hash - err := contractCall(ic) + err := contract.Call(ic) if err != nil { return stackitem.NewBigInteger(big.NewInt(-1)) }