compiler: allow to overload methods in manifest
Signed-off-by: Evgeniy Stratonikov <evgeniy@nspcc.ru>
This commit is contained in:
parent
67eac3a27f
commit
7758378d28
8 changed files with 128 additions and 0 deletions
|
@ -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))
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue