compiler: allow to overload methods in manifest

Signed-off-by: Evgeniy Stratonikov <evgeniy@nspcc.ru>
This commit is contained in:
Evgeniy Stratonikov 2021-10-16 14:10:17 +03:00
parent 67eac3a27f
commit 7758378d28
8 changed files with 128 additions and 0 deletions

View file

@ -647,6 +647,22 @@ func TestCompileExamples(t *testing.T) {
"--config", path.Join(examplePath, info.Name(), cfgName), "--config", path.Join(examplePath, info.Name(), cfgName),
} }
e.Run(t, opts...) 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))
}
}) })
} }

View file

@ -498,6 +498,7 @@ func contractCompile(ctx *cli.Context) error {
o.Permissions[i] = manifest.Permission(conf.Permissions[i]) o.Permissions[i] = manifest.Permission(conf.Permissions[i])
} }
o.SafeMethods = conf.SafeMethods o.SafeMethods = conf.SafeMethods
o.Overloads = conf.Overloads
} }
result, err := compiler.CompileAndSave(src, o) result, err := compiler.CompileAndSave(src, o)
@ -740,6 +741,7 @@ type ProjectConfig struct {
SupportedStandards []string SupportedStandards []string
Events []manifest.Event Events []manifest.Event
Permissions []permission Permissions []permission
Overloads map[string]string `yaml:"overloads,omitempty"`
} }
func inspect(ctx *cli.Context) error { func inspect(ctx *cli.Context) error {

View file

@ -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"]` | `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). | | `events` | Notifications emitted by this contract. | See [Events](#Events). |
| `permissions` | Foreign calls allowed for this contract. | See [Permissions](#Permissions). | | `permissions` | Foreign calls allowed for this contract. | See [Permissions](#Permissions). |
| `overloads` | Custom method names for this contract. | See [Overloads](#Overloads). |
##### Events ##### Events
Each event must have a name and 0 or more parameters. Parameters are specified using their name and type. 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. to perform more extensive analysis.
This check can be disabled with `--no-permissions` flag. 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 #### Manifest file
Any contract can be included in a group identified by a public key which is used in [permissions](#Permissions). 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. This is achieved with `manifest add-group` command.

View file

@ -8,6 +8,9 @@ import (
// ctx holds storage context for contract methods // ctx holds storage context for contract methods
var ctx storage.Context var ctx storage.Context
// defaultKey represents the default key.
var defaultKey = []byte("default")
// init inits storage context before any other contract method is called // init inits storage context before any other contract method is called
func init() { func init() {
ctx = storage.GetContext() ctx = storage.GetContext()
@ -19,11 +22,22 @@ func Put(key, value []byte) []byte {
return key 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. // Get returns the value at passed key.
func Get(key []byte) interface{} { func Get(key []byte) interface{} {
return storage.Get(ctx, key) 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. // Delete deletes the value at passed key.
func Delete(key []byte) bool { func Delete(key []byte) bool {
storage.Delete(ctx, key) storage.Delete(ctx, key)

View file

@ -2,3 +2,6 @@ name: "Storage example"
sourceurl: https://github.com/nspcc-dev/neo-go/ sourceurl: https://github.com/nspcc-dev/neo-go/
supportedstandards: [] supportedstandards: []
events: [] events: []
overloads:
getDefault: get
putDefault: put

View file

@ -64,6 +64,10 @@ type Options struct {
// SafeMethods contains list of methods which will be marked as safe in manifest. // SafeMethods contains list of methods which will be marked as safe in manifest.
SafeMethods []string 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 is a list of permissions for every contract method.
Permissions []manifest.Permission Permissions []manifest.Permission
} }

View file

@ -446,5 +446,21 @@ func (di *DebugInfo) ConvertToManifest(o *Options) (*manifest.Manifest, error) {
result.ABI.Events = make([]manifest.Event, 0) result.ABI.Events = make([]manifest.Event, 0)
} }
result.Permissions = o.Permissions 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 return result, nil
} }

View file

@ -2,6 +2,7 @@ package compiler
import ( import (
"encoding/json" "encoding/json"
"strings"
"testing" "testing"
"github.com/nspcc-dev/neo-go/internal/testserdes" "github.com/nspcc-dev/neo-go/internal/testserdes"
@ -361,3 +362,52 @@ func TestDebugInfo_MarshalJSON(t *testing.T) {
testserdes.MarshalUnmarshalJSON(t, d, new(DebugInfo)) 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)
})
}