diff --git a/pkg/compiler/syscall_test.go b/pkg/compiler/syscall_test.go index 9630c1f8c..fdb2f7228 100644 --- a/pkg/compiler/syscall_test.go +++ b/pkg/compiler/syscall_test.go @@ -1,9 +1,15 @@ package compiler_test import ( + "errors" + "fmt" "math/big" + "strings" "testing" + "github.com/nspcc-dev/neo-go/pkg/compiler" + "github.com/nspcc-dev/neo-go/pkg/core" + "github.com/nspcc-dev/neo-go/pkg/core/interop" "github.com/nspcc-dev/neo-go/pkg/core/interop/interopnames" istorage "github.com/nspcc-dev/neo-go/pkg/core/interop/storage" "github.com/nspcc-dev/neo-go/pkg/interop/contract" @@ -37,6 +43,133 @@ func TestFindFlags(t *testing.T) { require.EqualValues(t, storage.PickField1, istorage.FindPick1) } +type syscallTestCase struct { + method string + params []string + isVoid bool +} + +// This test ensures that our wrappers have necessary number of parameters +// and execute needed syscall. Because of lack of typing (compared to native contracts) +// parameter types can't be checked. +func TestSyscallExecution(t *testing.T) { + b := `[]byte{1}` + u160 := `interop.Hash160("aaaaaaaaaaaaaaaaaaaa")` + pub := `interop.PublicKey("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")` + pubs := "[]interop.PublicKey{ " + pub + "}" + sig := `interop.Signature("aaaaaa")` + sigs := "[]interop.Signature{" + sig + "}" + sctx := "storage.Context{}" + interops := map[string]syscallTestCase{ + "binary.Atoi": {interopnames.SystemBinaryAtoi, []string{`"123"`, "10"}, false}, + "binary.Base58Decode": {interopnames.SystemBinaryBase58Decode, []string{b}, false}, + "binary.Base58Encode": {interopnames.SystemBinaryBase58Encode, []string{b}, false}, + "binary.Base64Decode": {interopnames.SystemBinaryBase64Decode, []string{b}, false}, + "binary.Base64Encode": {interopnames.SystemBinaryBase64Encode, []string{b}, false}, + "binary.Deserialize": {interopnames.SystemBinaryDeserialize, []string{b}, false}, + "binary.Itoa": {interopnames.SystemBinaryItoa, []string{"123", "10"}, false}, + "binary.Serialize": {interopnames.SystemBinarySerialize, []string{"10"}, false}, + "contract.Call": {interopnames.SystemContractCall, []string{u160, `"m"`, "1", "3"}, false}, + "contract.CreateMultisigAccount": {interopnames.SystemContractCreateMultisigAccount, []string{"1", pubs}, false}, + "contract.CreateStandardAccount": {interopnames.SystemContractCreateStandardAccount, []string{pub}, false}, + "contract.IsStandard": {interopnames.SystemContractIsStandard, []string{b}, false}, + "contract.GetCallFlags": {interopnames.SystemContractGetCallFlags, nil, false}, + "iterator.Create": {interopnames.SystemIteratorCreate, []string{pubs}, false}, + "iterator.Next": {interopnames.SystemIteratorNext, []string{"iterator.Iterator{}"}, false}, + "iterator.Value": {interopnames.SystemIteratorValue, []string{"iterator.Iterator{}"}, false}, + "json.FromJSON": {interopnames.SystemJSONDeserialize, []string{b}, false}, + "json.ToJSON": {interopnames.SystemJSONSerialize, []string{b}, false}, + "runtime.CheckWitness": {interopnames.SystemRuntimeCheckWitness, []string{b}, false}, + "runtime.GasLeft": {interopnames.SystemRuntimeGasLeft, nil, false}, + "runtime.GetCallingScriptHash": {interopnames.SystemRuntimeGetCallingScriptHash, nil, false}, + "runtime.GetEntryScriptHash": {interopnames.SystemRuntimeGetEntryScriptHash, nil, false}, + "runtime.GetExecutingScriptHash": {interopnames.SystemRuntimeGetExecutingScriptHash, nil, false}, + "runtime.GetInvocationCounter": {interopnames.SystemRuntimeGetInvocationCounter, nil, false}, + "runtime.GetNotifications": {interopnames.SystemRuntimeGetNotifications, []string{u160}, false}, + "runtime.GetScriptContainer": {interopnames.SystemRuntimeGetScriptContainer, nil, false}, + "runtime.GetTime": {interopnames.SystemRuntimeGetTime, nil, false}, + "runtime.GetTrigger": {interopnames.SystemRuntimeGetTrigger, nil, false}, + "runtime.Log": {interopnames.SystemRuntimeLog, []string{`"msg"`}, true}, + "runtime.Notify": {interopnames.SystemRuntimeNotify, []string{`"ev"`, "1"}, true}, + "runtime.Platform": {interopnames.SystemRuntimePlatform, nil, false}, + "storage.Delete": {interopnames.SystemStorageDelete, []string{sctx, b}, true}, + "storage.Find": {interopnames.SystemStorageFind, []string{sctx, b, "storage.None"}, false}, + "storage.Get": {interopnames.SystemStorageGet, []string{sctx, b}, false}, + "storage.GetContext": {interopnames.SystemStorageGetContext, nil, false}, + "storage.GetReadOnlyContext": {interopnames.SystemStorageGetReadOnlyContext, nil, false}, + "storage.Put": {interopnames.SystemStoragePut, []string{sctx, b, b}, true}, + "storage.PutEx": {interopnames.SystemStoragePutEx, []string{sctx, b, b, "storage.PutConstant"}, true}, + "storage.ConvertContextToReadOnly": {interopnames.SystemStorageAsReadOnly, []string{sctx}, false}, + "crypto.ECDsaSecp256r1Verify": {interopnames.NeoCryptoVerifyWithECDsaSecp256r1, []string{b, pub, sig}, false}, + "crypto.ECDsaSecp256k1Verify": {interopnames.NeoCryptoVerifyWithECDsaSecp256k1, []string{b, pub, sig}, false}, + "crypto.ECDSASecp256r1CheckMultisig": {interopnames.NeoCryptoCheckMultisigWithECDsaSecp256r1, []string{b, pubs, sigs}, false}, + "crypto.ECDSASecp256k1CheckMultisig": {interopnames.NeoCryptoCheckMultisigWithECDsaSecp256k1, []string{b, pubs, sigs}, false}, + "crypto.SHA256": {interopnames.NeoCryptoSHA256, []string{b}, false}, + "crypto.RIPEMD160": {interopnames.NeoCryptoRIPEMD160, []string{b}, false}, + } + ic := &interop.Context{} + core.SpawnVM(ic) // set Functions field + for _, fs := range ic.Functions { + for i := range fs { + // It will be set in test and we want to fail if calling invalid syscall. + fs[i].Func = nil + } + } + for goName, tc := range interops { + t.Run(goName, func(t *testing.T) { + runSyscallTestCase(t, ic, goName, tc) + }) + } +} + +func runSyscallTestCase(t *testing.T, ic *interop.Context, goName string, tc syscallTestCase) { + syscallID := interopnames.ToID([]byte(tc.method)) + f := ic.GetFunction(syscallID) + require.NotNil(t, f) + require.Equal(t, f.ParamCount, len(tc.params)) + called := false + f.Func = func(ic *interop.Context) error { + called = true + if ic.VM.Estack().Len() < f.ParamCount { + return errors.New("not enough parameters") + } + for i := 0; i < f.ParamCount; i++ { + ic.VM.Estack().Pop() + } + if !tc.isVoid { + ic.VM.Estack().PushVal(42) + } + return nil + } + defer func() { f.Func = nil }() + + srcTmpl := `package foo + import "github.com/nspcc-dev/neo-go/pkg/interop/%s" + import "github.com/nspcc-dev/neo-go/pkg/interop" + func unused() { var _ interop.Hash160 } + ` + if tc.isVoid { + srcTmpl += `func Main() { %s(%s) }` + } else { + srcTmpl += `func Main() interface{} { return %s(%s) }` + } + ss := strings.Split(goName, ".") + src := fmt.Sprintf(srcTmpl, ss[0], goName, strings.Join(tc.params, ", ")) + b, _, err := compiler.CompileWithDebugInfo("foo", strings.NewReader(src)) + require.NoError(t, err) + + v := ic.SpawnVM() + v.LoadScriptWithFlags(b, callflag.All) + require.NoError(t, v.Run()) + require.True(t, called) + if tc.isVoid { + require.Equal(t, 0, v.Estack().Len()) + } else { + require.Equal(t, 1, v.Estack().Len()) + require.Equal(t, big.NewInt(42), v.Estack().Pop().Value()) + } +} + func TestStoragePutGet(t *testing.T) { src := ` package foo diff --git a/pkg/core/interops.go b/pkg/core/interops.go index 8c19e8955..750a21ba5 100644 --- a/pkg/core/interops.go +++ b/pkg/core/interops.go @@ -73,7 +73,7 @@ var systemInterops = []interop.Function{ {Name: interopnames.SystemStorageDelete, Func: storageDelete, Price: 0, RequiredFlags: callflag.WriteStates, ParamCount: 2}, {Name: interopnames.SystemStorageFind, Func: storageFind, Price: 1 << 15, RequiredFlags: callflag.ReadStates, - ParamCount: 2}, + ParamCount: 3}, {Name: interopnames.SystemStorageGet, Func: storageGet, Price: 1 << 15, RequiredFlags: callflag.ReadStates, ParamCount: 2}, {Name: interopnames.SystemStorageGetContext, Func: storageGetContext, Price: 1 << 4, diff --git a/pkg/interop/contract/contract.go b/pkg/interop/contract/contract.go index e37064ce1..0fe1b88bc 100644 --- a/pkg/interop/contract/contract.go +++ b/pkg/interop/contract/contract.go @@ -46,7 +46,7 @@ func CreateStandardAccount(pub interop.PublicKey) []byte { // GetCallFlags returns calling flags which execution context was created with. // This function uses `System.Contract.GetCallFlags` syscall. func GetCallFlags() CallFlag { - return neogointernal.Syscall0("System.Contract.GetFlags").(CallFlag) + return neogointernal.Syscall0("System.Contract.GetCallFlags").(CallFlag) } // Call executes previously deployed blockchain contract with specified hash