From e5d8c1c985a5b286399314dd2b24470fac893a0d Mon Sep 17 00:00:00 2001 From: Evgeniy Stratonikov Date: Wed, 3 Feb 2021 16:10:31 +0300 Subject: [PATCH 01/14] compiler: allow conversion to types from external packages --- pkg/compiler/codegen.go | 35 ++++++++++++++++++++++++---- pkg/compiler/convert_test.go | 31 ++++++++++++++++++++++++ pkg/compiler/testdata/types/types.go | 7 ++++++ 3 files changed, 68 insertions(+), 5 deletions(-) create mode 100644 pkg/compiler/testdata/types/types.go diff --git a/pkg/compiler/codegen.go b/pkg/compiler/codegen.go index b9204edff..4d9c7dc4d 100644 --- a/pkg/compiler/codegen.go +++ b/pkg/compiler/codegen.go @@ -821,13 +821,20 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { } f, ok = c.funcs[name] - // @FIXME this could cause runtime errors. - f.selector = fun.X.(*ast.Ident) - if !ok { - c.prog.Err = fmt.Errorf("could not resolve function %s", fun.Sel.Name) + if ok { + f.selector = fun.X.(*ast.Ident) + isBuiltin = isCustomBuiltin(f) + } else { + typ := c.typeOf(fun) + if _, ok := typ.(*types.Signature); ok { + c.prog.Err = fmt.Errorf("could not resolve function %s", fun.Sel.Name) + return nil + } + + ast.Walk(c, n.Args[0]) + c.emitExplicitConvert(c.typeOf(n.Args[0]), typ) return nil } - isBuiltin = isCustomBuiltin(f) case *ast.ArrayType: // For now we will assume that there are only byte slice conversions. // E.g. []byte("foobar") or []byte(scriptHash). @@ -1242,6 +1249,24 @@ func (c *codegen) processDefers() { } } +// emitExplicitConvert handles `someType(someValue)` conversions between string/[]byte. +// Rules for conversion: +// 1. interop.* types are converted to ByteArray if not already. +// 2. Otherwise convert between ByteArray/Buffer. +// 3. Rules for types which are not string/[]byte should already +// be enforced by go parser. +func (c *codegen) emitExplicitConvert(from, to types.Type) { + if isInteropPath(to.String()) { + if isByteSlice(from) && !isString(from) { + c.emitConvert(stackitem.ByteArrayT) + } + } else if isByteSlice(to) && !isByteSlice(from) { + c.emitConvert(stackitem.BufferT) + } else if isString(to) && !isString(from) { + c.emitConvert(stackitem.ByteArrayT) + } +} + func (c *codegen) rangeLoadKey() { emit.Int(c.prog.BinWriter, 2) emit.Opcodes(c.prog.BinWriter, diff --git a/pkg/compiler/convert_test.go b/pkg/compiler/convert_test.go index f0193acd0..44e4a696b 100644 --- a/pkg/compiler/convert_test.go +++ b/pkg/compiler/convert_test.go @@ -87,6 +87,37 @@ func TestTypeConversion(t *testing.T) { eval(t, src, big.NewInt(42)) } +func TestSelectorTypeConversion(t *testing.T) { + src := `package foo + import "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/types" + import "github.com/nspcc-dev/neo-go/pkg/interop/util" + import "github.com/nspcc-dev/neo-go/pkg/interop" + func Main() int { + var a int + if util.Equals(types.Buffer(nil), nil) { + a += 1 + } + + // Buffer != ByteArray + if util.Equals(types.Buffer("\x12"), "\x12") { + a += 10 + } + + tmp := []byte{0x23} + if util.Equals(types.ByteString(tmp), "\x23") { + a += 100 + } + + addr := "aaaaaaaaaaaaaaaaaaaa" + buf := []byte(addr) + if util.Equals(interop.Hash160(addr), interop.Hash160(buf)) { + a += 1000 + } + return a + }` + eval(t, src, big.NewInt(1101)) +} + func TestTypeConversionString(t *testing.T) { src := `package foo type mystr string diff --git a/pkg/compiler/testdata/types/types.go b/pkg/compiler/testdata/types/types.go new file mode 100644 index 000000000..0ab3a1672 --- /dev/null +++ b/pkg/compiler/testdata/types/types.go @@ -0,0 +1,7 @@ +package types + +// Buffer represents Buffer VM type. +type Buffer []byte + +// ByteString represents ByteString VM type. +type ByteString string From 6e866b622a8731ecc1787039607db3c93ef1166b Mon Sep 17 00:00:00 2001 From: Evgeniy Stratonikov Date: Mon, 8 Feb 2021 11:06:01 +0300 Subject: [PATCH 02/14] compiler: implement NEO contract wrapper --- pkg/compiler/analysis.go | 8 +++- pkg/compiler/codegen.go | 3 +- pkg/interop/native/neo/neo.go | 80 +++++++++++++++++++++++++++++++++++ 3 files changed, 89 insertions(+), 2 deletions(-) create mode 100644 pkg/interop/native/neo/neo.go diff --git a/pkg/compiler/analysis.go b/pkg/compiler/analysis.go index fa3fa5501..bc55364d5 100644 --- a/pkg/compiler/analysis.go +++ b/pkg/compiler/analysis.go @@ -275,6 +275,12 @@ func isSyscall(fun *funcScope) bool { return ok } +const interopPrefix = "github.com/nspcc-dev/neo-go/pkg/interop" + func isInteropPath(s string) bool { - return strings.HasPrefix(s, "github.com/nspcc-dev/neo-go/pkg/interop") + return strings.HasPrefix(s, interopPrefix) +} + +func isNativeHelpersPath(s string) bool { + return strings.HasPrefix(s, interopPrefix+"/native") } diff --git a/pkg/compiler/codegen.go b/pkg/compiler/codegen.go index 4d9c7dc4d..57652b405 100644 --- a/pkg/compiler/codegen.go +++ b/pkg/compiler/codegen.go @@ -1915,7 +1915,8 @@ func (c *codegen) compile(info *buildInfo, pkg *loader.PackageInfo) error { // Don't convert the function if it's not used. This will save a lot // of bytecode space. name := c.getFuncNameFromDecl(pkg.Path(), n) - if !isInitFunc(n) && !isDeployFunc(n) && funUsage.funcUsed(name) && !isInteropPath(pkg.Path()) { + if !isInitFunc(n) && !isDeployFunc(n) && funUsage.funcUsed(name) && + (!isInteropPath(pkg.Path()) || isNativeHelpersPath(pkg.Path())) { c.convertFuncDecl(f, n, pkg) } } diff --git a/pkg/interop/native/neo/neo.go b/pkg/interop/native/neo/neo.go new file mode 100644 index 000000000..b0ff66f78 --- /dev/null +++ b/pkg/interop/native/neo/neo.go @@ -0,0 +1,80 @@ +package neo + +import ( + "github.com/nspcc-dev/neo-go/pkg/interop" + "github.com/nspcc-dev/neo-go/pkg/interop/contract" +) + +// Hash represents NEO contract hash. +const Hash = "\x83\xab\x06\x79\xad\x55\xc0\x50\xa1\x3a\xd4\x3f\x59\x36\xea\x73\xf5\xeb\x1e\xf6" + +// Symbol represents `symbol` method of NEO native contract. +func Symbol() string { + return contract.Call(interop.Hash160(Hash), "symbol", contract.NoneFlag).(string) +} + +// Decimals represents `decimals` method of NEO native contract. +func Decimals() int { + return contract.Call(interop.Hash160(Hash), "decimals", contract.NoneFlag).(int) +} + +// TotalSupply represents `totalSupply` method of NEO native contract. +func TotalSupply() int { + return contract.Call(interop.Hash160(Hash), "totalSupply", contract.ReadStates).(int) +} + +// BalanceOf represents `balanceOf` method of NEO native contract. +func BalanceOf(addr interop.Hash160) int { + return contract.Call(interop.Hash160(Hash), "balanceOf", contract.ReadStates, addr).(int) +} + +// Transfer represents `transfer` method of NEO native contract. +func Transfer(from, to interop.Hash160, amount int, data interface{}) bool { + return contract.Call(interop.Hash160(Hash), "transfer", + contract.WriteStates|contract.AllowCall|contract.AllowNotify, from, to, amount, data).(bool) +} + +// GetCommittee represents `getCommittee` method of NEO native contract. +func GetCommittee() []interop.PublicKey { + return contract.Call(interop.Hash160(Hash), "getCommittee", contract.ReadStates).([]interop.PublicKey) +} + +// GetCandidates represents `getCandidates` method of NEO native contract. +func GetCandidates() []interop.PublicKey { + return contract.Call(interop.Hash160(Hash), "getCandidates", contract.ReadStates).([]interop.PublicKey) +} + +// GetNextBlockValidators represents `getNextBlockValidators` method of NEO native contract. +func GetNextBlockValidators() []interop.PublicKey { + return contract.Call(interop.Hash160(Hash), "getNextBlockValidators", contract.ReadStates).([]interop.PublicKey) +} + +// GetGASPerBlock represents `getGasPerBlock` method of NEO native contract. +func GetGASPerBlock() int { + return contract.Call(interop.Hash160(Hash), "getGasPerBlock", contract.ReadStates).(int) +} + +// SetGASPerBlock represents `setGasPerBlock` method of NEO native contract. +func SetGASPerBlock(amount int) { + contract.Call(interop.Hash160(Hash), "setGasPerBlock", contract.WriteStates, amount) +} + +// RegisterCandidate represents `registerCandidate` method of NEO native contract. +func RegisterCandidate(pub interop.PublicKey) bool { + return contract.Call(interop.Hash160(Hash), "registerCandidate", contract.WriteStates, pub).(bool) +} + +// UnregisterCandidate represents `unregisterCandidate` method of NEO native contract. +func UnregisterCandidate(pub interop.PublicKey) bool { + return contract.Call(interop.Hash160(Hash), "unregisterCandidate", contract.WriteStates, pub).(bool) +} + +// Vote represents `vote` method of NEO native contract. +func Vote(addr interop.Hash160, pub interop.PublicKey) bool { + return contract.Call(interop.Hash160(Hash), "vote", contract.WriteStates, addr, pub).(bool) +} + +// UnclaimedGAS represents `unclaimedGas` method of NEO native contract. +func UnclaimedGAS(addr interop.Hash160, end int) int { + return contract.Call(interop.Hash160(Hash), "unclaimedGas", contract.ReadStates, addr, end).(int) +} From 34a6eef8ce7178a319a8e1c87fbf5f6e5e91cf10 Mon Sep 17 00:00:00 2001 From: Evgeniy Stratonikov Date: Mon, 8 Feb 2021 11:06:14 +0300 Subject: [PATCH 03/14] compiler: implement GAS contract wrapper --- pkg/interop/native/gas/gas.go | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 pkg/interop/native/gas/gas.go diff --git a/pkg/interop/native/gas/gas.go b/pkg/interop/native/gas/gas.go new file mode 100644 index 000000000..289dd148a --- /dev/null +++ b/pkg/interop/native/gas/gas.go @@ -0,0 +1,35 @@ +package gas + +import ( + "github.com/nspcc-dev/neo-go/pkg/interop" + "github.com/nspcc-dev/neo-go/pkg/interop/contract" +) + +// Hash represents GAS contract hash. +const Hash = "\x28\xb3\xad\xab\x72\x69\xf9\xc2\x18\x1d\xb3\xcb\x74\x1e\xbf\x55\x19\x30\xe2\x70" + +// Symbol represents `symbol` method of GAS native contract. +func Symbol() string { + return contract.Call(interop.Hash160(Hash), "symbol", contract.NoneFlag).(string) +} + +// Decimals represents `decimals` method of GAS native contract. +func Decimals() int { + return contract.Call(interop.Hash160(Hash), "decimals", contract.NoneFlag).(int) +} + +// TotalSupply represents `totalSupply` method of GAS native contract. +func TotalSupply() int { + return contract.Call(interop.Hash160(Hash), "totalSupply", contract.ReadStates).(int) +} + +// BalanceOf represents `balanceOf` method of GAS native contract. +func BalanceOf(addr interop.Hash160) int { + return contract.Call(interop.Hash160(Hash), "balanceOf", contract.ReadStates, addr).(int) +} + +// Transfer represents `transfer` method of GAS native contract. +func Transfer(from, to interop.Hash160, amount int, data interface{}) bool { + return contract.Call(interop.Hash160(Hash), "transfer", + contract.WriteStates|contract.AllowCall|contract.AllowNotify, from, to, amount, data).(bool) +} From 779fba300193215b67cfdc6290b939a3992f5ec5 Mon Sep 17 00:00:00 2001 From: Evgeniy Stratonikov Date: Mon, 8 Feb 2021 11:06:28 +0300 Subject: [PATCH 04/14] compiler: implement Oracle contract wrapper --- pkg/interop/native/oracle/oracle.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 pkg/interop/native/oracle/oracle.go diff --git a/pkg/interop/native/oracle/oracle.go b/pkg/interop/native/oracle/oracle.go new file mode 100644 index 000000000..78eb26ca6 --- /dev/null +++ b/pkg/interop/native/oracle/oracle.go @@ -0,0 +1,16 @@ +package oracle + +import ( + "github.com/nspcc-dev/neo-go/pkg/interop" + "github.com/nspcc-dev/neo-go/pkg/interop/contract" +) + +// Hash represents Oracle contract hash. +const Hash = "\xee\x80\x4c\x14\x29\x68\xd4\x78\x8b\x8a\xff\x51\xda\xde\xdf\xcb\x42\xe7\xc0\x8d" + +// Request represents `request` method of Oracle native contract. +func Request(url string, filter []byte, cb string, userData interface{}, gasForResponse int) { + contract.Call(interop.Hash160(Hash), "request", + contract.WriteStates|contract.AllowNotify, + url, filter, cb, userData, gasForResponse) +} From 3c237e2a2983bf96a0ea4b36dbeea0d63a6c4fab Mon Sep 17 00:00:00 2001 From: Evgeniy Stratonikov Date: Mon, 8 Feb 2021 11:06:54 +0300 Subject: [PATCH 05/14] compiler: implement NameService contract wrapper --- pkg/compiler/native_test.go | 16 +++ .../native/nameservice/name_service.go | 125 ++++++++++++++++++ 2 files changed, 141 insertions(+) create mode 100644 pkg/compiler/native_test.go create mode 100644 pkg/interop/native/nameservice/name_service.go diff --git a/pkg/compiler/native_test.go b/pkg/compiler/native_test.go new file mode 100644 index 000000000..1ad90c65b --- /dev/null +++ b/pkg/compiler/native_test.go @@ -0,0 +1,16 @@ +package compiler_test + +import ( + "testing" + + "github.com/nspcc-dev/neo-go/pkg/core/native" + "github.com/nspcc-dev/neo-go/pkg/interop/native/nameservice" + "github.com/stretchr/testify/require" +) + +func TestNameServiceRecordType(t *testing.T) { + require.EqualValues(t, native.RecordTypeA, nameservice.TypeA) + require.EqualValues(t, native.RecordTypeCNAME, nameservice.TypeCNAME) + require.EqualValues(t, native.RecordTypeTXT, nameservice.TypeTXT) + require.EqualValues(t, native.RecordTypeAAAA, nameservice.TypeAAAA) +} diff --git a/pkg/interop/native/nameservice/name_service.go b/pkg/interop/native/nameservice/name_service.go new file mode 100644 index 000000000..445cd55a1 --- /dev/null +++ b/pkg/interop/native/nameservice/name_service.go @@ -0,0 +1,125 @@ +package nameservice + +import ( + "github.com/nspcc-dev/neo-go/pkg/interop" + "github.com/nspcc-dev/neo-go/pkg/interop/contract" + "github.com/nspcc-dev/neo-go/pkg/interop/iterator" +) + +// RecordType represents NameService record type. +type RecordType byte + +// Various record type. +const ( + TypeA RecordType = 1 + TypeCNAME RecordType = 5 + TypeTXT RecordType = 16 + TypeAAAA RecordType = 28 +) + +// Hash represents NameService contract hash. +const Hash = "\x8c\x02\xb8\x43\x98\x6b\x3c\x44\x4f\xf8\x6a\xd5\xa9\x43\xfe\x8d\xb6\x24\xb5\xa2" + +// Symbol represents `symbol` method of NameService native contract. +func Symbol() string { + return contract.Call(interop.Hash160(Hash), "symbol", contract.NoneFlag).(string) +} + +// Decimals represents `decimals` method of NameService native contract. +func Decimals() int { + return contract.Call(interop.Hash160(Hash), "decimals", contract.NoneFlag).(int) +} + +// TotalSupply represents `totalSupply` method of NameService native contract. +func TotalSupply() int { + return contract.Call(interop.Hash160(Hash), "totalSupply", contract.ReadStates).(int) +} + +// OwnerOf represents `ownerOf` method of NameService native contract. +func OwnerOf(tokenID string) interop.Hash160 { + return contract.Call(interop.Hash160(Hash), "ownerOf", contract.ReadStates, tokenID).(interop.Hash160) +} + +// BalanceOf represents `balanceOf` method of NameService native contract. +func BalanceOf(owner interop.Hash160) int { + return contract.Call(interop.Hash160(Hash), "balanceOf", contract.ReadStates, owner).(int) +} + +// Properties represents `properties` method of NameService native contract. +func Properties(tokenID string) map[string]interface{} { + return contract.Call(interop.Hash160(Hash), "properties", contract.ReadStates, tokenID).(map[string]interface{}) +} + +// Tokens represents `tokens` method of NameService native contract. +func Tokens() iterator.Iterator { + return contract.Call(interop.Hash160(Hash), "tokens", + contract.ReadStates).(iterator.Iterator) +} + +// TokensOf represents `tokensOf` method of NameService native contract. +func TokensOf(addr interop.Hash160) iterator.Iterator { + return contract.Call(interop.Hash160(Hash), "tokensOf", + contract.ReadStates, addr).(iterator.Iterator) +} + +// Transfer represents `transfer` method of NameService native contract. +func Transfer(to interop.Hash160, tokenID string) bool { + return contract.Call(interop.Hash160(Hash), "transfer", + contract.WriteStates|contract.AllowNotify, to, tokenID).(bool) +} + +// AddRoot represents `addRoot` method of NameService native contract. +func AddRoot(root string) { + contract.Call(interop.Hash160(Hash), "addRoot", contract.WriteStates, root) +} + +// SetPrice represents `setPrice` method of NameService native contract. +func SetPrice(price int) { + contract.Call(interop.Hash160(Hash), "setPrice", contract.WriteStates, price) +} + +// GetPrice represents `getPrice` method of NameService native contract. +func GetPrice() int { + return contract.Call(interop.Hash160(Hash), "getPrice", contract.ReadStates).(int) +} + +// IsAvailable represents `isAvailable` method of NameService native contract. +func IsAvailable(name string) bool { + return contract.Call(interop.Hash160(Hash), "isAvailable", contract.ReadStates, name).(bool) +} + +// Register represents `register` method of NameService native contract. +func Register(name string, owner interop.Hash160) bool { + return contract.Call(interop.Hash160(Hash), "register", contract.WriteStates, name, owner).(bool) +} + +// Renew represents `renew` method of NameService native contract. +func Renew(name string) int { + return contract.Call(interop.Hash160(Hash), "renew", contract.WriteStates, name).(int) +} + +// SetAdmin represents `setAdmin` method of NameService native contract. +func SetAdmin(name string, admin interop.Hash160) { + contract.Call(interop.Hash160(Hash), "setAdmin", contract.WriteStates, name, admin) +} + +// SetRecord represents `setRecord` method of NameService native contract. +func SetRecord(name string, recType RecordType, data string) { + contract.Call(interop.Hash160(Hash), "setRecord", contract.WriteStates, name, recType, data) +} + +// GetRecord represents `getRecord` method of NameService native contract. +// It returns `nil` if record is missing. +func GetRecord(name string, recType RecordType) []byte { + return contract.Call(interop.Hash160(Hash), "getRecord", contract.ReadStates, name, recType).([]byte) +} + +// DeleteRecord represents `deleteRecord` method of NameService native contract. +func DeleteRecord(name string, recType RecordType) { + contract.Call(interop.Hash160(Hash), "deleteRecord", contract.WriteStates, name, recType) +} + +// Resolve represents `resolve` method of NameService native contract. +func Resolve(name string, recType RecordType) []byte { + return contract.Call(interop.Hash160(Hash), "resolve", contract.ReadStates, name, recType).([]byte) +} From 77fcfeccffdcb4367eb18579efddcbe65a938e61 Mon Sep 17 00:00:00 2001 From: Evgeniy Stratonikov Date: Mon, 8 Feb 2021 11:07:06 +0300 Subject: [PATCH 06/14] compiler: implement Policy contract wrapper --- pkg/interop/native/policy/policy.go | 84 +++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 pkg/interop/native/policy/policy.go diff --git a/pkg/interop/native/policy/policy.go b/pkg/interop/native/policy/policy.go new file mode 100644 index 000000000..56270c879 --- /dev/null +++ b/pkg/interop/native/policy/policy.go @@ -0,0 +1,84 @@ +package policy + +import ( + "github.com/nspcc-dev/neo-go/pkg/interop" + "github.com/nspcc-dev/neo-go/pkg/interop/contract" +) + +// Hash represents Policy contract hash. +const Hash = "\xf2\xe2\x08\xed\xcd\x14\x6c\xbe\xe4\x67\x6e\xdf\x79\xb7\x5e\x50\x98\xd3\xbc\x79" + +// GetMaxTransactionsPerBlock represents `getMaxTransactionsPerBlock` method of Policy native contract. +func GetMaxTransactionsPerBlock() int { + return contract.Call(interop.Hash160(Hash), "getMaxTransactionsPerBlock", contract.ReadStates).(int) +} + +// SetMaxTransactionsPerBlock represents `setMaxTransactionsPerBlock` method of Policy native contract. +func SetMaxTransactionsPerBlock(value int) { + contract.Call(interop.Hash160(Hash), "setMaxTransactionsPerBlock", contract.WriteStates, value) +} + +// GetMaxBlockSize represents `getMaxBlockSize` method of Policy native contract. +func GetMaxBlockSize() int { + return contract.Call(interop.Hash160(Hash), "getMaxBlockSize", contract.ReadStates).(int) +} + +// SetMaxBlockSize represents `setMaxBlockSize` method of Policy native contract. +func SetMaxBlockSize(value int) { + contract.Call(interop.Hash160(Hash), "setMaxBlockSize", contract.WriteStates, value) +} + +// GetFeePerByte represents `getFeePerByte` method of Policy native contract. +func GetFeePerByte() int { + return contract.Call(interop.Hash160(Hash), "getFeePerByte", contract.ReadStates).(int) +} + +// SetFeePerByte represents `setFeePerByte` method of Policy native contract. +func SetFeePerByte(value int) { + contract.Call(interop.Hash160(Hash), "setFeePerByte", contract.WriteStates, value) +} + +// GetMaxBlockSystemFee represents `getMaxBlockSystemFee` method of Policy native contract. +func GetMaxBlockSystemFee() int { + return contract.Call(interop.Hash160(Hash), "getMaxBlockSystemFee", contract.ReadStates).(int) +} + +// SetMaxBlockSystemFee represents `setMaxBlockSystemFee` method of Policy native contract. +func SetMaxBlockSystemFee(value int) { + contract.Call(interop.Hash160(Hash), "setMaxBlockSystemFee", contract.WriteStates, value) +} + +// GetExecFeeFactor represents `getExecFeeFactor` method of Policy native contract. +func GetExecFeeFactor() int { + return contract.Call(interop.Hash160(Hash), "getExecFeeFactor", contract.ReadStates).(int) +} + +// SetExecFeeFactor represents `setExecFeeFactor` method of Policy native contract. +func SetExecFeeFactor(value int) { + contract.Call(interop.Hash160(Hash), "setExecFeeFactor", contract.WriteStates, value) +} + +// GetStoragePrice represents `getStoragePrice` method of Policy native contract. +func GetStoragePrice() int { + return contract.Call(interop.Hash160(Hash), "getStoragePrice", contract.ReadStates).(int) +} + +// SetStoragePrice represents `setStoragePrice` method of Policy native contract. +func SetStoragePrice(value int) { + contract.Call(interop.Hash160(Hash), "setStoragePrice", contract.WriteStates, value) +} + +// IsBlocked represents `isBlocked` method of Policy native contract. +func IsBlocked(addr interop.Hash160) bool { + return contract.Call(interop.Hash160(Hash), "isBlocked", contract.ReadStates, addr).(bool) +} + +// BlockAccount represents `blockAccount` method of Policy native contract. +func BlockAccount(addr interop.Hash160) bool { + return contract.Call(interop.Hash160(Hash), "blockAccount", contract.WriteStates, addr).(bool) +} + +// UnblockAccount represents `unblockAccount` method of Policy native contract. +func UnblockAccount(addr interop.Hash160) bool { + return contract.Call(interop.Hash160(Hash), "unblockAccount", contract.WriteStates, addr).(bool) +} From 2f26490e5903bd6ba082795b6cf749ce4f3e2d1c Mon Sep 17 00:00:00 2001 From: Evgeniy Stratonikov Date: Mon, 8 Feb 2021 11:08:48 +0300 Subject: [PATCH 07/14] compiler: implement RoleManagement contract wrapper --- pkg/compiler/native_test.go | 7 +++++++ pkg/interop/native/roles/roles.go | 31 +++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 pkg/interop/native/roles/roles.go diff --git a/pkg/compiler/native_test.go b/pkg/compiler/native_test.go index 1ad90c65b..b3de8fb15 100644 --- a/pkg/compiler/native_test.go +++ b/pkg/compiler/native_test.go @@ -5,9 +5,16 @@ import ( "github.com/nspcc-dev/neo-go/pkg/core/native" "github.com/nspcc-dev/neo-go/pkg/interop/native/nameservice" + "github.com/nspcc-dev/neo-go/pkg/interop/native/roles" "github.com/stretchr/testify/require" ) +func TestRoleManagementRole(t *testing.T) { + require.EqualValues(t, native.RoleOracle, roles.Oracle) + require.EqualValues(t, native.RoleStateValidator, roles.StateValidator) + require.EqualValues(t, native.RoleP2PNotary, roles.P2PNotary) +} + func TestNameServiceRecordType(t *testing.T) { require.EqualValues(t, native.RecordTypeA, nameservice.TypeA) require.EqualValues(t, native.RecordTypeCNAME, nameservice.TypeCNAME) diff --git a/pkg/interop/native/roles/roles.go b/pkg/interop/native/roles/roles.go new file mode 100644 index 000000000..d3fca9aa4 --- /dev/null +++ b/pkg/interop/native/roles/roles.go @@ -0,0 +1,31 @@ +package roles + +import ( + "github.com/nspcc-dev/neo-go/pkg/interop" + "github.com/nspcc-dev/neo-go/pkg/interop/contract" +) + +// Hash represents RoleManagement contract hash. +const Hash = "\x02\x8b\x00\x50\x70\xb6\x0d\xf1\xc8\xe2\x09\x78\x7b\x49\xce\xbb\x71\x14\x7b\x59" + +// Role represents node role. +type Role byte + +// Various node roles. +const ( + StateValidator Role = 4 + Oracle Role = 8 + P2PNotary Role = 128 +) + +// GetDesignatedByRole represents `getDesignatedByRole` method of RoleManagement native contract. +func GetDesignatedByRole(r Role, height uint32) []interop.PublicKey { + return contract.Call(interop.Hash160(Hash), "getDesignatedByRole", + contract.ReadStates, r, height).([]interop.PublicKey) +} + +// DesignateAsRole represents `designateAsRole` method of RoleManagement native contract. +func DesignateAsRole(r Role, pubs []interop.PublicKey) { + contract.Call(interop.Hash160(Hash), "designateAsRole", + contract.WriteStates, r, pubs) +} From 9988f4ed1c2c1dbe2965613e57df62834170b146 Mon Sep 17 00:00:00 2001 From: Evgeniy Stratonikov Date: Mon, 8 Feb 2021 11:10:01 +0300 Subject: [PATCH 08/14] compiler: implement Notary contract wrapper --- pkg/interop/native/notary/notary.go | 41 +++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 pkg/interop/native/notary/notary.go diff --git a/pkg/interop/native/notary/notary.go b/pkg/interop/native/notary/notary.go new file mode 100644 index 000000000..f3fc81687 --- /dev/null +++ b/pkg/interop/native/notary/notary.go @@ -0,0 +1,41 @@ +package notary + +import ( + "github.com/nspcc-dev/neo-go/pkg/interop" + "github.com/nspcc-dev/neo-go/pkg/interop/contract" +) + +// Hash represents Notary contract hash. +const Hash = "\x0c\xcf\x26\x94\x3f\xb5\xc9\xb6\x05\xe2\x06\xd2\xa2\x75\xbe\x3e\xa6\xa4\x75\xf4" + +// LockDepositUntil represents `lockDepositUntil` method of Notary native contract. +func LockDepositUntil(addr interop.Hash160, till int) bool { + return contract.Call(interop.Hash160(Hash), "lockDepositUntil", contract.WriteStates, + addr, till).(bool) +} + +// Withdraw represents `withdraw` method of Notary native contract. +func Withdraw(from, to interop.Hash160) bool { + return contract.Call(interop.Hash160(Hash), "withdraw", contract.WriteStates, + from, to).(bool) +} + +// BalanceOf represents `balanceOf` method of Notary native contract. +func BalanceOf(addr interop.Hash160) int { + return contract.Call(interop.Hash160(Hash), "balanceOf", contract.ReadStates, addr).(int) +} + +// ExpirationOf represents `expirationOf` method of Notary native contract. +func ExpirationOf(addr interop.Hash160) int { + return contract.Call(interop.Hash160(Hash), "expirationOf", contract.ReadStates, addr).(int) +} + +// GetMaxNotValidBeforeDelta represents `getMaxNotValidBeforeDelta` method of Notary native contract. +func GetMaxNotValidBeforeDelta() int { + return contract.Call(interop.Hash160(Hash), "getMaxNotValidBeforeDelta", contract.ReadStates).(int) +} + +// SetMaxNotValidBeforeDelta represents `setMaxNotValidBeforeDelta` method of Notary native contract. +func SetMaxNotValidBeforeDelta(value int) { + contract.Call(interop.Hash160(Hash), "setMaxNotValidBeforeDelta", contract.WriteStates, value) +} From 7bee28b81ea58b4a3f9882d76caf4c2630e92378 Mon Sep 17 00:00:00 2001 From: Evgeniy Stratonikov Date: Mon, 8 Feb 2021 11:59:25 +0300 Subject: [PATCH 09/14] compiler: implement Management contract wrapper --- pkg/interop/native/management/contract.go | 11 +++++ pkg/interop/native/management/management.go | 53 +++++++++++++++++++++ 2 files changed, 64 insertions(+) create mode 100644 pkg/interop/native/management/contract.go create mode 100644 pkg/interop/native/management/management.go diff --git a/pkg/interop/native/management/contract.go b/pkg/interop/native/management/contract.go new file mode 100644 index 000000000..01aae529e --- /dev/null +++ b/pkg/interop/native/management/contract.go @@ -0,0 +1,11 @@ +package management + +import "github.com/nspcc-dev/neo-go/pkg/interop" + +// Contract represents deployed contract. +type Contract struct { + ID int + UpdateCounter int + Hash interop.Hash160 + NEF []byte +} diff --git a/pkg/interop/native/management/management.go b/pkg/interop/native/management/management.go new file mode 100644 index 000000000..add31a39b --- /dev/null +++ b/pkg/interop/native/management/management.go @@ -0,0 +1,53 @@ +package management + +import ( + "github.com/nspcc-dev/neo-go/pkg/interop" + "github.com/nspcc-dev/neo-go/pkg/interop/contract" +) + +// Hash represents Management contract hash. +const Hash = "\x43\x0e\x9f\x6f\xb3\x13\xa8\xd3\xa2\xb7\x61\x3b\x67\x83\x09\xd1\xd7\xd7\x01\xa5" + +// Deploy represents `deploy` method of Management native contract. +func Deploy(script, manifest []byte) *Contract { + return contract.Call(interop.Hash160(Hash), "deploy", + contract.WriteStates|contract.AllowNotify, script, manifest).(*Contract) +} + +// DeployWithData represents `deploy` method of Management native contract. +func DeployWithData(script, manifest []byte, data interface{}) *Contract { + return contract.Call(interop.Hash160(Hash), "deploy", + contract.WriteStates|contract.AllowNotify, script, manifest, data).(*Contract) +} + +// Destroy represents `destroy` method of Management native contract. +func Destroy() { + contract.Call(interop.Hash160(Hash), "destroy", contract.WriteStates|contract.AllowNotify) +} + +// GetContract represents `getContract` method of Management native contract. +func GetContract(addr interop.Hash160) *Contract { + return contract.Call(interop.Hash160(Hash), "getContract", contract.ReadStates, addr).(*Contract) +} + +// GetMinimumDeploymentFee represents `getMinimumDeploymentFee` method of Management native contract. +func GetMinimumDeploymentFee() int { + return contract.Call(interop.Hash160(Hash), "getMinimumDeploymentFee", contract.ReadStates).(int) +} + +// SetMinimumDeploymentFee represents `setMinimumDeploymentFee` method of Management native contract. +func SetMinimumDeploymentFee(value int) { + contract.Call(interop.Hash160(Hash), "setMinimumDeploymentFee", contract.WriteStates, value) +} + +// Update represents `update` method of Management native contract. +func Update(script, manifest []byte) { + contract.Call(interop.Hash160(Hash), "update", + contract.WriteStates|contract.AllowNotify, script, manifest) +} + +// UpdateWithData represents `update` method of Management native contract. +func UpdateWithData(script, manifest []byte, data interface{}) { + contract.Call(interop.Hash160(Hash), "update", + contract.WriteStates|contract.AllowNotify, script, manifest, data) +} From 69c011b3e7f5594dc71de5f9cc7aa0b9de908984 Mon Sep 17 00:00:00 2001 From: Evgeniy Stratonikov Date: Mon, 8 Feb 2021 11:10:25 +0300 Subject: [PATCH 10/14] compiler: implement Ledger contract wrapper --- pkg/compiler/debug.go | 2 +- pkg/compiler/debug_test.go | 4 +-- pkg/interop/native/ledger/block.go | 29 +++++++++++++++++ pkg/interop/native/ledger/ledger.go | 40 ++++++++++++++++++++++++ pkg/interop/native/ledger/transaction.go | 27 ++++++++++++++++ pkg/interop/runtime/engine.go | 29 ++--------------- 6 files changed, 102 insertions(+), 29 deletions(-) create mode 100644 pkg/interop/native/ledger/block.go create mode 100644 pkg/interop/native/ledger/ledger.go create mode 100644 pkg/interop/native/ledger/transaction.go diff --git a/pkg/compiler/debug.go b/pkg/compiler/debug.go index fe8588ddf..042d47fd5 100644 --- a/pkg/compiler/debug.go +++ b/pkg/compiler/debug.go @@ -239,7 +239,7 @@ func scAndVMInteropTypeFromExpr(named *types.Named) (smartcontract.ParamType, st name := named.Obj().Name() pkg := named.Obj().Pkg().Name() switch pkg { - case "runtime", "contract": + case "ledger", "contract": return smartcontract.ArrayType, stackitem.ArrayT // Block, Transaction, Contract case "interop": if name != "Interface" { diff --git a/pkg/compiler/debug_test.go b/pkg/compiler/debug_test.go index 04f545384..5e1c0346d 100644 --- a/pkg/compiler/debug_test.go +++ b/pkg/compiler/debug_test.go @@ -16,7 +16,7 @@ func TestCodeGen_DebugInfo(t *testing.T) { src := `package foo import "github.com/nspcc-dev/neo-go/pkg/interop" import "github.com/nspcc-dev/neo-go/pkg/interop/storage" - import "github.com/nspcc-dev/neo-go/pkg/interop/runtime" + import "github.com/nspcc-dev/neo-go/pkg/interop/native/ledger" func Main(op string) bool { var s string _ = s @@ -47,7 +47,7 @@ func unexportedMethod() int { return 1 } func MethodParams(addr interop.Hash160, h interop.Hash256, sig interop.Signature, pub interop.PublicKey, inter interop.Interface, - ctx storage.Context, tx runtime.Transaction) bool { + ctx storage.Context, tx ledger.Transaction) bool { return true } type MyStruct struct {} diff --git a/pkg/interop/native/ledger/block.go b/pkg/interop/native/ledger/block.go new file mode 100644 index 000000000..1196b5154 --- /dev/null +++ b/pkg/interop/native/ledger/block.go @@ -0,0 +1,29 @@ +package ledger + +import "github.com/nspcc-dev/neo-go/pkg/interop" + +// Block represents a NEO block, it's a data structure that you can get +// block-related data from. It's similar to the Block class in the Neo .net +// framework. To use it you need to get it via GetBlock function call. +type Block struct { + // Hash represents the hash (256 bit BE value in a 32 byte slice) of the + // given block. + Hash interop.Hash256 + // Version of the block. + Version int + // PrevHash represents the hash (256 bit BE value in a 32 byte slice) of the + // previous block. + PrevHash interop.Hash256 + // MerkleRoot represents the root hash (256 bit BE value in a 32 byte slice) + // of a transaction list. + MerkleRoot interop.Hash256 + // Timestamp represents millisecond-precision block timestamp. + Timestamp int + // Index represents the height of the block. + Index int + // NextConsensus represents contract address of the next miner (160 bit BE + // value in a 20 byte slice). + NextConsensus interop.Hash160 + // TransactionsLength represents the length of block's transactions array. + TransactionsLength int +} diff --git a/pkg/interop/native/ledger/ledger.go b/pkg/interop/native/ledger/ledger.go new file mode 100644 index 000000000..fe9787c30 --- /dev/null +++ b/pkg/interop/native/ledger/ledger.go @@ -0,0 +1,40 @@ +package ledger + +import ( + "github.com/nspcc-dev/neo-go/pkg/interop" + "github.com/nspcc-dev/neo-go/pkg/interop/contract" +) + +// Hash represents Ledger contract hash. +const Hash = "\x64\x87\x5a\x12\xc6\x03\xc6\x1d\xec\xff\xdf\xe7\x88\xce\x10\xdd\xc6\x69\x1d\x97" + +// CurrentHash represents `currentHash` method of Ledger native contract. +func CurrentHash() interop.Hash256 { + return contract.Call(interop.Hash160(Hash), "currentHash", contract.ReadStates).(interop.Hash256) +} + +// CurrentIndex represents `currentIndex` method of Ledger native contract. +func CurrentIndex() int { + return contract.Call(interop.Hash160(Hash), "currentIndex", contract.ReadStates).(int) +} + +// GetBlock represents `getBlock` method of Ledger native contract. +func GetBlock(indexOrHash interface{}) *Block { + return contract.Call(interop.Hash160(Hash), "getBlock", contract.ReadStates, indexOrHash).(*Block) +} + +// GetTransaction represents `getTransaction` method of Ledger native contract. +func GetTransaction(hash interop.Hash256) *Transaction { + return contract.Call(interop.Hash160(Hash), "getTransaction", contract.ReadStates, hash).(*Transaction) +} + +// GetTransactionHeight represents `getTransactionHeight` method of Ledger native contract. +func GetTransactionHeight(hash interop.Hash256) int { + return contract.Call(interop.Hash160(Hash), "getTransactionHeight", contract.ReadStates, hash).(int) +} + +// GetTransactionFromBlock represents `getTransactionFromBlock` method of Ledger native contract. +func GetTransactionFromBlock(indexOrHash interface{}, txIndex int) *Transaction { + return contract.Call(interop.Hash160(Hash), "getTransactionFromBlock", contract.ReadStates, + indexOrHash, txIndex).(*Transaction) +} diff --git a/pkg/interop/native/ledger/transaction.go b/pkg/interop/native/ledger/transaction.go new file mode 100644 index 000000000..0cc7c49d2 --- /dev/null +++ b/pkg/interop/native/ledger/transaction.go @@ -0,0 +1,27 @@ +package ledger + +import "github.com/nspcc-dev/neo-go/pkg/interop" + +// Transaction represents a NEO transaction. It's similar to Transaction class +// in Neo .net framework. +type Transaction struct { + // Hash represents the hash (256 bit BE value in a 32 byte slice) of the + // given transaction (which also is its ID). + Hash interop.Hash256 + // Version represents the transaction version. + Version int + // Nonce is a random number to avoid hash collision. + Nonce int + // Sender represents the sender (160 bit BE value in a 20 byte slice) of the + // given Transaction. + Sender interop.Hash160 + // SysFee represents fee to be burned. + SysFee int + // NetFee represents fee to be distributed to consensus nodes. + NetFee int + // ValidUntilBlock is the maximum blockchain height exceeding which + // transaction should fail verification. + ValidUntilBlock int + // Script represents code to run in NeoVM for this transaction. + Script []byte +} diff --git a/pkg/interop/runtime/engine.go b/pkg/interop/runtime/engine.go index a8180b457..9975b4e90 100644 --- a/pkg/interop/runtime/engine.go +++ b/pkg/interop/runtime/engine.go @@ -2,38 +2,15 @@ package runtime import ( "github.com/nspcc-dev/neo-go/pkg/interop" + "github.com/nspcc-dev/neo-go/pkg/interop/native/ledger" ) -// Transaction represents a NEO transaction. It's similar to Transaction class -// in Neo .net framework. -type Transaction struct { - // Hash represents the hash (256 bit BE value in a 32 byte slice) of the - // given transaction (which also is its ID). - Hash interop.Hash256 - // Version represents the transaction version. - Version int - // Nonce is a random number to avoid hash collision. - Nonce int - // Sender represents the sender (160 bit BE value in a 20 byte slice) of the - // given Transaction. - Sender interop.Hash160 - // SysFee represents fee to be burned. - SysFee int - // NetFee represents fee to be distributed to consensus nodes. - NetFee int - // ValidUntilBlock is the maximum blockchain height exceeding which - // transaction should fail verification. - ValidUntilBlock int - // Script represents code to run in NeoVM for this transaction. - Script []byte -} - // GetScriptContainer returns the transaction that initially triggered current // execution context. It never changes in a single execution, no matter how deep // this execution goes. This function uses // `System.Runtime.GetScriptContainer` syscall. -func GetScriptContainer() *Transaction { - return &Transaction{} +func GetScriptContainer() *ledger.Transaction { + return &ledger.Transaction{} } // GetExecutingScriptHash returns script hash (160 bit in BE form represented From 2f6345f2d9c35490d935bae67428e37a63ebf6e5 Mon Sep 17 00:00:00 2001 From: Evgeniy Stratonikov Date: Mon, 8 Feb 2021 11:10:58 +0300 Subject: [PATCH 11/14] compiler: add Hash compatibility test --- pkg/compiler/native_test.go | 30 +++++++++++++++++++++++++++ pkg/core/native/compatibility_test.go | 13 ------------ 2 files changed, 30 insertions(+), 13 deletions(-) diff --git a/pkg/compiler/native_test.go b/pkg/compiler/native_test.go index b3de8fb15..78a6c14b8 100644 --- a/pkg/compiler/native_test.go +++ b/pkg/compiler/native_test.go @@ -1,14 +1,44 @@ package compiler_test import ( + "fmt" "testing" "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/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/util" "github.com/stretchr/testify/require" ) +func TestContractHashes(t *testing.T) { + cs := native.NewContracts(true) + require.Equal(t, []byte(neo.Hash), cs.NEO.Hash.BytesBE()) + require.Equal(t, []byte(gas.Hash), cs.GAS.Hash.BytesBE()) + require.Equal(t, []byte(oracle.Hash), cs.Oracle.Hash.BytesBE()) + require.Equal(t, []byte(roles.Hash), cs.Designate.Hash.BytesBE()) + require.Equal(t, []byte(policy.Hash), cs.Policy.Hash.BytesBE()) + require.Equal(t, []byte(nameservice.Hash), cs.NameService.Hash.BytesBE()) + require.Equal(t, []byte(ledger.Hash), cs.Ledger.Hash.BytesBE()) + require.Equal(t, []byte(management.Hash), cs.Management.Hash.BytesBE()) + require.Equal(t, []byte(notary.Hash), cs.Notary.Hash.BytesBE()) +} + +// testPrintHash is a helper for updating contract hashes. +func testPrintHash(u util.Uint160) { + fmt.Print(`"`) + for _, b := range u.BytesBE() { + fmt.Printf("\\x%02x", b) + } + fmt.Println(`"`) +} + func TestRoleManagementRole(t *testing.T) { require.EqualValues(t, native.RoleOracle, roles.Oracle) require.EqualValues(t, native.RoleStateValidator, roles.StateValidator) diff --git a/pkg/core/native/compatibility_test.go b/pkg/core/native/compatibility_test.go index 5e0c7b471..d055628be 100644 --- a/pkg/core/native/compatibility_test.go +++ b/pkg/core/native/compatibility_test.go @@ -7,19 +7,6 @@ import ( "github.com/stretchr/testify/require" ) -// Compatibility test. hashes are taken directly from C# node. -func TestNativeHashes(t *testing.T) { - require.Equal(t, "a501d7d7d10983673b61b7a2d3a813b36f9f0e43", newManagement().Hash.StringLE()) - require.Equal(t, "971d69c6dd10ce88e7dfffec1dc603c6125a8764", newLedger().Hash.StringLE()) - require.Equal(t, "f61eebf573ea36593fd43aa150c055ad7906ab83", newNEO().Hash.StringLE()) - require.Equal(t, "70e2301955bf1e74cbb31d18c2f96972abadb328", newGAS().Hash.StringLE()) - require.Equal(t, "79bcd398505eb779df6e67e4be6c14cded08e2f2", newPolicy().Hash.StringLE()) - require.Equal(t, "597b1471bbce497b7809e2c8f10db67050008b02", newDesignate(false).Hash.StringLE()) - require.Equal(t, "8dc0e742cbdfdeda51ff8a8b78d46829144c80ee", newOracle().Hash.StringLE()) - // Not yet a part of NEO. - //require.Equal(t, "", newNotary().Hash.StringLE()()) -} - // "C" and "O" can easily be typed by accident. func TestNamesASCII(t *testing.T) { cs := NewContracts(true) From 73a75cc27a76bb943b0404bf16cd6a0cce0e2669 Mon Sep 17 00:00:00 2001 From: Evgeniy Stratonikov Date: Fri, 5 Feb 2021 13:06:42 +0300 Subject: [PATCH 12/14] compiler: do not convert interop types on assertion --- pkg/compiler/analysis.go | 9 +++++++++ pkg/compiler/codegen.go | 7 +++++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/pkg/compiler/analysis.go b/pkg/compiler/analysis.go index bc55364d5..70df4d99f 100644 --- a/pkg/compiler/analysis.go +++ b/pkg/compiler/analysis.go @@ -284,3 +284,12 @@ func isInteropPath(s string) bool { func isNativeHelpersPath(s string) bool { return strings.HasPrefix(s, interopPrefix+"/native") } + +// canConvert returns true if type doesn't need to be converted on type assertion. +func canConvert(s string) bool { + if isInteropPath(s) { + s = s[len(interopPrefix):] + return s != "/iterator.Iterator" && s != "/storage.Context" + } + return true +} diff --git a/pkg/compiler/codegen.go b/pkg/compiler/codegen.go index 57652b405..3bb330422 100644 --- a/pkg/compiler/codegen.go +++ b/pkg/compiler/codegen.go @@ -1197,8 +1197,11 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { // not the assertion type. case *ast.TypeAssertExpr: ast.Walk(c, n.X) - typ := toNeoType(c.typeOf(n.Type)) - emit.Instruction(c.prog.BinWriter, opcode.CONVERT, []byte{byte(typ)}) + goTyp := c.typeOf(n.Type) + if canConvert(goTyp.String()) { + typ := toNeoType(goTyp) + emit.Instruction(c.prog.BinWriter, opcode.CONVERT, []byte{byte(typ)}) + } return nil } return c From 6cf40749a92cf64cc4a294a67575f2367f350049 Mon Sep 17 00:00:00 2001 From: Evgeniy Stratonikov Date: Fri, 5 Feb 2021 13:15:39 +0300 Subject: [PATCH 13/14] 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 + } +} From 18b7584cb49363447075d4486eb6dd64ec0aa0ae Mon Sep 17 00:00:00 2001 From: Evgeniy Stratonikov Date: Mon, 8 Feb 2021 13:09:28 +0300 Subject: [PATCH 14/14] compiler/interop: add Manifest to Contract struct --- pkg/compiler/native_test.go | 16 +++++ pkg/interop/native/management/contract.go | 71 +++++++++++++++++++++++ 2 files changed, 87 insertions(+) diff --git a/pkg/compiler/native_test.go b/pkg/compiler/native_test.go index 5aafd6208..d964ecfc0 100644 --- a/pkg/compiler/native_test.go +++ b/pkg/compiler/native_test.go @@ -48,6 +48,22 @@ func testPrintHash(u util.Uint160) { fmt.Println(`"`) } +func TestContractParameterTypes(t *testing.T) { + require.EqualValues(t, management.AnyType, smartcontract.AnyType) + require.EqualValues(t, management.BoolType, smartcontract.BoolType) + require.EqualValues(t, management.IntegerType, smartcontract.IntegerType) + require.EqualValues(t, management.ByteArrayType, smartcontract.ByteArrayType) + require.EqualValues(t, management.StringType, smartcontract.StringType) + require.EqualValues(t, management.Hash160Type, smartcontract.Hash160Type) + require.EqualValues(t, management.Hash256Type, smartcontract.Hash256Type) + require.EqualValues(t, management.PublicKeyType, smartcontract.PublicKeyType) + require.EqualValues(t, management.SignatureType, smartcontract.SignatureType) + require.EqualValues(t, management.ArrayType, smartcontract.ArrayType) + require.EqualValues(t, management.MapType, smartcontract.MapType) + require.EqualValues(t, management.InteropInterfaceType, smartcontract.InteropInterfaceType) + require.EqualValues(t, management.VoidType, smartcontract.VoidType) +} + func TestRoleManagementRole(t *testing.T) { require.EqualValues(t, native.RoleOracle, roles.Oracle) require.EqualValues(t, native.RoleStateValidator, roles.StateValidator) diff --git a/pkg/interop/native/management/contract.go b/pkg/interop/native/management/contract.go index 01aae529e..415cf3984 100644 --- a/pkg/interop/native/management/contract.go +++ b/pkg/interop/native/management/contract.go @@ -8,4 +8,75 @@ type Contract struct { UpdateCounter int Hash interop.Hash160 NEF []byte + Manifest Manifest +} + +// ParameterType represents smartcontract parameter type. +type ParameterType byte + +// Various parameter types. +const ( + AnyType ParameterType = 0x00 + BoolType ParameterType = 0x10 + IntegerType ParameterType = 0x11 + ByteArrayType ParameterType = 0x12 + StringType ParameterType = 0x13 + Hash160Type ParameterType = 0x14 + Hash256Type ParameterType = 0x15 + PublicKeyType ParameterType = 0x16 + SignatureType ParameterType = 0x17 + ArrayType ParameterType = 0x20 + MapType ParameterType = 0x22 + InteropInterfaceType ParameterType = 0x30 + VoidType ParameterType = 0xff +) + +// Manifest represents contract's manifest. +type Manifest struct { + Name string + Groups []Group + SupportedStandards []string + ABI ABI + Permissions []Permission + Trusts []interop.Hash160 + Extra interface{} +} + +// ABI represents contract's ABI. +type ABI struct { + Methods []Method + Events []Event +} + +// Method represents contract method. +type Method struct { + Name string + Params []Parameter + ReturnType ParameterType + Offset int + Safe bool +} + +// Event represents contract event. +type Event struct { + Name string + Params []Parameter +} + +// Parameter represents method parameter. +type Parameter struct { + Name string + Type ParameterType +} + +// Permission represents contract permission. +type Permission struct { + Contract interop.Hash160 + Methods []string +} + +// Group represents manifest group. +type Group struct { + PublicKey interop.PublicKey + Signature interop.Signature }