From 7758378d28d295509863c12a1256c424933eaa0b Mon Sep 17 00:00:00 2001 From: Evgeniy Stratonikov Date: Sat, 16 Oct 2021 14:10:17 +0300 Subject: [PATCH] compiler: allow to overload methods in manifest Signed-off-by: Evgeniy Stratonikov --- cli/contract_test.go | 16 +++++++++ cli/smartcontract/smart_contract.go | 2 ++ docs/compiler.md | 23 +++++++++++++ examples/storage/storage.go | 14 ++++++++ examples/storage/storage.yml | 3 ++ pkg/compiler/compiler.go | 4 +++ pkg/compiler/debug.go | 16 +++++++++ pkg/compiler/debug_test.go | 50 +++++++++++++++++++++++++++++ 8 files changed, 128 insertions(+) diff --git a/cli/contract_test.go b/cli/contract_test.go index 37aef9a68..d43eb8d3e 100644 --- a/cli/contract_test.go +++ b/cli/contract_test.go @@ -647,6 +647,22 @@ func TestCompileExamples(t *testing.T) { "--config", path.Join(examplePath, info.Name(), cfgName), } e.Run(t, opts...) + + if info.Name() == "storage" { + rawM, err := ioutil.ReadFile(manifestF) + require.NoError(t, err) + + m := new(manifest.Manifest) + require.NoError(t, json.Unmarshal(rawM, m)) + + require.Nil(t, m.ABI.GetMethod("getDefault", 0)) + require.NotNil(t, m.ABI.GetMethod("get", 0)) + require.NotNil(t, m.ABI.GetMethod("get", 1)) + + require.Nil(t, m.ABI.GetMethod("putDefault", 1)) + require.NotNil(t, m.ABI.GetMethod("put", 1)) + require.NotNil(t, m.ABI.GetMethod("put", 2)) + } }) } diff --git a/cli/smartcontract/smart_contract.go b/cli/smartcontract/smart_contract.go index c66659d66..8d9cc9b0a 100644 --- a/cli/smartcontract/smart_contract.go +++ b/cli/smartcontract/smart_contract.go @@ -498,6 +498,7 @@ func contractCompile(ctx *cli.Context) error { o.Permissions[i] = manifest.Permission(conf.Permissions[i]) } o.SafeMethods = conf.SafeMethods + o.Overloads = conf.Overloads } result, err := compiler.CompileAndSave(src, o) @@ -740,6 +741,7 @@ type ProjectConfig struct { SupportedStandards []string Events []manifest.Event Permissions []permission + Overloads map[string]string `yaml:"overloads,omitempty"` } func inspect(ctx *cli.Context) error { diff --git a/docs/compiler.md b/docs/compiler.md index 51ebf38c3..b53a92a65 100644 --- a/docs/compiler.md +++ b/docs/compiler.md @@ -233,6 +233,7 @@ Configuration file contains following options: | `supportedstandards` | List of standards this contract implements. For example, `NEP-11` or `NEP-17` token standard. This will enable additional checks in compiler. The check can be disabled with `--no-standards` flag. | `["NEP-17"]` | `events` | Notifications emitted by this contract. | See [Events](#Events). | | `permissions` | Foreign calls allowed for this contract. | See [Permissions](#Permissions). | +| `overloads` | Custom method names for this contract. | See [Overloads](#Overloads). | ##### Events Each event must have a name and 0 or more parameters. Parameters are specified using their name and type. @@ -318,6 +319,28 @@ Using either constant or literal for contract hash and method will allow compile to perform more extensive analysis. This check can be disabled with `--no-permissions` flag. +##### Overloads +NeoVM allows a contract to have multiple methods with the same name +but different parameters number. Go lacks this feature but this can be circumvented +with `overloads` section. Essentially it is a mapping from default contract method names +to the new ones. +``` +- overloads: + oldName1: newName + oldName2: newName +``` +Because the use-case for this is to provide multiple implementations with the same ABI name, +`newName` is required to be already present in the compiled contract. + +As an example consider [`NEP-11` standard](https://github.com/neo-project/proposals/blob/master/nep-11.mediawiki#transfer). +It requires divisible NFT contract to have 2 `transfer` methods. To achieve this we might implement +`Tranfer` and `TransferDivisible` and specify emitted name in config: +``` +- overloads: + transferDivisible:transfer +``` + + #### Manifest file Any contract can be included in a group identified by a public key which is used in [permissions](#Permissions). This is achieved with `manifest add-group` command. diff --git a/examples/storage/storage.go b/examples/storage/storage.go index 3831ff119..306e986bc 100644 --- a/examples/storage/storage.go +++ b/examples/storage/storage.go @@ -8,6 +8,9 @@ import ( // ctx holds storage context for contract methods var ctx storage.Context +// defaultKey represents the default key. +var defaultKey = []byte("default") + // init inits storage context before any other contract method is called func init() { ctx = storage.GetContext() @@ -19,11 +22,22 @@ func Put(key, value []byte) []byte { return key } +// PutDefault puts value to the default key. +func PutDefault(value []byte) []byte { + storage.Put(ctx, defaultKey, value) + return defaultKey +} + // Get returns the value at passed key. func Get(key []byte) interface{} { return storage.Get(ctx, key) } +// GetDefault returns the value at the default key. +func GetDefault() interface{} { + return storage.Get(ctx, defaultKey) +} + // Delete deletes the value at passed key. func Delete(key []byte) bool { storage.Delete(ctx, key) diff --git a/examples/storage/storage.yml b/examples/storage/storage.yml index 5d1dcd7e7..c5db1ef44 100644 --- a/examples/storage/storage.yml +++ b/examples/storage/storage.yml @@ -2,3 +2,6 @@ name: "Storage example" sourceurl: https://github.com/nspcc-dev/neo-go/ supportedstandards: [] events: [] +overloads: + getDefault: get + putDefault: put diff --git a/pkg/compiler/compiler.go b/pkg/compiler/compiler.go index ba9dc0ee4..6e90b5476 100644 --- a/pkg/compiler/compiler.go +++ b/pkg/compiler/compiler.go @@ -64,6 +64,10 @@ type Options struct { // SafeMethods contains list of methods which will be marked as safe in manifest. SafeMethods []string + // Overloads contains mapping from compiled method name to the name emitted in manifest. + // It can be used to provide method overloads as Go doesn't have such capability. + Overloads map[string]string + // Permissions is a list of permissions for every contract method. Permissions []manifest.Permission } diff --git a/pkg/compiler/debug.go b/pkg/compiler/debug.go index 2bcd6a52e..b9bcc9339 100644 --- a/pkg/compiler/debug.go +++ b/pkg/compiler/debug.go @@ -446,5 +446,21 @@ func (di *DebugInfo) ConvertToManifest(o *Options) (*manifest.Manifest, error) { result.ABI.Events = make([]manifest.Event, 0) } result.Permissions = o.Permissions + for name, emitName := range o.Overloads { + m := result.ABI.GetMethod(name, -1) + if m == nil { + return nil, fmt.Errorf("overload for method %s was provided but it wasn't found", name) + } + if result.ABI.GetMethod(emitName, -1) == nil { + return nil, fmt.Errorf("overload with target method %s was provided but it wasn't found", emitName) + } + + realM := result.ABI.GetMethod(emitName, len(m.Parameters)) + if realM != nil { + return nil, fmt.Errorf("conflict overload for %s: "+ + "multiple methods with the same number of parameters", name) + } + m.Name = emitName + } return result, nil } diff --git a/pkg/compiler/debug_test.go b/pkg/compiler/debug_test.go index efc7efb21..f30006140 100644 --- a/pkg/compiler/debug_test.go +++ b/pkg/compiler/debug_test.go @@ -2,6 +2,7 @@ package compiler import ( "encoding/json" + "strings" "testing" "github.com/nspcc-dev/neo-go/internal/testserdes" @@ -361,3 +362,52 @@ func TestDebugInfo_MarshalJSON(t *testing.T) { testserdes.MarshalUnmarshalJSON(t, d, new(DebugInfo)) } + +func TestManifestOverload(t *testing.T) { + src := `package foo + func Main() int { + return 1 + } + func Add3() int { + return Add3Aux(0) + } + func Add3Aux(a int) int { + return a + 3 + } + func Add3Aux2(b int) int { + return b + 3 + } + func Add4() int { + return 4 + }` + + _, di, err := CompileWithDebugInfo("foo", strings.NewReader(src)) + require.NoError(t, err) + + m, err := di.ConvertToManifest(&Options{Overloads: map[string]string{"add3Aux": "add3"}}) + require.NoError(t, err) + require.NoError(t, m.ABI.IsValid()) + require.NotNil(t, m.ABI.GetMethod("add3", 0)) + require.NotNil(t, m.ABI.GetMethod("add3", 1)) + require.Nil(t, m.ABI.GetMethod("add3Aux", 1)) + + t.Run("missing method", func(t *testing.T) { + _, err := di.ConvertToManifest(&Options{Overloads: map[string]string{"miss": "add3"}}) + require.Error(t, err) + }) + t.Run("parameter conflict", func(t *testing.T) { + _, err := di.ConvertToManifest(&Options{Overloads: map[string]string{"add4": "add3"}}) + require.Error(t, err) + }) + t.Run("parameter conflict, overload", func(t *testing.T) { + _, err := di.ConvertToManifest(&Options{Overloads: map[string]string{ + "add3Aux": "add3", + "add3Aux2": "add3", + }}) + require.Error(t, err) + }) + t.Run("missing target method", func(t *testing.T) { + _, err := di.ConvertToManifest(&Options{Overloads: map[string]string{"add4": "add5"}}) + require.Error(t, err) + }) +}