From e52c39ae7eae8099ef986c5613da8153785defd1 Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Thu, 23 Jul 2020 16:03:00 +0300 Subject: [PATCH 01/13] manifest: remove EntryPoint from manifest --- pkg/compiler/debug.go | 16 +++++----------- pkg/compiler/debug_test.go | 16 ++++++---------- pkg/core/dao/cacheddao_test.go | 7 ------- pkg/core/helper_test.go | 6 ------ pkg/core/interop_neo_test.go | 6 ------ pkg/core/interop_system_test.go | 14 -------------- pkg/core/native/interop.go | 7 ------- pkg/rpc/client/rpc_test.go | 8 +------- pkg/smartcontract/manifest/manifest.go | 8 +++----- pkg/smartcontract/manifest/manifest_test.go | 14 +++++++------- pkg/smartcontract/manifest/method.go | 12 ------------ 11 files changed, 22 insertions(+), 92 deletions(-) diff --git a/pkg/compiler/debug.go b/pkg/compiler/debug.go index 5c91297106..b96d60abc6 100644 --- a/pkg/compiler/debug.go +++ b/pkg/compiler/debug.go @@ -341,26 +341,21 @@ func parsePairJSON(data []byte, sep string) (string, string, error) { // Note: manifest is taken from the external source, however it can be generated ad-hoc. See #1038. func (di *DebugInfo) convertToManifest(fs smartcontract.PropertyState) (*manifest.Manifest, error) { var ( - entryPoint manifest.Method mainNamespace string err error ) for _, method := range di.Methods { if method.Name.Name == mainIdent { - entryPoint, err = method.ToManifestMethod() - if err != nil { - return nil, err - } mainNamespace = method.Name.Namespace break } } - if entryPoint.Name == "" { + if mainNamespace == "" { return nil, errors.New("no Main method was found") } methods := make([]manifest.Method, 0) for _, method := range di.Methods { - if method.Name.Name != mainIdent && method.IsExported && method.Name.Namespace == mainNamespace { + if method.IsExported && method.Name.Namespace == mainNamespace { mMethod, err := method.ToManifestMethod() if err != nil { return nil, err @@ -379,10 +374,9 @@ func (di *DebugInfo) convertToManifest(fs smartcontract.PropertyState) (*manifes result := manifest.NewManifest(di.Hash) result.Features = fs result.ABI = manifest.ABI{ - Hash: di.Hash, - EntryPoint: entryPoint, - Methods: methods, - Events: events, + Hash: di.Hash, + Methods: methods, + Events: events, } result.Permissions = []manifest.Permission{ { diff --git a/pkg/compiler/debug_test.go b/pkg/compiler/debug_test.go index 1074563185..8301c88fef 100644 --- a/pkg/compiler/debug_test.go +++ b/pkg/compiler/debug_test.go @@ -132,17 +132,14 @@ func unexportedMethod() int { return 1 } expected := &manifest.Manifest{ ABI: manifest.ABI{ Hash: hash.Hash160(buf), - EntryPoint: manifest.Method{ - Name: "main", - Parameters: []manifest.Parameter{ - { - Name: "op", - Type: smartcontract.StringType, + Methods: []manifest.Method{ + { + Name: "main", + Parameters: []manifest.Parameter{ + manifest.NewParameter("op", smartcontract.StringType), }, + ReturnType: smartcontract.BoolType, }, - ReturnType: smartcontract.BoolType, - }, - Methods: []manifest.Method{ { Name: "methodInt", Parameters: []manifest.Parameter{ @@ -214,7 +211,6 @@ func unexportedMethod() int { return 1 } } require.True(t, expected.ABI.Hash.Equals(actual.ABI.Hash)) require.ElementsMatch(t, expected.ABI.Methods, actual.ABI.Methods) - require.Equal(t, expected.ABI.EntryPoint, actual.ABI.EntryPoint) require.Equal(t, expected.ABI.Events, actual.ABI.Events) require.Equal(t, expected.Groups, actual.Groups) require.Equal(t, expected.Features, actual.Features) diff --git a/pkg/core/dao/cacheddao_test.go b/pkg/core/dao/cacheddao_test.go index 236372b2f5..b983da3638 100644 --- a/pkg/core/dao/cacheddao_test.go +++ b/pkg/core/dao/cacheddao_test.go @@ -8,7 +8,6 @@ import ( "github.com/nspcc-dev/neo-go/pkg/core/storage" "github.com/nspcc-dev/neo-go/pkg/crypto/hash" "github.com/nspcc-dev/neo-go/pkg/internal/random" - "github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -62,12 +61,6 @@ func TestCachedDaoContracts(t *testing.T) { require.NotNil(t, err) m := manifest.NewManifest(hash.Hash160(script)) - m.ABI.EntryPoint.Name = "somename" - m.ABI.EntryPoint.Parameters = []manifest.Parameter{ - manifest.NewParameter("first", smartcontract.IntegerType), - manifest.NewParameter("second", smartcontract.StringType), - } - m.ABI.EntryPoint.ReturnType = smartcontract.BoolType cs := &state.Contract{ ID: 123, diff --git a/pkg/core/helper_test.go b/pkg/core/helper_test.go index acd55d969b..27397fc1f9 100644 --- a/pkg/core/helper_test.go +++ b/pkg/core/helper_test.go @@ -229,12 +229,6 @@ func TestCreateBasicChain(t *testing.T) { script := io.NewBufBinWriter() m := manifest.NewManifest(hash.Hash160(avm)) - m.ABI.EntryPoint.Name = "Main" - m.ABI.EntryPoint.Parameters = []manifest.Parameter{ - manifest.NewParameter("method", smartcontract.StringType), - manifest.NewParameter("params", smartcontract.ArrayType), - } - m.ABI.EntryPoint.ReturnType = smartcontract.BoolType m.Features = smartcontract.HasStorage bs, err := m.MarshalJSON() require.NoError(t, err) diff --git a/pkg/core/interop_neo_test.go b/pkg/core/interop_neo_test.go index 92cd8fcf8f..ed5a98f43e 100644 --- a/pkg/core/interop_neo_test.go +++ b/pkg/core/interop_neo_test.go @@ -288,12 +288,6 @@ func createVMAndContractState(t *testing.T) (*vm.VM, *state.Contract, *interop.C v := vm.New() script := []byte("testscript") m := manifest.NewManifest(hash.Hash160(script)) - m.ABI.EntryPoint.Parameters = []manifest.Parameter{ - manifest.NewParameter("Name", smartcontract.StringType), - manifest.NewParameter("Amount", smartcontract.IntegerType), - manifest.NewParameter("Hash", smartcontract.Hash160Type), - } - m.ABI.EntryPoint.ReturnType = smartcontract.ArrayType m.Features = smartcontract.HasStorage contractState := &state.Contract{ Script: script, diff --git a/pkg/core/interop_system_test.go b/pkg/core/interop_system_test.go index 02f0b62d34..1fb13b6033 100644 --- a/pkg/core/interop_system_test.go +++ b/pkg/core/interop_system_test.go @@ -520,13 +520,6 @@ func TestContractUpdate(t *testing.T) { manifest := &manifest.Manifest{ ABI: manifest.ABI{ Hash: cs.ScriptHash(), - EntryPoint: manifest.Method{ - Name: "Main", - Parameters: []manifest.Parameter{ - manifest.NewParameter("NewParameter", smartcontract.IntegerType), - }, - ReturnType: smartcontract.StringType, - }, }, Features: smartcontract.HasStorage, } @@ -554,13 +547,6 @@ func TestContractUpdate(t *testing.T) { newManifest := manifest.Manifest{ ABI: manifest.ABI{ Hash: hash.Hash160(newScript), - EntryPoint: manifest.Method{ - Name: "Main", - Parameters: []manifest.Parameter{ - manifest.NewParameter("VeryNewParameter", smartcontract.IntegerType), - }, - ReturnType: smartcontract.StringType, - }, }, Features: smartcontract.HasStorage, } diff --git a/pkg/core/native/interop.go b/pkg/core/native/interop.go index f2e3e487a2..d28e8b6181 100644 --- a/pkg/core/native/interop.go +++ b/pkg/core/native/interop.go @@ -6,7 +6,6 @@ import ( "github.com/nspcc-dev/neo-go/pkg/core/interop" "github.com/nspcc-dev/neo-go/pkg/core/state" - "github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/vm" ) @@ -19,12 +18,6 @@ func Deploy(ic *interop.Context, _ *vm.VM) error { for _, native := range ic.Natives { md := native.Metadata() - ps := md.Manifest.ABI.EntryPoint.Parameters - params := make([]smartcontract.ParamType, len(ps)) - for i := range ps { - params[i] = ps[i].Type - } - cs := &state.Contract{ ID: md.ContractID, Script: md.Script, diff --git a/pkg/rpc/client/rpc_test.go b/pkg/rpc/client/rpc_test.go index 35e6e561cf..20e35d979e 100644 --- a/pkg/rpc/client/rpc_test.go +++ b/pkg/rpc/client/rpc_test.go @@ -298,19 +298,13 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{ } return c.GetContractState(hash) }, - serverResponse: `{"id":1,"jsonrpc":"2.0","result":{"id":0,"script":"VgJXHwIMDWNvbnRyYWN0IGNhbGx4eVMTwEEFB5IWIXhKDANQdXSXJyQAAAAQVUGEGNYNIXJqeRDOeRHOU0FSoUH1IUURQCOPAgAASgwLdG90YWxTdXBwbHmXJxEAAABFAkBCDwBAI28CAABKDAhkZWNpbWFsc5cnDQAAAEUSQCNWAgAASgwEbmFtZZcnEgAAAEUMBFJ1YmxAIzwCAABKDAZzeW1ib2yXJxEAAABFDANSVUJAIyECAABKDAliYWxhbmNlT2aXJ2IAAAAQVUGEGNYNIXN5EM50bMoAFLQnIwAAAAwPaW52YWxpZCBhZGRyZXNzEVVBNtNSBiFFENsgQGtsUEEfLnsHIXUMCWJhbGFuY2VPZmxtUxPAQQUHkhYhRW1AI7IBAABKDAh0cmFuc2ZlcpcnKwEAABBVQYQY1g0hdnkQzncHbwfKABS0JyoAAAAMFmludmFsaWQgJ2Zyb20nIGFkZHJlc3MRVUE201IGIUUQ2yBAeRHOdwhvCMoAFLQnKAAAAAwUaW52YWxpZCAndG8nIGFkZHJlc3MRVUE201IGIUUQ2yBAeRLOdwlvCRC1JyIAAAAMDmludmFsaWQgYW1vdW50EVVBNtNSBiFFENsgQG5vB1BBHy57ByF3Cm8Kbwm1JyYAAAAMEmluc3VmZmljaWVudCBmdW5kcxFVQTbTUgYhRRDbIEBvCm8Jn3cKbm8HbwpTQVKhQfUhbm8IUEEfLnsHIXcLbwtvCZ53C25vCG8LU0FSoUH1IQwIdHJhbnNmZXJvB28IbwlUFMBBBQeSFiFFEUAjewAAAEoMBGluaXSXJ1AAAAAQVUGEGNYNIXcMEFVBh8PSZCF3DQJAQg8Adw5vDG8Nbw5TQVKhQfUhDAh0cmFuc2ZlcgwA2zBvDW8OVBTAQQUHkhYhRRFAIyMAAAAMEWludmFsaWQgb3BlcmF0aW9uQTbTUgY6IwUAAABFQA==","manifest":{"abi":{"hash":"0x1b4357bff5a01bdf2a6581247cf9ed1e24629176","entryPoint":{"name":"Main","parameters":[{"name":"method","type":"String"},{"name":"params","type":"Array"}],"returntype":"Boolean"},"methods":[],"events":[]},"groups":[],"features":{"payable":false,"storage":true},"permissions":null,"trusts":[],"safemethods":[],"extra":null},"hash":"0x1b4357bff5a01bdf2a6581247cf9ed1e24629176"}}`, + serverResponse: `{"id":1,"jsonrpc":"2.0","result":{"id":0,"script":"VgJXHwIMDWNvbnRyYWN0IGNhbGx4eVMTwEEFB5IWIXhKDANQdXSXJyQAAAAQVUGEGNYNIXJqeRDOeRHOU0FSoUH1IUURQCOPAgAASgwLdG90YWxTdXBwbHmXJxEAAABFAkBCDwBAI28CAABKDAhkZWNpbWFsc5cnDQAAAEUSQCNWAgAASgwEbmFtZZcnEgAAAEUMBFJ1YmxAIzwCAABKDAZzeW1ib2yXJxEAAABFDANSVUJAIyECAABKDAliYWxhbmNlT2aXJ2IAAAAQVUGEGNYNIXN5EM50bMoAFLQnIwAAAAwPaW52YWxpZCBhZGRyZXNzEVVBNtNSBiFFENsgQGtsUEEfLnsHIXUMCWJhbGFuY2VPZmxtUxPAQQUHkhYhRW1AI7IBAABKDAh0cmFuc2ZlcpcnKwEAABBVQYQY1g0hdnkQzncHbwfKABS0JyoAAAAMFmludmFsaWQgJ2Zyb20nIGFkZHJlc3MRVUE201IGIUUQ2yBAeRHOdwhvCMoAFLQnKAAAAAwUaW52YWxpZCAndG8nIGFkZHJlc3MRVUE201IGIUUQ2yBAeRLOdwlvCRC1JyIAAAAMDmludmFsaWQgYW1vdW50EVVBNtNSBiFFENsgQG5vB1BBHy57ByF3Cm8Kbwm1JyYAAAAMEmluc3VmZmljaWVudCBmdW5kcxFVQTbTUgYhRRDbIEBvCm8Jn3cKbm8HbwpTQVKhQfUhbm8IUEEfLnsHIXcLbwtvCZ53C25vCG8LU0FSoUH1IQwIdHJhbnNmZXJvB28IbwlUFMBBBQeSFiFFEUAjewAAAEoMBGluaXSXJ1AAAAAQVUGEGNYNIXcMEFVBh8PSZCF3DQJAQg8Adw5vDG8Nbw5TQVKhQfUhDAh0cmFuc2ZlcgwA2zBvDW8OVBTAQQUHkhYhRRFAIyMAAAAMEWludmFsaWQgb3BlcmF0aW9uQTbTUgY6IwUAAABFQA==","manifest":{"abi":{"hash":"0x1b4357bff5a01bdf2a6581247cf9ed1e24629176","methods":[],"events":[]},"groups":[],"features":{"payable":false,"storage":true},"permissions":null,"trusts":[],"safemethods":[],"extra":null},"hash":"0x1b4357bff5a01bdf2a6581247cf9ed1e24629176"}}`, result: func(c *Client) interface{} { script, err := base64.StdEncoding.DecodeString("VgJXHwIMDWNvbnRyYWN0IGNhbGx4eVMTwEEFB5IWIXhKDANQdXSXJyQAAAAQVUGEGNYNIXJqeRDOeRHOU0FSoUH1IUURQCOPAgAASgwLdG90YWxTdXBwbHmXJxEAAABFAkBCDwBAI28CAABKDAhkZWNpbWFsc5cnDQAAAEUSQCNWAgAASgwEbmFtZZcnEgAAAEUMBFJ1YmxAIzwCAABKDAZzeW1ib2yXJxEAAABFDANSVUJAIyECAABKDAliYWxhbmNlT2aXJ2IAAAAQVUGEGNYNIXN5EM50bMoAFLQnIwAAAAwPaW52YWxpZCBhZGRyZXNzEVVBNtNSBiFFENsgQGtsUEEfLnsHIXUMCWJhbGFuY2VPZmxtUxPAQQUHkhYhRW1AI7IBAABKDAh0cmFuc2ZlcpcnKwEAABBVQYQY1g0hdnkQzncHbwfKABS0JyoAAAAMFmludmFsaWQgJ2Zyb20nIGFkZHJlc3MRVUE201IGIUUQ2yBAeRHOdwhvCMoAFLQnKAAAAAwUaW52YWxpZCAndG8nIGFkZHJlc3MRVUE201IGIUUQ2yBAeRLOdwlvCRC1JyIAAAAMDmludmFsaWQgYW1vdW50EVVBNtNSBiFFENsgQG5vB1BBHy57ByF3Cm8Kbwm1JyYAAAAMEmluc3VmZmljaWVudCBmdW5kcxFVQTbTUgYhRRDbIEBvCm8Jn3cKbm8HbwpTQVKhQfUhbm8IUEEfLnsHIXcLbwtvCZ53C25vCG8LU0FSoUH1IQwIdHJhbnNmZXJvB28IbwlUFMBBBQeSFiFFEUAjewAAAEoMBGluaXSXJ1AAAAAQVUGEGNYNIXcMEFVBh8PSZCF3DQJAQg8Adw5vDG8Nbw5TQVKhQfUhDAh0cmFuc2ZlcgwA2zBvDW8OVBTAQQUHkhYhRRFAIyMAAAAMEWludmFsaWQgb3BlcmF0aW9uQTbTUgY6IwUAAABFQA==") if err != nil { panic(err) } m := manifest.NewManifest(hash.Hash160(script)) - m.ABI.EntryPoint.Name = "Main" - m.ABI.EntryPoint.Parameters = []manifest.Parameter{ - manifest.NewParameter("method", smartcontract.StringType), - manifest.NewParameter("params", smartcontract.ArrayType), - } - m.ABI.EntryPoint.ReturnType = smartcontract.BoolType m.Features = smartcontract.HasStorage cs := &state.Contract{ ID: 0, diff --git a/pkg/smartcontract/manifest/manifest.go b/pkg/smartcontract/manifest/manifest.go index 1b1c1bbee0..307a6f2e63 100644 --- a/pkg/smartcontract/manifest/manifest.go +++ b/pkg/smartcontract/manifest/manifest.go @@ -13,10 +13,9 @@ const MaxManifestSize = 2048 // ABI represents a contract application binary interface. type ABI struct { - Hash util.Uint160 `json:"hash"` - EntryPoint Method `json:"entryPoint"` - Methods []Method `json:"methods"` - Events []Event `json:"events"` + Hash util.Uint160 `json:"hash"` + Methods []Method `json:"methods"` + Events []Event `json:"events"` } // Manifest represens contract metadata. @@ -65,7 +64,6 @@ func NewManifest(h util.Uint160) *Manifest { // DefaultManifest returns default contract manifest. func DefaultManifest(h util.Uint160) *Manifest { m := NewManifest(h) - m.ABI.EntryPoint = *DefaultEntryPoint() m.Permissions = []Permission{*NewPermission(PermissionWildcard)} return m } diff --git a/pkg/smartcontract/manifest/manifest_test.go b/pkg/smartcontract/manifest/manifest_test.go index c012829a43..3d26a01e35 100644 --- a/pkg/smartcontract/manifest/manifest_test.go +++ b/pkg/smartcontract/manifest/manifest_test.go @@ -13,39 +13,39 @@ import ( // https://github.com/neo-project/neo/blob/master/tests/neo.UnitTests/SmartContract/Manifest/UT_ContractManifest.cs#L10 func TestManifest_MarshalJSON(t *testing.T) { t.Run("default", func(t *testing.T) { - s := `{"groups":[],"features":{"storage":false,"payable":false},"abi":{"hash":"0x0000000000000000000000000000000000000000","entryPoint":{"name":"Main","parameters":[{"name":"operation","type":"String"},{"name":"args","type":"Array"}],"returntype":"Any"},"methods":[],"events":[]},"permissions":[{"contract":"*","methods":"*"}],"trusts":[],"safemethods":[],"extra":null}` + s := `{"groups":[],"features":{"storage":false,"payable":false},"abi":{"hash":"0x0000000000000000000000000000000000000000","methods":[],"events":[]},"permissions":[{"contract":"*","methods":"*"}],"trusts":[],"safemethods":[],"extra":null}` m := testUnmarshalMarshalManifest(t, s) require.Equal(t, DefaultManifest(util.Uint160{}), m) }) // this vector is missing from original repo t.Run("features", func(t *testing.T) { - s := `{"groups":[],"features":{"storage":true,"payable":true},"abi":{"hash":"0x0000000000000000000000000000000000000000","entryPoint":{"name":"Main","parameters":[{"name":"operation","type":"String"},{"name":"args","type":"Array"}],"returntype":"Any"},"methods":[],"events":[]},"permissions":[{"contract":"*","methods":"*"}],"trusts":[],"safemethods":[],"extra":null}` + s := `{"groups":[],"features":{"storage":true,"payable":true},"abi":{"hash":"0x0000000000000000000000000000000000000000","methods":[],"events":[]},"permissions":[{"contract":"*","methods":"*"}],"trusts":[],"safemethods":[],"extra":null}` testUnmarshalMarshalManifest(t, s) }) t.Run("permissions", func(t *testing.T) { - s := `{"groups":[],"features":{"storage":false,"payable":false},"abi":{"hash":"0x0000000000000000000000000000000000000000","entryPoint":{"name":"Main","parameters":[{"name":"operation","type":"String"},{"name":"args","type":"Array"}],"returntype":"Any"},"methods":[],"events":[]},"permissions":[{"contract":"0x0000000000000000000000000000000000000000","methods":["method1","method2"]}],"trusts":[],"safemethods":[],"extra":null}` + s := `{"groups":[],"features":{"storage":false,"payable":false},"abi":{"hash":"0x0000000000000000000000000000000000000000","methods":[],"events":[]},"permissions":[{"contract":"0x0000000000000000000000000000000000000000","methods":["method1","method2"]}],"trusts":[],"safemethods":[],"extra":null}` testUnmarshalMarshalManifest(t, s) }) t.Run("safe methods", func(t *testing.T) { - s := `{"groups":[],"features":{"storage":false,"payable":false},"abi":{"hash":"0x0000000000000000000000000000000000000000","entryPoint":{"name":"Main","parameters":[{"name":"operation","type":"String"},{"name":"args","type":"Array"}],"returntype":"Any"},"methods":[],"events":[]},"permissions":[{"contract":"*","methods":"*"}],"trusts":[],"safemethods":["balanceOf"],"extra":null}` + s := `{"groups":[],"features":{"storage":false,"payable":false},"abi":{"hash":"0x0000000000000000000000000000000000000000","methods":[],"events":[]},"permissions":[{"contract":"*","methods":"*"}],"trusts":[],"safemethods":["balanceOf"],"extra":null}` testUnmarshalMarshalManifest(t, s) }) t.Run("trust", func(t *testing.T) { - s := `{"groups":[],"features":{"storage":false,"payable":false},"abi":{"hash":"0x0000000000000000000000000000000000000000","entryPoint":{"name":"Main","parameters":[{"name":"operation","type":"String"},{"name":"args","type":"Array"}],"returntype":"Any"},"methods":[],"events":[]},"permissions":[{"contract":"*","methods":"*"}],"trusts":["0x0000000000000000000000000000000000000001"],"safemethods":[],"extra":null}` + s := `{"groups":[],"features":{"storage":false,"payable":false},"abi":{"hash":"0x0000000000000000000000000000000000000000","methods":[],"events":[]},"permissions":[{"contract":"*","methods":"*"}],"trusts":["0x0000000000000000000000000000000000000001"],"safemethods":[],"extra":null}` testUnmarshalMarshalManifest(t, s) }) t.Run("groups", func(t *testing.T) { - s := `{"groups":[{"pubkey":"03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c","signature":"QUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQQ=="}],"features":{"storage":false,"payable":false},"abi":{"hash":"0x0000000000000000000000000000000000000000","entryPoint":{"name":"Main","parameters":[{"name":"operation","type":"String"},{"name":"args","type":"Array"}],"returntype":"Any"},"methods":[],"events":[]},"permissions":[{"contract":"*","methods":"*"}],"trusts":[],"safemethods":[],"extra":null}` + s := `{"groups":[{"pubkey":"03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c","signature":"QUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQQ=="}],"features":{"storage":false,"payable":false},"abi":{"hash":"0x0000000000000000000000000000000000000000","methods":[],"events":[]},"permissions":[{"contract":"*","methods":"*"}],"trusts":[],"safemethods":[],"extra":null}` testUnmarshalMarshalManifest(t, s) }) t.Run("extra", func(t *testing.T) { - s := `{"groups":[],"features":{"storage":false,"payable":false},"abi":{"hash":"0x0000000000000000000000000000000000000000","entryPoint":{"name":"Main","parameters":[{"name":"operation","type":"String"},{"name":"args","type":"Array"}],"returntype":"Any"},"methods":[],"events":[]},"permissions":[{"contract":"*","methods":"*"}],"trusts":[],"safemethods":[],"extra":{"key":"value"}}` + s := `{"groups":[],"features":{"storage":false,"payable":false},"abi":{"hash":"0x0000000000000000000000000000000000000000","methods":[],"events":[]},"permissions":[{"contract":"*","methods":"*"}],"trusts":[],"safemethods":[],"extra":{"key":"value"}}` testUnmarshalMarshalManifest(t, s) }) } diff --git a/pkg/smartcontract/manifest/method.go b/pkg/smartcontract/manifest/method.go index d4ae4ecfdb..37d7fa4d54 100644 --- a/pkg/smartcontract/manifest/method.go +++ b/pkg/smartcontract/manifest/method.go @@ -50,18 +50,6 @@ func NewParameter(name string, typ smartcontract.ParamType) Parameter { } } -// DefaultEntryPoint represents default entrypoint to a contract. -func DefaultEntryPoint() *Method { - return &Method{ - Name: "Main", - Parameters: []Parameter{ - NewParameter("operation", smartcontract.StringType), - NewParameter("args", smartcontract.ArrayType), - }, - ReturnType: smartcontract.AnyType, - } -} - // IsValid checks whether group's signature corresponds to the given hash. func (g *Group) IsValid(h util.Uint160) bool { return g.PublicKey.Verify(g.Signature, hash.Sha256(h.BytesBE()).BytesBE()) From a892e3ffa80e20f54d7756ea0d15163c4e0c5bb4 Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Thu, 23 Jul 2020 16:12:41 +0300 Subject: [PATCH 02/13] manifest: add Offset in method descriptor --- pkg/compiler/debug.go | 1 + pkg/compiler/debug_test.go | 14 +++++++++++--- pkg/smartcontract/manifest/method.go | 1 + 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/pkg/compiler/debug.go b/pkg/compiler/debug.go index b96d60abc6..352ad0b85a 100644 --- a/pkg/compiler/debug.go +++ b/pkg/compiler/debug.go @@ -270,6 +270,7 @@ func (m *MethodDebugInfo) ToManifestMethod() (manifest.Method, error) { return result, err } result.Name = strings.ToLower(string(m.Name.Name[0])) + m.Name.Name[1:] + result.Offset = int(m.Range.Start) result.Parameters = parameters result.ReturnType = returnType return result, nil diff --git a/pkg/compiler/debug_test.go b/pkg/compiler/debug_test.go index 8301c88fef..f76f09cdef 100644 --- a/pkg/compiler/debug_test.go +++ b/pkg/compiler/debug_test.go @@ -129,19 +129,22 @@ func unexportedMethod() int { return 1 } t.Run("convert to Manifest", func(t *testing.T) { actual, err := d.convertToManifest(smartcontract.HasStorage) require.NoError(t, err) + // note: offsets are hard to predict, so we just take them from the output expected := &manifest.Manifest{ ABI: manifest.ABI{ Hash: hash.Hash160(buf), Methods: []manifest.Method{ { - Name: "main", + Name: "main", + Offset: 0, Parameters: []manifest.Parameter{ manifest.NewParameter("op", smartcontract.StringType), }, ReturnType: smartcontract.BoolType, }, { - Name: "methodInt", + Name: "methodInt", + Offset: 66, Parameters: []manifest.Parameter{ { Name: "a", @@ -152,26 +155,31 @@ func unexportedMethod() int { return 1 } }, { Name: "methodString", + Offset: 97, Parameters: []manifest.Parameter{}, ReturnType: smartcontract.StringType, }, { Name: "methodByteArray", + Offset: 103, Parameters: []manifest.Parameter{}, ReturnType: smartcontract.ByteArrayType, }, { Name: "methodArray", + Offset: 108, Parameters: []manifest.Parameter{}, ReturnType: smartcontract.ArrayType, }, { Name: "methodStruct", + Offset: 113, Parameters: []manifest.Parameter{}, ReturnType: smartcontract.ArrayType, }, { - Name: "methodConcat", + Name: "methodConcat", + Offset: 88, Parameters: []manifest.Parameter{ { Name: "a", diff --git a/pkg/smartcontract/manifest/method.go b/pkg/smartcontract/manifest/method.go index 37d7fa4d54..30ad923f0e 100644 --- a/pkg/smartcontract/manifest/method.go +++ b/pkg/smartcontract/manifest/method.go @@ -38,6 +38,7 @@ type groupAux struct { // Method represents method's metadata. type Method struct { Name string `json:"name"` + Offset int `json:"offset"` Parameters []Parameter `json:"parameters"` ReturnType smartcontract.ParamType `json:"returntype"` } From 7d8fead1fd49843cb2dd6fa444387f4e29b9d838 Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Thu, 23 Jul 2020 16:55:40 +0300 Subject: [PATCH 03/13] native: change onPersist return type to Void --- pkg/core/native/native_nep5.go | 2 +- pkg/core/native/policy.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/core/native/native_nep5.go b/pkg/core/native/native_nep5.go index 308d199e30..f7dc87aed4 100644 --- a/pkg/core/native/native_nep5.go +++ b/pkg/core/native/native_nep5.go @@ -78,7 +78,7 @@ func newNEP5Native(name string) *nep5TokenNative { md = newMethodAndPrice(n.Transfer, 8000000, smartcontract.AllowModifyStates) n.AddMethod(md, desc, false) - desc = newDescriptor("onPersist", smartcontract.BoolType) + desc = newDescriptor("onPersist", smartcontract.VoidType) md = newMethodAndPrice(getOnPersistWrapper(n.OnPersist), 0, smartcontract.AllowModifyStates) n.AddMethod(md, desc, false) diff --git a/pkg/core/native/policy.go b/pkg/core/native/policy.go index 87c198c6d9..e80dc32648 100644 --- a/pkg/core/native/policy.go +++ b/pkg/core/native/policy.go @@ -105,7 +105,7 @@ func newPolicy() *Policy { md = newMethodAndPrice(p.unblockAccount, 3000000, smartcontract.NoneFlag) p.AddMethod(md, desc, false) - desc = newDescriptor("onPersist", smartcontract.BoolType) + desc = newDescriptor("onPersist", smartcontract.VoidType) md = newMethodAndPrice(getOnPersistWrapper(p.OnPersist), 0, smartcontract.AllowModifyStates) p.AddMethod(md, desc, false) return p From 04bf357fa567df0cbd3d5f9603dce201da32a6f3 Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Thu, 23 Jul 2020 17:01:51 +0300 Subject: [PATCH 04/13] compiler: make DebugInfo.convertToManifest public Allow to generate manifest when using compiler as a library. --- pkg/compiler/compiler.go | 2 +- pkg/compiler/debug.go | 4 ++-- pkg/compiler/debug_test.go | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/compiler/compiler.go b/pkg/compiler/compiler.go index 5191d3fce8..4c16af02e9 100644 --- a/pkg/compiler/compiler.go +++ b/pkg/compiler/compiler.go @@ -134,7 +134,7 @@ func CompileAndSave(src string, o *Options) ([]byte, error) { } if o.ManifestFile != "" { - m, err := di.convertToManifest(o.ContractFeatures) + m, err := di.ConvertToManifest(o.ContractFeatures) if err != nil { return b, errors.Wrap(err, "failed to convert debug info to manifest") } diff --git a/pkg/compiler/debug.go b/pkg/compiler/debug.go index 352ad0b85a..ebf5a3e61b 100644 --- a/pkg/compiler/debug.go +++ b/pkg/compiler/debug.go @@ -338,9 +338,9 @@ func parsePairJSON(data []byte, sep string) (string, string, error) { return ss[0], ss[1], nil } -// convertToManifest converts contract to the manifest.Manifest struct for debugger. +// ConvertToManifest converts contract to the manifest.Manifest struct for debugger. // Note: manifest is taken from the external source, however it can be generated ad-hoc. See #1038. -func (di *DebugInfo) convertToManifest(fs smartcontract.PropertyState) (*manifest.Manifest, error) { +func (di *DebugInfo) ConvertToManifest(fs smartcontract.PropertyState) (*manifest.Manifest, error) { var ( mainNamespace string err error diff --git a/pkg/compiler/debug_test.go b/pkg/compiler/debug_test.go index f76f09cdef..0a7f8cbb8e 100644 --- a/pkg/compiler/debug_test.go +++ b/pkg/compiler/debug_test.go @@ -127,7 +127,7 @@ func unexportedMethod() int { return 1 } } t.Run("convert to Manifest", func(t *testing.T) { - actual, err := d.convertToManifest(smartcontract.HasStorage) + actual, err := d.ConvertToManifest(smartcontract.HasStorage) require.NoError(t, err) // note: offsets are hard to predict, so we just take them from the output expected := &manifest.Manifest{ From 54d7882acf5a9a95d9bcf525d8dced7fc59f7eb9 Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Thu, 23 Jul 2020 17:00:06 +0300 Subject: [PATCH 05/13] core: compiler contract on-the-fly in helper test Generate proper manifest file too. --- pkg/core/helper_test.go | 11 +++++++---- pkg/rpc/server/testdata/test_contract.avm | Bin 792 -> 0 bytes 2 files changed, 7 insertions(+), 4 deletions(-) delete mode 100755 pkg/rpc/server/testdata/test_contract.avm diff --git a/pkg/core/helper_test.go b/pkg/core/helper_test.go index 27397fc1f9..a32ec6c8bd 100644 --- a/pkg/core/helper_test.go +++ b/pkg/core/helper_test.go @@ -1,6 +1,7 @@ package core import ( + "bytes" "encoding/hex" "encoding/json" "fmt" @@ -10,6 +11,7 @@ import ( "testing" "time" + "github.com/nspcc-dev/neo-go/pkg/compiler" "github.com/nspcc-dev/neo-go/pkg/config" "github.com/nspcc-dev/neo-go/pkg/core/block" "github.com/nspcc-dev/neo-go/pkg/core/native" @@ -20,7 +22,6 @@ import ( "github.com/nspcc-dev/neo-go/pkg/internal/testserdes" "github.com/nspcc-dev/neo-go/pkg/io" "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/emit" "github.com/nspcc-dev/neo-go/pkg/vm/opcode" @@ -223,13 +224,15 @@ func TestCreateBasicChain(t *testing.T) { require.NoError(t, err) // Push some contract into the chain. - avm, err := ioutil.ReadFile(prefix + "test_contract.avm") + c, err := ioutil.ReadFile(prefix + "test_contract.go") + require.NoError(t, err) + avm, di, err := compiler.CompileWithDebugInfo(bytes.NewReader(c)) require.NoError(t, err) t.Logf("contractHash: %s", hash.Hash160(avm).StringLE()) script := io.NewBufBinWriter() - m := manifest.NewManifest(hash.Hash160(avm)) - m.Features = smartcontract.HasStorage + m, err := di.ConvertToManifest(smartcontract.HasStorage) + require.NoError(t, err) bs, err := m.MarshalJSON() require.NoError(t, err) emit.Bytes(script.BinWriter, bs) diff --git a/pkg/rpc/server/testdata/test_contract.avm b/pkg/rpc/server/testdata/test_contract.avm deleted file mode 100755 index dbb93433899a0a48093d0625d5a48d3720a8c5c8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 792 zcmZ`%F>ljA7*zqNGLWLM(9C3{8%7R_VLW+cl8bK$7E{+XO&-VrUTqn%@0t9$K zf)ydL(VZEIuvYR8`~pVAotrd`D9cHA?|bjwd-@($4!af9@PP4{c)a5gN~g1a`%B+> zy#d6W_RwZO$$+qPbnK$Vfm&v9EmqUu39q_*|EM6M*WXb5zn zhBgl#?AzAk64Hi*66X2$gB)k)2A`Gw;&Jb^yY=-()isoz&)5_&+78(dB>=3JX0{4Ou8w@SeDw)te!w`166AS)*L8 mtO*Dj(ELXcDaCFHtchAOS8Le}O#GPe5Lj8xZEH)QuKgDli}cR` From e87eba51f9cd9780b878ca73441a32a2bfa33684 Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Thu, 23 Jul 2020 17:40:49 +0300 Subject: [PATCH 06/13] compiler: emit bytecode for unused exported functions Exported functions should always be present in byte-code. This will be needed later when arbitrary method calls are allowed. --- pkg/compiler/analysis.go | 8 +++++++- pkg/compiler/codegen.go | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/pkg/compiler/analysis.go b/pkg/compiler/analysis.go index 41526aba09..34b648656a 100644 --- a/pkg/compiler/analysis.go +++ b/pkg/compiler/analysis.go @@ -113,10 +113,11 @@ func lastStmtIsReturn(decl *ast.FuncDecl) (b bool) { return false } -func analyzeFuncUsage(pkgs map[*types.Package]*loader.PackageInfo) funcUsage { +func analyzeFuncUsage(mainPkg *loader.PackageInfo, pkgs map[*types.Package]*loader.PackageInfo) funcUsage { usage := funcUsage{} for _, pkg := range pkgs { + isMain := pkg == mainPkg for _, f := range pkg.Files { ast.Inspect(f, func(node ast.Node) bool { switch n := node.(type) { @@ -127,6 +128,11 @@ func analyzeFuncUsage(pkgs map[*types.Package]*loader.PackageInfo) funcUsage { case *ast.SelectorExpr: usage[t.Sel.Name] = true } + case *ast.FuncDecl: + // exported functions are always assumed to be used + if isMain && n.Name.IsExported() { + usage[n.Name.Name] = true + } } return true }) diff --git a/pkg/compiler/codegen.go b/pkg/compiler/codegen.go index 1e91e3bb8b..25d9a6e62a 100644 --- a/pkg/compiler/codegen.go +++ b/pkg/compiler/codegen.go @@ -1426,7 +1426,7 @@ func (c *codegen) compile(info *buildInfo, pkg *loader.PackageInfo) error { return c.prog.Err } - funUsage := analyzeFuncUsage(info.program.AllPackages) + funUsage := analyzeFuncUsage(pkg, info.program.AllPackages) // Bring all imported functions into scope. for _, pkg := range info.program.AllPackages { From d2ddf7b7cb031daa502f8a830f9e5b5be70764fc Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Thu, 23 Jul 2020 18:13:02 +0300 Subject: [PATCH 07/13] *: support invoking methods by offset Allow to invoke methods by offset: 1. Every invoked contract must have manifest. 2. Check arguments count on invocation. 3. Change AppCall to a regular syscall. 4. Add test suite for `System.Contract.Call`. --- pkg/compiler/analysis.go | 1 - pkg/compiler/codegen.go | 7 - pkg/compiler/interop_test.go | 27 ++-- pkg/compiler/syscall.go | 3 + pkg/core/helper_test.go | 2 +- pkg/core/interop_system.go | 38 +++++- pkg/core/interop_system_test.go | 104 +++++++++++++++ pkg/core/native_contract_test.go | 5 +- pkg/interop/engine/engine.go | 2 +- pkg/rpc/server/server_test.go | 4 +- pkg/rpc/server/testdata/test_contract.go | 156 ++++++++++++----------- pkg/rpc/server/testdata/testblocks.acc | Bin 6846 -> 7315 bytes pkg/smartcontract/manifest/manifest.go | 10 ++ pkg/vm/vm.go | 18 +-- 14 files changed, 266 insertions(+), 111 deletions(-) diff --git a/pkg/compiler/analysis.go b/pkg/compiler/analysis.go index 34b648656a..621825a61f 100644 --- a/pkg/compiler/analysis.go +++ b/pkg/compiler/analysis.go @@ -16,7 +16,6 @@ var ( goBuiltins = []string{"len", "append", "panic"} // Custom builtin utility functions. customBuiltins = []string{ - "AppCall", "FromAddress", "Equals", "ToBool", "ToByteArray", "ToInteger", } diff --git a/pkg/compiler/codegen.go b/pkg/compiler/codegen.go index 25d9a6e62a..2dbcbc9005 100644 --- a/pkg/compiler/codegen.go +++ b/pkg/compiler/codegen.go @@ -1220,13 +1220,6 @@ func (c *codegen) convertBuiltin(expr *ast.CallExpr) { typ = stackitem.BooleanT } c.emitConvert(typ) - case "AppCall": - c.emitReverse(len(expr.Args)) - buf := c.getByteArray(expr.Args[0]) - if buf != nil && len(buf) != 20 { - c.prog.Err = errors.New("invalid script hash") - } - emit.Syscall(c.prog.BinWriter, "System.Contract.Call") case "Equals": emit.Opcode(c.prog.BinWriter, opcode.EQUAL) case "FromAddress": diff --git a/pkg/compiler/interop_test.go b/pkg/compiler/interop_test.go index 6a8308924e..8f73266371 100644 --- a/pkg/compiler/interop_test.go +++ b/pkg/compiler/interop_test.go @@ -74,17 +74,25 @@ func TestAppCall(t *testing.T) { srcInner := ` package foo func Main(a []byte, b []byte) []byte { + panic("Main was called") + } + func Append(a []byte, b []byte) []byte { return append(a, b...) } ` - inner, err := compiler.Compile(strings.NewReader(srcInner)) + inner, di, err := compiler.CompileWithDebugInfo(strings.NewReader(srcInner)) + require.NoError(t, err) + m, err := di.ConvertToManifest(smartcontract.NoProperties) require.NoError(t, err) + ih := hash.Hash160(inner) ic := interop.NewContext(trigger.Application, nil, dao.NewSimple(storage.NewMemoryStore(), netmode.UnitTestNet), nil, nil, nil, zaptest.NewLogger(t)) - require.NoError(t, ic.DAO.PutContractState(&state.Contract{Script: inner})) + require.NoError(t, ic.DAO.PutContractState(&state.Contract{ + Script: inner, + Manifest: *m, + })) - ih := hash.Hash160(inner) t.Run("valid script", func(t *testing.T) { src := getAppCallScript(fmt.Sprintf("%#v", ih.BytesBE())) v := spawnVM(t, ic, src) @@ -102,13 +110,6 @@ func TestAppCall(t *testing.T) { require.Error(t, v.Run()) }) - t.Run("invalid script address", func(t *testing.T) { - src := getAppCallScript("[]byte{1, 2, 3}") - - _, err := compiler.Compile(strings.NewReader(src)) - require.Error(t, err) - }) - t.Run("convert from string constant", func(t *testing.T) { src := ` package foo @@ -117,7 +118,7 @@ func TestAppCall(t *testing.T) { func Main() []byte { x := []byte{1, 2} y := []byte{3, 4} - result := engine.AppCall([]byte(scriptHash), x, y) + result := engine.AppCall([]byte(scriptHash), "append", x, y) return result.([]byte) } ` @@ -136,7 +137,7 @@ func TestAppCall(t *testing.T) { x := []byte{1, 2} y := []byte{3, 4} var addr = []byte(` + fmt.Sprintf("%#v", string(ih.BytesBE())) + `) - result := engine.AppCall(addr, x, y) + result := engine.AppCall(addr, "append", x, y) return result.([]byte) } ` @@ -155,7 +156,7 @@ func getAppCallScript(h string) string { func Main() []byte { x := []byte{1, 2} y := []byte{3, 4} - result := engine.AppCall(` + h + `, x, y) + result := engine.AppCall(` + h + `, "append", x, y) return result.([]byte) } ` diff --git a/pkg/compiler/syscall.go b/pkg/compiler/syscall.go index aa46aafe1a..9f643bdd8b 100644 --- a/pkg/compiler/syscall.go +++ b/pkg/compiler/syscall.go @@ -44,6 +44,9 @@ var syscalls = map[string]map[string]Syscall{ "Next": {"System.Enumerator.Next", false}, "Value": {"System.Enumerator.Value", false}, }, + "engine": { + "AppCall": {"System.Contract.Call", false}, + }, "iterator": { "Concat": {"System.Iterator.Concat", false}, "Create": {"System.Iterator.Create", false}, diff --git a/pkg/core/helper_test.go b/pkg/core/helper_test.go index a32ec6c8bd..a58d76b1a4 100644 --- a/pkg/core/helper_test.go +++ b/pkg/core/helper_test.go @@ -252,7 +252,7 @@ func TestCreateBasicChain(t *testing.T) { // Now invoke this contract. script = io.NewBufBinWriter() - emit.AppCallWithOperationAndArgs(script.BinWriter, hash.Hash160(avm), "Put", "testkey", "testvalue") + emit.AppCallWithOperationAndArgs(script.BinWriter, hash.Hash160(avm), "putValue", "testkey", "testvalue") txInv := transaction.New(testchain.Network(), script.Bytes(), 1*native.GASFactor) txInv.Nonce = getNextNonce() diff --git a/pkg/core/interop_system.go b/pkg/core/interop_system.go index 7ac8feb3f7..698d391a00 100644 --- a/pkg/core/interop_system.go +++ b/pkg/core/interop_system.go @@ -6,6 +6,7 @@ import ( "fmt" "math" "math/big" + "strings" "unicode/utf8" "github.com/nspcc-dev/neo-go/pkg/core/block" @@ -492,16 +493,49 @@ func contractCallExInternal(ic *interop.Context, v *vm.VM, h []byte, method stac if err != nil { return err } + name := string(bs) + 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(v.GetCurrentScriptHash()) if err == nil { if !curr.Manifest.CanCall(&cs.Manifest, string(bs)) { return errors.New("disallowed method call") } } + + arr, ok := args.Value().([]stackitem.Item) + if !ok { + return errors.New("second argument must be an array") + } + if len(arr) != len(md.Parameters) { + return fmt.Errorf("invalid argument count: %d (expected %d)", len(arr), len(md.Parameters)) + } + ic.Invocations[u]++ v.LoadScriptWithHash(cs.Script, u, v.Context().GetCallFlags()&f) - v.Estack().PushVal(args) - v.Estack().PushVal(method) + var isNative bool + for i := range ic.Natives { + if ic.Natives[i].Metadata().Hash.Equals(u) { + isNative = true + break + } + } + if isNative { + v.Estack().PushVal(args) + v.Estack().PushVal(method) + } else { + for i := len(arr) - 1; i >= 0; i-- { + v.Estack().PushVal(arr[i]) + } + // use Jump not Call here because context was loaded in LoadScript above. + v.Jump(v.Context(), md.Offset) + } + return nil } diff --git a/pkg/core/interop_system_test.go b/pkg/core/interop_system_test.go index 1fb13b6033..941e51529d 100644 --- a/pkg/core/interop_system_test.go +++ b/pkg/core/interop_system_test.go @@ -14,6 +14,7 @@ import ( "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/opcode" "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" "github.com/stretchr/testify/require" @@ -323,6 +324,109 @@ func TestBlockchainGetContractState(t *testing.T) { }) } +func getTestContractState() *state.Contract { + script := []byte{ + byte(opcode.ABORT), // abort if no offset was provided + byte(opcode.ADD), byte(opcode.RET), + byte(opcode.PUSH7), byte(opcode.RET), + } + h := hash.Hash160(script) + m := manifest.NewManifest(h) + m.ABI.Methods = []manifest.Method{ + { + Name: "add", + Offset: 1, + Parameters: []manifest.Parameter{ + manifest.NewParameter("addend1", smartcontract.IntegerType), + manifest.NewParameter("addend2", smartcontract.IntegerType), + }, + ReturnType: smartcontract.IntegerType, + }, + { + Name: "ret7", + Offset: 3, + Parameters: []manifest.Parameter{}, + ReturnType: smartcontract.IntegerType, + }, + } + return &state.Contract{ + Script: script, + Manifest: *m, + ID: 42, + } +} + +func TestContractCall(t *testing.T) { + v, ic, bc := createVM(t) + defer bc.Close() + + cs := getTestContractState() + require.NoError(t, ic.DAO.PutContractState(cs)) + + currScript := []byte{byte(opcode.NOP)} + initVM := func(v *vm.VM) { + v.Istack().Clear() + v.Estack().Clear() + v.Load(currScript) + v.Estack().PushVal(42) // canary + } + + h := cs.Manifest.ABI.Hash + m := manifest.NewManifest(hash.Hash160(currScript)) + perm := manifest.NewPermission(manifest.PermissionHash, h) + perm.Methods.Add("add") + m.Permissions = append(m.Permissions, *perm) + + require.NoError(t, ic.DAO.PutContractState(&state.Contract{ + Script: currScript, + Manifest: *m, + ID: 123, + })) + + addArgs := stackitem.NewArray([]stackitem.Item{stackitem.Make(1), stackitem.Make(2)}) + t.Run("Good", func(t *testing.T) { + initVM(v) + v.Estack().PushVal(addArgs) + v.Estack().PushVal("add") + v.Estack().PushVal(h.BytesBE()) + require.NoError(t, contractCall(ic, v)) + require.NoError(t, v.Run()) + require.Equal(t, 2, v.Estack().Len()) + require.Equal(t, big.NewInt(3), v.Estack().Pop().Value()) + require.Equal(t, big.NewInt(42), v.Estack().Pop().Value()) + }) + + t.Run("CallExInvalidFlag", func(t *testing.T) { + initVM(v) + v.Estack().PushVal(byte(0xFF)) + v.Estack().PushVal(addArgs) + v.Estack().PushVal("add") + v.Estack().PushVal(h.BytesBE()) + require.Error(t, contractCallEx(ic, v)) + }) + + runInvalid := func(args ...interface{}) func(t *testing.T) { + return func(t *testing.T) { + initVM(v) + for i := range args { + v.Estack().PushVal(args[i]) + } + require.Error(t, contractCall(ic, v)) + } + } + + t.Run("Invalid", func(t *testing.T) { + t.Run("Hash", runInvalid(addArgs, "add", h.BytesBE()[1:])) + t.Run("MissingHash", runInvalid(addArgs, "add", util.Uint160{}.BytesBE())) + t.Run("Method", runInvalid(addArgs, stackitem.NewInterop("add"), h.BytesBE())) + t.Run("MissingMethod", runInvalid(addArgs, "sub", h.BytesBE())) + t.Run("DisallowedMethod", runInvalid(stackitem.NewArray(nil), "ret7", h.BytesBE())) + t.Run("Arguments", runInvalid(1, "add", h.BytesBE())) + t.Run("NotEnoughArguments", runInvalid( + stackitem.NewArray([]stackitem.Item{stackitem.Make(1)}), "add", h.BytesBE())) + }) +} + func TestContractCreate(t *testing.T) { v, cs, ic, bc := createVMAndContractState(t) v.GasLimit = -1 diff --git a/pkg/core/native_contract_test.go b/pkg/core/native_contract_test.go index 929413a1f2..c155abab64 100644 --- a/pkg/core/native_contract_test.go +++ b/pkg/core/native_contract_test.go @@ -95,7 +95,10 @@ func TestNativeContract_Invoke(t *testing.T) { tn := newTestNative() chain.registerNative(tn) - err := chain.dao.PutContractState(&state.Contract{Script: tn.meta.Script}) + err := chain.dao.PutContractState(&state.Contract{ + Script: tn.meta.Script, + Manifest: tn.meta.Manifest, + }) require.NoError(t, err) w := io.NewBufBinWriter() diff --git a/pkg/interop/engine/engine.go b/pkg/interop/engine/engine.go index d126a5b99d..480e644d6f 100644 --- a/pkg/interop/engine/engine.go +++ b/pkg/interop/engine/engine.go @@ -13,6 +13,6 @@ package engine // dynamic calls in Neo (contracts should have a special property declared // and paid for to be able to use dynamic calls). This function uses // `System.Contract.Call` syscall. -func AppCall(scriptHash []byte, args ...interface{}) interface{} { +func AppCall(scriptHash []byte, method string, args ...interface{}) interface{} { return nil } diff --git a/pkg/rpc/server/server_test.go b/pkg/rpc/server/server_test.go index 6c1b2f8467..03d5ee7ec0 100644 --- a/pkg/rpc/server/server_test.go +++ b/pkg/rpc/server/server_test.go @@ -51,8 +51,8 @@ type rpcTestCase struct { check func(t *testing.T, e *executor, result interface{}) } -const testContractHash = "402da558b87b5e54b59dc242c788bb4dd4cd906c" -const deploymentTxHash = "2afd69cc80ebe900a060450e8628b57063f3ec93ca5fc7f94582be4a4f3a041f" +const testContractHash = "6e2d823c81589871590653a100c7e9bdf9c94344" +const deploymentTxHash = "3b434127495a6dd0e786a2e0f04696009cd6e6e5f9b930f0e79356638532096c" var rpcTestCases = map[string][]rpcTestCase{ "getapplicationlog": { diff --git a/pkg/rpc/server/testdata/test_contract.go b/pkg/rpc/server/testdata/test_contract.go index af08ea213d..c7da44f4c9 100644 --- a/pkg/rpc/server/testdata/test_contract.go +++ b/pkg/rpc/server/testdata/test_contract.go @@ -11,83 +11,91 @@ const ( ) func Main(operation string, args []interface{}) interface{} { - runtime.Notify("contract call", operation, args) - switch operation { - case "Put": - ctx := storage.GetContext() - storage.Put(ctx, args[0].([]byte), args[1].([]byte)) - return true - case "totalSupply": - return totalSupply - case "decimals": - return decimals - case "name": - return "Rubl" - case "symbol": - return "RUB" - case "balanceOf": - ctx := storage.GetContext() - addr := args[0].([]byte) - if len(addr) != 20 { - runtime.Log("invalid address") - return false - } - var amount int - val := storage.Get(ctx, addr) - if val != nil { - amount = val.(int) - } - runtime.Notify("balanceOf", addr, amount) - return amount - case "transfer": - ctx := storage.GetContext() - from := args[0].([]byte) - if len(from) != 20 { - runtime.Log("invalid 'from' address") - return false - } - to := args[1].([]byte) - if len(to) != 20 { - runtime.Log("invalid 'to' address") - return false - } - amount := args[2].(int) - if amount < 0 { - runtime.Log("invalid amount") - return false - } + panic("invoking via Main is no longer supported") // catch possible bugs +} + +func Init() bool { + ctx := storage.GetContext() + h := runtime.GetExecutingScriptHash() + amount := totalSupply + storage.Put(ctx, h, amount) + runtime.Notify("transfer", []byte{}, h, amount) + return true +} + +func Transfer(from, to []byte, amount int) bool { + ctx := storage.GetContext() + if len(from) != 20 { + runtime.Log("invalid 'from' address") + return false + } + if len(to) != 20 { + runtime.Log("invalid 'to' address") + return false + } + if amount < 0 { + runtime.Log("invalid amount") + return false + } + + var fromBalance int + val := storage.Get(ctx, from) + if val != nil { + fromBalance = val.(int) + } + if fromBalance < amount { + runtime.Log("insufficient funds") + return false + } + fromBalance -= amount + storage.Put(ctx, from, fromBalance) - var fromBalance int - val := storage.Get(ctx, from) - if val != nil { - fromBalance = val.(int) - } - if fromBalance < amount { - runtime.Log("insufficient funds") - return false - } - fromBalance -= amount - storage.Put(ctx, from, fromBalance) + var toBalance int + val = storage.Get(ctx, to) + if val != nil { + toBalance = val.(int) + } + toBalance += amount + storage.Put(ctx, to, toBalance) - var toBalance int - val = storage.Get(ctx, to) - if val != nil { - toBalance = val.(int) - } - toBalance += amount - storage.Put(ctx, to, toBalance) + runtime.Notify("transfer", from, to, amount) - runtime.Notify("transfer", from, to, amount) + return true +} - return true - case "init": - ctx := storage.GetContext() - h := runtime.GetExecutingScriptHash() - amount := totalSupply - storage.Put(ctx, h, amount) - runtime.Notify("transfer", []byte{}, h, amount) - return true - default: - panic("invalid operation") +func BalanceOf(addr []byte) int { + ctx := storage.GetContext() + if len(addr) != 20 { + runtime.Log("invalid address") + return 0 } + var amount int + val := storage.Get(ctx, addr) + if val != nil { + amount = val.(int) + } + runtime.Notify("balanceOf", addr, amount) + return amount +} + +func Name() string { + return "Rubl" +} + +func Symbol() string { + return "RUB" +} + +func Decimals() int { + return decimals +} + +func TotalSupply() int { + return totalSupply +} + +func PutValue(key []byte, value []byte) bool { + ctx := storage.GetContext() + storage.Put(ctx, key, value) + return true } diff --git a/pkg/rpc/server/testdata/testblocks.acc b/pkg/rpc/server/testdata/testblocks.acc index f068d8f477da070189062f2b2b90188601b04ca9..e53450d08b61b176b5336b11d1ff9b754e092a79 100644 GIT binary patch delta 4071 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 delta 3553 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 diff --git a/pkg/smartcontract/manifest/manifest.go b/pkg/smartcontract/manifest/manifest.go index 307a6f2e63..d733c90c98 100644 --- a/pkg/smartcontract/manifest/manifest.go +++ b/pkg/smartcontract/manifest/manifest.go @@ -68,6 +68,16 @@ func DefaultManifest(h util.Uint160) *Manifest { return m } +// GetMethod returns methods with the specified name. +func (a *ABI) GetMethod(name string) *Method { + for i := range a.Methods { + if a.Methods[i].Name == name { + return &a.Methods[i] + } + } + return nil +} + // CanCall returns true is current contract is allowed to call // method of another contract. func (m *Manifest) CanCall(toCall *Manifest, method string) bool { diff --git a/pkg/vm/vm.go b/pkg/vm/vm.go index ea124a7958..d5ef55f7fc 100644 --- a/pkg/vm/vm.go +++ b/pkg/vm/vm.go @@ -1232,7 +1232,7 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro } if cond { - v.jump(ctx, offset) + v.Jump(ctx, offset) } case opcode.CALL, opcode.CALLL: @@ -1243,7 +1243,7 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro v.istack.PushVal(newCtx) offset := v.getJumpOffset(newCtx, parameter) - v.jump(newCtx, offset) + v.Jump(newCtx, offset) case opcode.CALLA: ptr := v.estack.Pop().Item().(*stackitem.Pointer) @@ -1255,7 +1255,7 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro newCtx.local = nil newCtx.arguments = nil v.istack.PushVal(newCtx) - v.jump(newCtx, ptr.Position()) + v.Jump(newCtx, ptr.Position()) case opcode.SYSCALL: interopID := GetInteropID(parameter) @@ -1404,7 +1404,7 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro } else { ctx.tryStack.Pop() } - v.jump(ctx, eOffset) + v.Jump(ctx, eOffset) case opcode.ENDFINALLY: if v.uncaughtException != nil { @@ -1412,7 +1412,7 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro return } eCtx := ctx.tryStack.Pop().Value().(*exceptionHandlingContext) - v.jump(ctx, eCtx.EndOffset) + v.Jump(ctx, eCtx.EndOffset) default: panic(fmt.Sprintf("unknown opcode %s", op.String())) @@ -1468,8 +1468,8 @@ func (v *VM) throw(item stackitem.Item) { v.handleException() } -// jump performs jump to the offset. -func (v *VM) jump(ctx *Context, offset int) { +// Jump performs jump to the offset. +func (v *VM) Jump(ctx *Context, offset int) { ctx.nextip = offset } @@ -1526,10 +1526,10 @@ func (v *VM) handleException() { ectx.State = eCatch v.estack.PushVal(v.uncaughtException) v.uncaughtException = nil - v.jump(ictx, ectx.CatchOffset) + v.Jump(ictx, ectx.CatchOffset) } else { ectx.State = eFinally - v.jump(ictx, ectx.FinallyOffset) + v.Jump(ictx, ectx.FinallyOffset) } return } From 28e0661f82046d043e9dec6ddce8df3db7764595 Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Fri, 24 Jul 2020 10:46:21 +0300 Subject: [PATCH 08/13] interop: update AppCall comment Do not require contract hash to be known at compile time. --- pkg/interop/engine/engine.go | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/pkg/interop/engine/engine.go b/pkg/interop/engine/engine.go index 480e644d6f..5cd16ac1ef 100644 --- a/pkg/interop/engine/engine.go +++ b/pkg/interop/engine/engine.go @@ -7,11 +7,7 @@ package engine // AppCall executes previously deployed blockchain contract with specified hash // (160 bit in BE form represented as 20-byte slice) using provided arguments. -// It returns whatever this contract returns. Even though this function accepts -// a slice for scriptHash you can only use it for contracts known at -// compile time, because there is a significant difference between static and -// dynamic calls in Neo (contracts should have a special property declared -// and paid for to be able to use dynamic calls). This function uses +// It returns whatever this contract returns. This function uses // `System.Contract.Call` syscall. func AppCall(scriptHash []byte, method string, args ...interface{}) interface{} { return nil From 6ecd1ae437f94860bca9282ab6b5a186dc69c882 Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Fri, 24 Jul 2020 11:54:12 +0300 Subject: [PATCH 09/13] vm: allow to initialize static slot in method --- pkg/vm/json_test.go | 2 +- pkg/vm/slot.go | 30 ++++++++++++++++++++++-------- pkg/vm/slot_test.go | 5 ++++- pkg/vm/vm.go | 6 ++---- pkg/vm/vm_test.go | 8 ++++++++ 5 files changed, 37 insertions(+), 14 deletions(-) diff --git a/pkg/vm/json_test.go b/pkg/vm/json_test.go index 7a915a64a6..e97328362f 100644 --- a/pkg/vm/json_test.go +++ b/pkg/vm/json_test.go @@ -228,7 +228,7 @@ func compareStacks(t *testing.T, expected []vmUTStackItem, actual *Stack) { } func compareSlots(t *testing.T, expected []vmUTStackItem, actual *Slot) { - if actual == nil && len(expected) == 0 { + if actual.storage == nil && len(expected) == 0 { return } require.NotNil(t, actual) diff --git a/pkg/vm/slot.go b/pkg/vm/slot.go index 18a65ac856..22e4d8d5cd 100644 --- a/pkg/vm/slot.go +++ b/pkg/vm/slot.go @@ -10,16 +10,25 @@ type Slot struct { refs *refCounter } -// newSlot returns new slot of n items. -func newSlot(n int, refs *refCounter) *Slot { +// newSlot returns new slot with the provided reference counter. +func newSlot(refs *refCounter) *Slot { return &Slot{ - storage: make([]stackitem.Item, n), - refs: refs, + refs: refs, } } +// init sets static slot size to n. It is intended to be used only by INITSSLOT. +func (s *Slot) init(n int) { + if s.storage != nil { + panic("already initialized") + } + s.storage = make([]stackitem.Item, n) +} + func (v *VM) newSlot(n int) *Slot { - return newSlot(n, v.refs) + s := newSlot(v.refs) + s.init(n) + return s } // Set sets i-th storage slot. @@ -43,12 +52,17 @@ func (s *Slot) Get(i int) stackitem.Item { return stackitem.Null{} } -// Size returns slot size. -func (s *Slot) Size() int { return len(s.storage) } - // Clear removes all slot variables from reference counter. func (s *Slot) Clear() { for _, item := range s.storage { s.refs.Remove(item) } } + +// Size returns slot size. +func (s *Slot) Size() int { + if s.storage == nil { + panic("not initialized") + } + return len(s.storage) +} diff --git a/pkg/vm/slot_test.go b/pkg/vm/slot_test.go index 19186cdee7..4344644761 100644 --- a/pkg/vm/slot_test.go +++ b/pkg/vm/slot_test.go @@ -9,8 +9,11 @@ import ( ) func TestSlot_Get(t *testing.T) { - s := newSlot(3, newRefCounter()) + s := newSlot(newRefCounter()) require.NotNil(t, s) + require.Panics(t, func() { s.Size() }) + + s.init(3) require.Equal(t, 3, s.Size()) // Null is the default diff --git a/pkg/vm/vm.go b/pkg/vm/vm.go index d5ef55f7fc..558d8ccc4b 100644 --- a/pkg/vm/vm.go +++ b/pkg/vm/vm.go @@ -282,6 +282,7 @@ func (v *VM) LoadScriptWithFlags(b []byte, f smartcontract.CallFlag) { ctx.estack = v.estack ctx.tryStack = NewStack("exception") ctx.callFlag = f + ctx.static = newSlot(v.refs) v.istack.PushVal(ctx) } @@ -568,13 +569,10 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro v.estack.PushVal(result) case opcode.INITSSLOT: - if ctx.static != nil { - panic("already initialized") - } if parameter[0] == 0 { panic("zero argument") } - ctx.static = v.newSlot(int(parameter[0])) + ctx.static.init(int(parameter[0])) case opcode.INITSLOT: if ctx.local != nil || ctx.arguments != nil { diff --git a/pkg/vm/vm_test.go b/pkg/vm/vm_test.go index 3eb6532835..f4a0a94ecc 100644 --- a/pkg/vm/vm_test.go +++ b/pkg/vm/vm_test.go @@ -2596,6 +2596,14 @@ func TestSLOTOpcodes(t *testing.T) { t.Run("Local", getTestFuncForVM(makeProgram(opcode.INITSLOT, 8, 0, opcode.STLOC, 7, opcode.LDLOC, 7), 42, 42)) t.Run("Argument", getTestFuncForVM(makeProgram(opcode.INITSLOT, 0, 2, opcode.STARG, 1, opcode.LDARG, 1), 42, 42, 1, 2)) }) + + t.Run("InitStaticSlotInMethod", func(t *testing.T) { + prog := makeProgram( + opcode.CALL, 4, opcode.LDSFLD0, opcode.RET, + opcode.INITSSLOT, 1, opcode.PUSH12, opcode.STSFLD0, opcode.RET, + ) + runWithArgs(t, prog, 12) + }) } func makeProgram(opcodes ...opcode.Opcode) []byte { From 13b9eda08dff6cac445fac42ec660e89290f82d6 Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Fri, 24 Jul 2020 11:56:35 +0300 Subject: [PATCH 10/13] vm: allow to call VM methods from outside Abstract out (*VM).Call method and use in in CALL* opcodes. --- pkg/vm/vm.go | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/pkg/vm/vm.go b/pkg/vm/vm.go index 558d8ccc4b..78961146ff 100644 --- a/pkg/vm/vm.go +++ b/pkg/vm/vm.go @@ -1235,13 +1235,9 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro case opcode.CALL, opcode.CALLL: v.checkInvocationStackSize() - newCtx := ctx.Copy() - newCtx.local = nil - newCtx.arguments = nil - v.istack.PushVal(newCtx) - - offset := v.getJumpOffset(newCtx, parameter) - v.Jump(newCtx, offset) + // Note: jump offset must be calculated regarding to new context, + // but it is cloned and thus has the same script and instruction pointer. + v.Call(ctx, v.getJumpOffset(ctx, parameter)) case opcode.CALLA: ptr := v.estack.Pop().Item().(*stackitem.Pointer) @@ -1249,11 +1245,7 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro panic("invalid script in pointer") } - newCtx := ctx.Copy() - newCtx.local = nil - newCtx.arguments = nil - v.istack.PushVal(newCtx) - v.Jump(newCtx, ptr.Position()) + v.Call(ctx, ptr.Position()) case opcode.SYSCALL: interopID := GetInteropID(parameter) @@ -1471,6 +1463,16 @@ func (v *VM) Jump(ctx *Context, offset int) { ctx.nextip = offset } +// Call calls method by offset. It is similar to Jump but also +// pushes new context to the invocation state +func (v *VM) Call(ctx *Context, offset int) { + newCtx := ctx.Copy() + newCtx.local = nil + newCtx.arguments = nil + v.istack.PushVal(newCtx) + v.Jump(newCtx, offset) +} + // getJumpOffset returns instruction number in a current context // to a which JMP should be performed. // parameter should have length either 1 or 4 and From 466af55deaed71a91aef4a2a5e874fde85fd1216 Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Fri, 24 Jul 2020 13:09:02 +0300 Subject: [PATCH 11/13] vm: isolate stack in LoadScript When calling contract it must be provided with a new stack containing only it's arguments. The result is then copied back on RET. Fix #1220. --- pkg/core/interop_system_test.go | 16 ++++++++++++++++ pkg/vm/vm.go | 1 + 2 files changed, 17 insertions(+) diff --git a/pkg/core/interop_system_test.go b/pkg/core/interop_system_test.go index 941e51529d..d3b33add2f 100644 --- a/pkg/core/interop_system_test.go +++ b/pkg/core/interop_system_test.go @@ -329,6 +329,7 @@ func getTestContractState() *state.Contract { byte(opcode.ABORT), // abort if no offset was provided byte(opcode.ADD), byte(opcode.RET), byte(opcode.PUSH7), byte(opcode.RET), + byte(opcode.DROP), byte(opcode.RET), } h := hash.Hash160(script) m := manifest.NewManifest(h) @@ -348,6 +349,11 @@ func getTestContractState() *state.Contract { Parameters: []manifest.Parameter{}, ReturnType: smartcontract.IntegerType, }, + { + Name: "drop", + Offset: 5, + ReturnType: smartcontract.VoidType, + }, } return &state.Contract{ Script: script, @@ -375,6 +381,7 @@ func TestContractCall(t *testing.T) { m := manifest.NewManifest(hash.Hash160(currScript)) perm := manifest.NewPermission(manifest.PermissionHash, h) perm.Methods.Add("add") + perm.Methods.Add("drop") m.Permissions = append(m.Permissions, *perm) require.NoError(t, ic.DAO.PutContractState(&state.Contract{ @@ -425,6 +432,15 @@ func TestContractCall(t *testing.T) { t.Run("NotEnoughArguments", runInvalid( stackitem.NewArray([]stackitem.Item{stackitem.Make(1)}), "add", h.BytesBE())) }) + + t.Run("IsolatedStack", func(t *testing.T) { + initVM(v) + v.Estack().PushVal(stackitem.NewArray(nil)) + v.Estack().PushVal("drop") + v.Estack().PushVal(h.BytesBE()) + require.NoError(t, contractCall(ic, v)) + require.Error(t, v.Run()) + }) } func TestContractCreate(t *testing.T) { diff --git a/pkg/vm/vm.go b/pkg/vm/vm.go index 78961146ff..d9e2947315 100644 --- a/pkg/vm/vm.go +++ b/pkg/vm/vm.go @@ -279,6 +279,7 @@ func (v *VM) LoadScript(b []byte) { // LoadScriptWithFlags loads script and sets call flag to f. func (v *VM) LoadScriptWithFlags(b []byte, f smartcontract.CallFlag) { ctx := NewContext(b) + v.estack = v.newItemStack("estack") ctx.estack = v.estack ctx.tryStack = NewStack("exception") ctx.callFlag = f From 685d44dbc15938b165e074c4c63927c28b45e3e1 Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Fri, 24 Jul 2020 13:40:54 +0300 Subject: [PATCH 12/13] *: support `_initialize` method in contracts Invoke `_initialize` method on every call if present. In NEO3 there is no entrypoint and methods are invoked by offset, thus `Main` function is no longer required. We still have special `Main` method in tests to simplify them. --- pkg/compiler/analysis.go | 15 ++++++++--- pkg/compiler/codegen.go | 34 +++++++++++------------- pkg/compiler/debug.go | 36 ++++++++++++++++---------- pkg/compiler/global_test.go | 22 ++++++++++++++++ pkg/compiler/interop_test.go | 22 +++++++++++++++- pkg/compiler/vm_test.go | 28 ++++++++++++++++++-- pkg/core/interop_system.go | 6 +++++ pkg/core/interop_system_test.go | 30 +++++++++++++++++++++ pkg/smartcontract/manifest/manifest.go | 3 +++ 9 files changed, 156 insertions(+), 40 deletions(-) diff --git a/pkg/compiler/analysis.go b/pkg/compiler/analysis.go index 621825a61f..0033e427ba 100644 --- a/pkg/compiler/analysis.go +++ b/pkg/compiler/analysis.go @@ -27,16 +27,23 @@ func (c *codegen) newGlobal(name string) { } // traverseGlobals visits and initializes global variables. -func (c *codegen) traverseGlobals(f ast.Node) { - n := countGlobals(f) +// and returns number of variables initialized. +func (c *codegen) traverseGlobals(fs ...*ast.File) int { + var n int + for _, f := range fs { + n += countGlobals(f) + } if n != 0 { if n > 255 { c.prog.BinWriter.Err = errors.New("too many global variables") - return + return 0 } emit.Instruction(c.prog.BinWriter, opcode.INITSSLOT, []byte{byte(n)}) + for _, f := range fs { + c.convertGlobals(f) + } } - c.convertGlobals(f) + return n } // countGlobals counts the global variables in the program to add diff --git a/pkg/compiler/codegen.go b/pkg/compiler/codegen.go index 2dbcbc9005..5b96f5f942 100644 --- a/pkg/compiler/codegen.go +++ b/pkg/compiler/codegen.go @@ -21,9 +21,6 @@ import ( "golang.org/x/tools/go/loader" ) -// The identifier of the entry function. Default set to Main. -const mainIdent = "Main" - type codegen struct { // Information about the program with all its dependencies. buildInfo *buildInfo @@ -62,6 +59,12 @@ type codegen struct { // to a text span in the source file. sequencePoints map[string][]DebugSeqPoint + // initEndOffset specifies the end of the initialization method. + initEndOffset int + + // mainPkg is a main package metadata. + mainPkg *loader.PackageInfo + // Label table for recording jump destinations. l []int } @@ -1412,13 +1415,6 @@ func (c *codegen) newLambda(u uint16, lit *ast.FuncLit) { } func (c *codegen) compile(info *buildInfo, pkg *loader.PackageInfo) error { - // Resolve the entrypoint of the program. - main, mainFile := resolveEntryPoint(mainIdent, pkg) - if main == nil { - c.prog.Err = fmt.Errorf("could not find func main. Did you forget to declare it? ") - return c.prog.Err - } - funUsage := analyzeFuncUsage(pkg, info.program.AllPackages) // Bring all imported functions into scope. @@ -1428,10 +1424,12 @@ func (c *codegen) compile(info *buildInfo, pkg *loader.PackageInfo) error { } } - c.traverseGlobals(mainFile) - - // convert the entry point first. - c.convertFuncDecl(mainFile, main, pkg.Pkg) + c.mainPkg = pkg + n := c.traverseGlobals(pkg.Files...) + if n > 0 { + emit.Opcode(c.prog.BinWriter, opcode.RET) + c.initEndOffset = c.prog.Len() + } // sort map keys to generate code deterministically. keys := make([]*types.Package, 0, len(info.program.AllPackages)) @@ -1451,7 +1449,7 @@ func (c *codegen) compile(info *buildInfo, pkg *loader.PackageInfo) error { case *ast.FuncDecl: // Don't convert the function if it's not used. This will save a lot // of bytecode space. - if n.Name.Name != mainIdent && funUsage.funcUsed(n.Name.Name) { + if funUsage.funcUsed(n.Name.Name) { c.convertFuncDecl(f, n, k) } } @@ -1497,10 +1495,8 @@ func (c *codegen) resolveFuncDecls(f *ast.File, pkg *types.Package) { for _, decl := range f.Decls { switch n := decl.(type) { case *ast.FuncDecl: - if n.Name.Name != mainIdent { - c.newFunc(n) - c.funcs[n.Name.Name].pkg = pkg - } + c.newFunc(n) + c.funcs[n.Name.Name].pkg = pkg } } } diff --git a/pkg/compiler/debug.go b/pkg/compiler/debug.go index ebf5a3e61b..01b1aed64c 100644 --- a/pkg/compiler/debug.go +++ b/pkg/compiler/debug.go @@ -17,6 +17,7 @@ import ( // DebugInfo represents smart-contract debug information. type DebugInfo struct { + MainPkg string `json:"-"` Hash util.Uint160 `json:"hash"` Documents []string `json:"documents"` Methods []MethodDebugInfo `json:"methods"` @@ -102,8 +103,24 @@ func (c *codegen) saveSequencePoint(n ast.Node) { func (c *codegen) emitDebugInfo(contract []byte) *DebugInfo { d := &DebugInfo{ - Hash: hash.Hash160(contract), - Events: []EventDebugInfo{}, + MainPkg: c.mainPkg.Pkg.Name(), + Hash: hash.Hash160(contract), + Events: []EventDebugInfo{}, + } + if c.initEndOffset > 0 { + d.Methods = append(d.Methods, MethodDebugInfo{ + ID: manifest.MethodInit, + Name: DebugMethodName{ + Name: manifest.MethodInit, + Namespace: c.mainPkg.Pkg.Name(), + }, + IsExported: true, + Range: DebugRange{ + Start: 0, + End: uint16(c.initEndOffset), + }, + ReturnType: "Void", + }) } for name, scope := range c.funcs { m := c.methodInfoFromScope(name, scope) @@ -341,22 +358,13 @@ func parsePairJSON(data []byte, sep string) (string, string, error) { // ConvertToManifest converts contract to the manifest.Manifest struct for debugger. // Note: manifest is taken from the external source, however it can be generated ad-hoc. See #1038. func (di *DebugInfo) ConvertToManifest(fs smartcontract.PropertyState) (*manifest.Manifest, error) { - var ( - mainNamespace string - err error - ) - for _, method := range di.Methods { - if method.Name.Name == mainIdent { - mainNamespace = method.Name.Namespace - break - } - } - if mainNamespace == "" { + var err error + if di.MainPkg == "" { return nil, errors.New("no Main method was found") } methods := make([]manifest.Method, 0) for _, method := range di.Methods { - if method.IsExported && method.Name.Namespace == mainNamespace { + if method.IsExported && method.Name.Namespace == di.MainPkg { mMethod, err := method.ToManifestMethod() if err != nil { return nil, err diff --git a/pkg/compiler/global_test.go b/pkg/compiler/global_test.go index 8b98761d34..359e8ef69a 100644 --- a/pkg/compiler/global_test.go +++ b/pkg/compiler/global_test.go @@ -3,7 +3,12 @@ package compiler_test import ( "fmt" "math/big" + "strings" "testing" + + "github.com/nspcc-dev/neo-go/pkg/compiler" + "github.com/nspcc-dev/neo-go/pkg/vm" + "github.com/stretchr/testify/require" ) func TestChangeGlobal(t *testing.T) { @@ -105,3 +110,20 @@ func TestArgumentLocal(t *testing.T) { eval(t, src, big.NewInt(40)) }) } + +func TestContractWithNoMain(t *testing.T) { + src := `package foo + var someGlobal int = 1 + func Add3(a int) int { + someLocal := 2 + return someGlobal + someLocal + a + }` + b, di, err := compiler.CompileWithDebugInfo(strings.NewReader(src)) + require.NoError(t, err) + v := vm.New() + invokeMethod(t, "Add3", b, v, di) + v.Estack().PushVal(39) + require.NoError(t, v.Run()) + require.Equal(t, 1, v.Estack().Len()) + require.Equal(t, big.NewInt(42), v.PopResult()) +} diff --git a/pkg/compiler/interop_test.go b/pkg/compiler/interop_test.go index 8f73266371..e0cceeb59d 100644 --- a/pkg/compiler/interop_test.go +++ b/pkg/compiler/interop_test.go @@ -63,9 +63,10 @@ func TestFromAddress(t *testing.T) { } func spawnVM(t *testing.T, ic *interop.Context, src string) *vm.VM { - b, err := compiler.Compile(strings.NewReader(src)) + b, di, err := compiler.CompileWithDebugInfo(strings.NewReader(src)) require.NoError(t, err) v := core.SpawnVM(ic) + invokeMethod(t, testMainIdent, b, v, di) v.LoadScriptWithFlags(b, smartcontract.All) return v } @@ -73,12 +74,16 @@ func spawnVM(t *testing.T, ic *interop.Context, src string) *vm.VM { func TestAppCall(t *testing.T) { srcInner := ` package foo + var a int = 3 func Main(a []byte, b []byte) []byte { panic("Main was called") } func Append(a []byte, b []byte) []byte { return append(a, b...) } + func Add3(n int) int { + return a + n + } ` inner, di, err := compiler.CompileWithDebugInfo(strings.NewReader(srcInner)) @@ -147,6 +152,21 @@ func TestAppCall(t *testing.T) { assertResult(t, v, []byte{1, 2, 3, 4}) }) + + t.Run("InitializedGlobals", func(t *testing.T) { + src := `package foo + import "github.com/nspcc-dev/neo-go/pkg/interop/engine" + func Main() int { + var addr = []byte(` + fmt.Sprintf("%#v", string(ih.BytesBE())) + `) + result := engine.AppCall(addr, "add3", 39) + return result.(int) + }` + + v := spawnVM(t, ic, src) + require.NoError(t, v.Run()) + + assertResult(t, v, big.NewInt(42)) + }) } func getAppCallScript(h string) string { diff --git a/pkg/compiler/vm_test.go b/pkg/compiler/vm_test.go index 89d0d40c0b..f2af0d5b80 100644 --- a/pkg/compiler/vm_test.go +++ b/pkg/compiler/vm_test.go @@ -7,6 +7,8 @@ import ( "github.com/nspcc-dev/neo-go/pkg/compiler" "github.com/nspcc-dev/neo-go/pkg/core/state" + "github.com/nspcc-dev/neo-go/pkg/smartcontract" + "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" "github.com/nspcc-dev/neo-go/pkg/vm" "github.com/nspcc-dev/neo-go/pkg/vm/emit" "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" @@ -20,6 +22,9 @@ type testCase struct { result interface{} } +// testMainIdent is a method invoked in tests by default. +const testMainIdent = "Main" + func runTestCases(t *testing.T, tcases []testCase) { for _, tcase := range tcases { t.Run(tcase.name, func(t *testing.T) { eval(t, tcase.src, tcase.result) }) @@ -65,12 +70,31 @@ func vmAndCompileInterop(t *testing.T, src string) (*vm.VM, *storagePlugin) { storePlugin := newStoragePlugin() vm.RegisterInteropGetter(storePlugin.getInterop) - b, err := compiler.Compile(strings.NewReader(src)) + b, di, err := compiler.CompileWithDebugInfo(strings.NewReader(src)) require.NoError(t, err) - vm.Load(b) + invokeMethod(t, testMainIdent, b, vm, di) return vm, storePlugin } +func invokeMethod(t *testing.T, method string, script []byte, v *vm.VM, di *compiler.DebugInfo) { + mainOffset := -1 + initOffset := -1 + for i := range di.Methods { + switch di.Methods[i].ID { + case method: + mainOffset = int(di.Methods[i].Range.Start) + case manifest.MethodInit: + initOffset = int(di.Methods[i].Range.Start) + } + } + require.True(t, mainOffset >= 0) + v.LoadScriptWithFlags(script, smartcontract.All) + v.Jump(v.Context(), mainOffset) + if initOffset >= 0 { + v.Call(v.Context(), initOffset) + } +} + type storagePlugin struct { mem map[string][]byte interops map[uint32]vm.InteropFunc diff --git a/pkg/core/interop_system.go b/pkg/core/interop_system.go index 698d391a00..b7aaa116a8 100644 --- a/pkg/core/interop_system.go +++ b/pkg/core/interop_system.go @@ -17,6 +17,7 @@ import ( "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" @@ -536,6 +537,11 @@ func contractCallExInternal(ic *interop.Context, v *vm.VM, h []byte, method stac v.Jump(v.Context(), md.Offset) } + md = cs.Manifest.ABI.GetMethod(manifest.MethodInit) + if md != nil { + v.Call(v.Context(), md.Offset) + } + return nil } diff --git a/pkg/core/interop_system_test.go b/pkg/core/interop_system_test.go index d3b33add2f..33f9414b19 100644 --- a/pkg/core/interop_system_test.go +++ b/pkg/core/interop_system_test.go @@ -330,6 +330,8 @@ func getTestContractState() *state.Contract { byte(opcode.ADD), byte(opcode.RET), byte(opcode.PUSH7), byte(opcode.RET), byte(opcode.DROP), byte(opcode.RET), + byte(opcode.INITSSLOT), 1, byte(opcode.PUSH3), byte(opcode.STSFLD0), byte(opcode.RET), + byte(opcode.LDSFLD0), byte(opcode.ADD), byte(opcode.RET), } h := hash.Hash160(script) m := manifest.NewManifest(h) @@ -354,6 +356,19 @@ func getTestContractState() *state.Contract { Offset: 5, ReturnType: smartcontract.VoidType, }, + { + Name: manifest.MethodInit, + Offset: 7, + ReturnType: smartcontract.VoidType, + }, + { + Name: "add3", + Offset: 12, + Parameters: []manifest.Parameter{ + manifest.NewParameter("addend", smartcontract.IntegerType), + }, + ReturnType: smartcontract.IntegerType, + }, } return &state.Contract{ Script: script, @@ -382,6 +397,7 @@ func TestContractCall(t *testing.T) { perm := manifest.NewPermission(manifest.PermissionHash, h) perm.Methods.Add("add") perm.Methods.Add("drop") + perm.Methods.Add("add3") m.Permissions = append(m.Permissions, *perm) require.NoError(t, ic.DAO.PutContractState(&state.Contract{ @@ -441,6 +457,20 @@ func TestContractCall(t *testing.T) { require.NoError(t, contractCall(ic, v)) require.Error(t, v.Run()) }) + + t.Run("CallInitialize", func(t *testing.T) { + t.Run("Directly", runInvalid(stackitem.NewArray([]stackitem.Item{}), "_initialize", h.BytesBE())) + + initVM(v) + v.Estack().PushVal(stackitem.NewArray([]stackitem.Item{stackitem.Make(5)})) + v.Estack().PushVal("add3") + v.Estack().PushVal(h.BytesBE()) + require.NoError(t, contractCall(ic, v)) + require.NoError(t, v.Run()) + require.Equal(t, 2, v.Estack().Len()) + require.Equal(t, big.NewInt(8), v.Estack().Pop().Value()) + require.Equal(t, big.NewInt(42), v.Estack().Pop().Value()) + }) } func TestContractCreate(t *testing.T) { diff --git a/pkg/smartcontract/manifest/manifest.go b/pkg/smartcontract/manifest/manifest.go index d733c90c98..d3db02bba7 100644 --- a/pkg/smartcontract/manifest/manifest.go +++ b/pkg/smartcontract/manifest/manifest.go @@ -11,6 +11,9 @@ import ( // MaxManifestSize is a max length for a valid contract manifest. const MaxManifestSize = 2048 +// MethodInit is a name for default initialization method. +const MethodInit = "_initialize" + // ABI represents a contract application binary interface. type ABI struct { Hash util.Uint160 `json:"hash"` From 84c148575d4ae61773c3e1aa105ac08878dec3ce Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Fri, 24 Jul 2020 13:54:58 +0300 Subject: [PATCH 13/13] rpc: remove Main func from test contract --- pkg/rpc/server/server_test.go | 4 ++-- pkg/rpc/server/testdata/test_contract.go | 4 ---- pkg/rpc/server/testdata/testblocks.acc | Bin 7315 -> 7333 bytes 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/pkg/rpc/server/server_test.go b/pkg/rpc/server/server_test.go index 03d5ee7ec0..7b3635a7e1 100644 --- a/pkg/rpc/server/server_test.go +++ b/pkg/rpc/server/server_test.go @@ -51,8 +51,8 @@ type rpcTestCase struct { check func(t *testing.T, e *executor, result interface{}) } -const testContractHash = "6e2d823c81589871590653a100c7e9bdf9c94344" -const deploymentTxHash = "3b434127495a6dd0e786a2e0f04696009cd6e6e5f9b930f0e79356638532096c" +const testContractHash = "36c3b0c85d98607db00b711885ec3e411d9b1672" +const deploymentTxHash = "dcf4fe429ec84947361c86c2192b14641be7f0c6e2bdf8d150fad731160ed386" var rpcTestCases = map[string][]rpcTestCase{ "getapplicationlog": { diff --git a/pkg/rpc/server/testdata/test_contract.go b/pkg/rpc/server/testdata/test_contract.go index c7da44f4c9..38ccd62f0a 100644 --- a/pkg/rpc/server/testdata/test_contract.go +++ b/pkg/rpc/server/testdata/test_contract.go @@ -10,10 +10,6 @@ const ( decimals = 2 ) -func Main(operation string, args []interface{}) interface{} { - panic("invoking via Main is no longer supported") // catch possible bugs -} - func Init() bool { ctx := storage.GetContext() h := runtime.GetExecutingScriptHash() diff --git a/pkg/rpc/server/testdata/testblocks.acc b/pkg/rpc/server/testdata/testblocks.acc index e53450d08b61b176b5336b11d1ff9b754e092a79..be4676b9f526d99c8c21b228d9b09af0b759a51d 100644 GIT binary patch delta 2846 zcmaJ?dpy(YAD`K>F@;T*>kPS8V`H0?5keD^d#v4Da?8+V%&MazmCIK|h{z#|;uMKo zn#koOA>|U4ToOwcxgPxbo!9x}yw3B_^T+dfKA+F?et+KY2O!@hCsHBxK*Xse2P_{j zL9U$hZq2UR{-VgTyVS3TMZ@k zPKkb3JK+N&_r)z|jt&N!iP@ApjT^epR7cOIcdAxK(lDt?VLb>+ep!^wj3#tCyM8~+ ziwB)q_936Ab|+NmO_XFk)$SM`J8Mf7FTCGhoGF5_KVE#}=Ns!0Y+z>|EN8Bn{6~cm z2&fmmQZ5O;2yukMLF>!C-D|q!-0XRIX2t>rTJl=2g$o)8j|zjIFY?VT$oNu)p|qVf zop7$^5zbo*wT*FNC=&&F%j+AV2|s`%>^BXo2#1M)Kww~alM=ro?%O4Tk!Jn@Bx%&v zVJywA@8xs#x_h%>X+sylPNtlI2Q8h%{*K^f88#9r|&R1s`m|~5vj5PPWu@IGtdPON;i|U((fZ<9@p{?i358cVaFfk4EU--moB zu6&sM4hpo`-V?=twGx1!gq*ZJG(6cF*MS}tDZA8rV&1nh$Wp0#&nxu9bd7PO#m(7; zlNb@SLC=lf2)#f8dCa{dCS}x*=HzRo*w>JrilCT378Ew11<7gz)kn^Wi&7p=XF7MJ zDTrEX+4E-S_G&ONc)UFcnu4C6I9dP23ucBdl&JKoJ}Xyb(u&$#_60o*~De z2hxL?YUl0i` zI2zUeDzGl9i8<2{Nt>JUvw8WvEJBZ9D`|6dqk-L?eKeP%Yn1yhdeMcb=Llt?$4SWJ z?;5hq#?^TS`ZrYJ-lo{?A1I^7*p6(>P!eqI6ZKM zo6y1V*up*U{MHzHJf_%DLHV6y*)iPvnwf&D=@+ov?-&CA)caM}Y2YkyKQHe6mOiFP zxvMo~a-?!!OHH2OVMg+#vd4h58Y(fiN>v@x-MSoIkWI=VdOrzP^1F3cuSFs8bj*Vs zJp=`I)xu%h>j-UFOYC@0pVxZ@FXXnsKG_}L&$$^m6!~cMla-(e)_YMV%fCxb9-5|( z=cFpERE{5ljV>Gm1*ATH_xC90OFMdTHeXXR+x_V*I#i;JclfGTb6KO28H49}K*StD zQMr)g_$F(R_F<1$H-hQ zr76CpPy#vb3)O+}bq=FsU>Nabr_9*Y8GW(qU!-(&MS}u*pAS8RcN z84Uc%FXMr%8;^-)fa((qd#p*Nm=J(v|r*1-VmJhRPq}VSsu{G> z;RgzR+sTzHU?r#@&%l~Q&E&MzOPy?Mb-{UGvRY2*aeO-FB>06fbRA={KhHrye9%!V zh&054V;W>2dyhyxy? z#CV6{X3a`W4{Kj5#s=3RN z|6386$D~D#Tgzz4p(|HDX_Oa;Jl4}Vb2l^EecLw)nWngxzBN}L?N+Qcx5<4uZFB$nwD{M zNmG@d@4N_jtrJJI==whf8vCJ`mn|!d_2o@%BPMDZkh2Q4)O;KxlV+YlF4aW*%%}Rk zOul*C2}5*9(Ut*nbf0wIn0ewZgE5a}QvtOOg58;bz!4)%3mt%VJ8j|BNS_sO< z_QmmU{dNHd3#rkfS|GoG_|Nd*%M%_-Z@viaSCVM3jo0(y3NN7^G34Yw2RlEDJNs~n za$mSbvbcHNfFE01f}rf?5pjzji$$l?v2R;_mr}Z&pM+O%T)Q5Zq$baO6mWGHK5s>b z<$3YR@w-|NUVPgnkc2Yr<8C}1c2oGe3^|9OQ0D|%^5{1erJtYc3OXBcnxzgD^>ph= zW@XZgk(5N2J7ql(VdBzp71aLuUHvr^gK27UXVj#2zy?!&qOO`%5PwNG-uu6)T-mI8 zt#52`k4pQz8xq7A4D$%|b}8a*5SPS+L<);s&Evveq$G@>_B}T3f-NalqwB<3T01zo PT9t6fiiNMp8k2tkcEs?0 delta 2851 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