From 6cf40749a92cf64cc4a294a67575f2367f350049 Mon Sep 17 00:00:00 2001 From: Evgeniy Stratonikov Date: Fri, 5 Feb 2021 13:15:39 +0300 Subject: [PATCH] compiler: add tests for native wrappers --- pkg/compiler/analysis.go | 7 +- pkg/compiler/native_test.go | 202 ++++++++++++++++++++++++++++++++++++ 2 files changed, 208 insertions(+), 1 deletion(-) diff --git a/pkg/compiler/analysis.go b/pkg/compiler/analysis.go index 70df4d99f..0004db63b 100644 --- a/pkg/compiler/analysis.go +++ b/pkg/compiler/analysis.go @@ -287,9 +287,14 @@ func isNativeHelpersPath(s string) bool { // canConvert returns true if type doesn't need to be converted on type assertion. func canConvert(s string) bool { + if len(s) != 0 && s[0] == '*' { + s = s[1:] + } if isInteropPath(s) { s = s[len(interopPrefix):] - return s != "/iterator.Iterator" && s != "/storage.Context" + return s != "/iterator.Iterator" && s != "/storage.Context" && + s != "/native/ledger.Block" && s != "/native/ledger.Transaction" && + s != "/native/management.Contract" } return true } diff --git a/pkg/compiler/native_test.go b/pkg/compiler/native_test.go index 78a6c14b8..5aafd6208 100644 --- a/pkg/compiler/native_test.go +++ b/pkg/compiler/native_test.go @@ -2,18 +2,27 @@ package compiler_test import ( "fmt" + "math/big" + "strings" "testing" + "github.com/nspcc-dev/neo-go/pkg/core/interop" + "github.com/nspcc-dev/neo-go/pkg/core/interop/interopnames" "github.com/nspcc-dev/neo-go/pkg/core/native" "github.com/nspcc-dev/neo-go/pkg/interop/native/gas" "github.com/nspcc-dev/neo-go/pkg/interop/native/ledger" + "github.com/nspcc-dev/neo-go/pkg/interop/native/management" "github.com/nspcc-dev/neo-go/pkg/interop/native/nameservice" "github.com/nspcc-dev/neo-go/pkg/interop/native/neo" "github.com/nspcc-dev/neo-go/pkg/interop/native/notary" "github.com/nspcc-dev/neo-go/pkg/interop/native/oracle" "github.com/nspcc-dev/neo-go/pkg/interop/native/policy" "github.com/nspcc-dev/neo-go/pkg/interop/native/roles" + "github.com/nspcc-dev/neo-go/pkg/smartcontract" + "github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag" "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" "github.com/stretchr/testify/require" ) @@ -51,3 +60,196 @@ func TestNameServiceRecordType(t *testing.T) { require.EqualValues(t, native.RecordTypeTXT, nameservice.TypeTXT) require.EqualValues(t, native.RecordTypeAAAA, nameservice.TypeAAAA) } + +type nativeTestCase struct { + method string + params []string +} + +// Here we test that corresponding method does exist, is invoked and correct value is returned. +func TestNativeHelpersCompile(t *testing.T) { + cs := native.NewContracts(true) + u160 := `interop.Hash160("aaaaaaaaaaaaaaaaaaaa")` + u256 := `interop.Hash256("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")` + pub := `interop.PublicKey("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")` + nep17TestCases := []nativeTestCase{ + {"balanceOf", []string{u160}}, + {"decimals", nil}, + {"symbol", nil}, + {"totalSupply", nil}, + {"transfer", []string{u160, u160, "123", "nil"}}, + } + runNativeTestCases(t, cs.NEO.ContractMD, "neo", append([]nativeTestCase{ + {"getCandidates", nil}, + {"getCommittee", nil}, + {"getGasPerBlock", nil}, + {"getNextBlockValidators", nil}, + {"registerCandidate", []string{pub}}, + {"setGasPerBlock", []string{"1"}}, + {"vote", []string{u160, pub}}, + {"unclaimedGas", []string{u160, "123"}}, + {"unregisterCandidate", []string{pub}}, + }, nep17TestCases...)) + runNativeTestCases(t, cs.GAS.ContractMD, "gas", nep17TestCases) + runNativeTestCases(t, cs.Oracle.ContractMD, "oracle", []nativeTestCase{ + {"request", []string{`"url"`, "nil", `"callback"`, "nil", "123"}}, + }) + runNativeTestCases(t, cs.Designate.ContractMD, "roles", []nativeTestCase{ + {"designateAsRole", []string{"1", "[]interop.PublicKey{}"}}, + {"getDesignatedByRole", []string{"1", "1000"}}, + }) + runNativeTestCases(t, cs.Policy.ContractMD, "policy", []nativeTestCase{ + {"blockAccount", []string{u160}}, + {"getExecFeeFactor", nil}, + {"getFeePerByte", nil}, + {"getMaxBlockSize", nil}, + {"getMaxBlockSystemFee", nil}, + {"getMaxTransactionsPerBlock", nil}, + {"getStoragePrice", nil}, + {"isBlocked", []string{u160}}, + {"setExecFeeFactor", []string{"42"}}, + {"setFeePerByte", []string{"42"}}, + {"setMaxBlockSize", []string{"42"}}, + {"setMaxBlockSystemFee", []string{"42"}}, + {"setMaxTransactionsPerBlock", []string{"42"}}, + {"setStoragePrice", []string{"42"}}, + {"unblockAccount", []string{u160}}, + }) + runNativeTestCases(t, cs.NameService.ContractMD, "nameservice", []nativeTestCase{ + // nonfungible + {"symbol", nil}, + {"decimals", nil}, + {"totalSupply", nil}, + {"ownerOf", []string{`"neo.com"`}}, + {"balanceOf", []string{u160}}, + {"properties", []string{`"neo.com"`}}, + {"tokens", nil}, + {"tokensOf", []string{u160}}, + {"transfer", []string{u160, `"neo.com"`}}, + + // name service + {"addRoot", []string{`"com"`}}, + {"deleteRecord", []string{`"neo.com"`, "nameservice.TypeA"}}, + {"isAvailable", []string{`"neo.com"`}}, + {"getPrice", nil}, + {"getRecord", []string{`"neo.com"`, "nameservice.TypeA"}}, + {"register", []string{`"neo.com"`, u160}}, + {"renew", []string{`"neo.com"`}}, + {"resolve", []string{`"neo.com"`, "nameservice.TypeA"}}, + {"setPrice", []string{"42"}}, + {"setAdmin", []string{`"neo.com"`, u160}}, + {"setRecord", []string{`"neo.com"`, "nameservice.TypeA", `"1.1.1.1"`}}, + }) + runNativeTestCases(t, cs.Ledger.ContractMD, "ledger", []nativeTestCase{ + {"currentHash", nil}, + {"currentIndex", nil}, + {"getBlock", []string{"1"}}, + {"getTransaction", []string{u256}}, + {"getTransactionFromBlock", []string{u256, "1"}}, + {"getTransactionHeight", []string{u256}}, + }) + runNativeTestCases(t, cs.Notary.ContractMD, "notary", []nativeTestCase{ + {"lockDepositUntil", []string{u160, "123"}}, + {"withdraw", []string{u160, u160}}, + {"balanceOf", []string{u160}}, + {"expirationOf", []string{u160}}, + {"getMaxNotValidBeforeDelta", nil}, + {"setMaxNotValidBeforeDelta", []string{"42"}}, + }) + runNativeTestCases(t, cs.Management.ContractMD, "management", []nativeTestCase{ + {"deploy", []string{"nil", "nil"}}, + {"deployWithData", []string{"nil", "nil", "123"}}, + {"destroy", nil}, + {"getContract", []string{u160}}, + {"getMinimumDeploymentFee", nil}, + {"setMinimumDeploymentFee", []string{"42"}}, + {"update", []string{"nil", "nil"}}, + {"updateWithData", []string{"nil", "nil", "123"}}, + }) +} + +func runNativeTestCases(t *testing.T, ctr interop.ContractMD, name string, testCases []nativeTestCase) { + t.Run(ctr.Name, func(t *testing.T) { + for _, tc := range testCases { + t.Run(tc.method, func(t *testing.T) { + runNativeTestCase(t, ctr, name, tc.method, tc.params...) + }) + } + }) +} + +func runNativeTestCase(t *testing.T, ctr interop.ContractMD, name, method string, params ...string) { + md, ok := ctr.GetMethod(strings.TrimSuffix(method, "WithData"), len(params)) + require.True(t, ok) + + isVoid := md.MD.ReturnType == smartcontract.VoidType + srcTmpl := `package foo + import "github.com/nspcc-dev/neo-go/pkg/interop/native/%s" + import "github.com/nspcc-dev/neo-go/pkg/interop" + var _ interop.Hash160 + ` + if isVoid { + srcTmpl += `func Main() { %s.%s(%s) }` + } else { + srcTmpl += `func Main() interface{} { return %s.%s(%s) }` + } + methodUpper := strings.ToUpper(method[:1]) + method[1:] // ASCII only + methodUpper = strings.ReplaceAll(methodUpper, "Gas", "GAS") + src := fmt.Sprintf(srcTmpl, name, name, methodUpper, strings.Join(params, ",")) + + v, s := vmAndCompileInterop(t, src) + id := interopnames.ToID([]byte(interopnames.SystemContractCall)) + result := getTestStackItem(md.MD.ReturnType) + s.interops[id] = testContractCall(t, ctr.Hash, md, result) + require.NoError(t, v.Run()) + if isVoid { + require.Equal(t, 0, v.Estack().Len()) + return + } + require.Equal(t, 1, v.Estack().Len(), "stack contains unexpected items") + require.Equal(t, result.Value(), v.Estack().Pop().Item().Value()) +} + +func getTestStackItem(typ smartcontract.ParamType) stackitem.Item { + switch typ { + case smartcontract.AnyType, smartcontract.VoidType: + return stackitem.Null{} + case smartcontract.BoolType: + return stackitem.NewBool(true) + case smartcontract.IntegerType: + return stackitem.NewBigInteger(big.NewInt(42)) + case smartcontract.ByteArrayType, smartcontract.StringType, smartcontract.Hash160Type, + smartcontract.Hash256Type, smartcontract.PublicKeyType, smartcontract.SignatureType: + return stackitem.NewByteArray([]byte("result")) + case smartcontract.ArrayType: + return stackitem.NewArray([]stackitem.Item{stackitem.NewBool(true), stackitem.Null{}}) + case smartcontract.MapType: + return stackitem.NewMapWithValue([]stackitem.MapElement{{ + Key: stackitem.NewByteArray([]byte{1, 2, 3}), + Value: stackitem.NewByteArray([]byte{5, 6, 7}), + }}) + case smartcontract.InteropInterfaceType: + return stackitem.NewInterop(42) + default: + panic("unexpected type") + } +} + +func testContractCall(t *testing.T, hash util.Uint160, md interop.MethodAndPrice, result stackitem.Item) func(*vm.VM) error { + return func(v *vm.VM) error { + h := v.Estack().Pop().Bytes() + require.Equal(t, hash.BytesBE(), h) + + method := v.Estack().Pop().String() + require.Equal(t, md.MD.Name, method) + + fs := callflag.CallFlag(int32(v.Estack().Pop().BigInt().Int64())) + require.Equal(t, md.RequiredFlags, fs) + + args := v.Estack().Pop().Array() + require.Equal(t, len(md.MD.Parameters), len(args)) + + v.Estack().PushVal(result) + return nil + } +}