manifest: support interface checking
There are some standards (NEP5, etc.) which impose some restrictions on what methods and events a contract must contain and their signatures. This commit supports checking if arbitrary manifest complies with the standard.
This commit is contained in:
parent
882c214646
commit
279b769fa3
5 changed files with 261 additions and 0 deletions
|
@ -89,6 +89,16 @@ func (a *ABI) GetMethod(name string) *Method {
|
|||
return nil
|
||||
}
|
||||
|
||||
// GetEvent returns event with the specified name.
|
||||
func (a *ABI) GetEvent(name string) *Event {
|
||||
for i := range a.Events {
|
||||
if a.Events[i].Name == name {
|
||||
return &a.Events[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 {
|
||||
|
|
76
pkg/smartcontract/manifest/standard/comply.go
Normal file
76
pkg/smartcontract/manifest/standard/comply.go
Normal file
|
@ -0,0 +1,76 @@
|
|||
package standard
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
|
||||
)
|
||||
|
||||
// Various validation errors.
|
||||
var (
|
||||
ErrMethodMissing = errors.New("method missing")
|
||||
ErrEventMissing = errors.New("event missing")
|
||||
ErrInvalidReturnType = errors.New("invalid return type")
|
||||
ErrInvalidParameterCount = errors.New("invalid parameter count")
|
||||
ErrInvalidParameterType = errors.New("invalid parameter type")
|
||||
)
|
||||
|
||||
var checks = map[string]*manifest.Manifest{
|
||||
manifest.NEP17StandardName: nep17,
|
||||
}
|
||||
|
||||
// Check checks if manifest complies with all provided standards.
|
||||
// Currently only NEP-17 is supported.
|
||||
func Check(m *manifest.Manifest, standards ...string) error {
|
||||
for i := range standards {
|
||||
s, ok := checks[standards[i]]
|
||||
if ok {
|
||||
if err := Comply(m, s); err != nil {
|
||||
return fmt.Errorf("manifest is not compliant with '%s': %w", standards[i], err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Comply if m has all methods and event from st manifest and they have the same signature.
|
||||
// Parameter names are ignored.
|
||||
func Comply(m, st *manifest.Manifest) error {
|
||||
for _, stm := range st.ABI.Methods {
|
||||
name := stm.Name
|
||||
md := m.ABI.GetMethod(name)
|
||||
if md == nil {
|
||||
return fmt.Errorf("%w: '%s'", ErrMethodMissing, name)
|
||||
} else if stm.ReturnType != md.ReturnType {
|
||||
return fmt.Errorf("%w: '%s' (expected %s, got %s)", ErrInvalidReturnType,
|
||||
name, stm.ReturnType, md.ReturnType)
|
||||
} else if len(stm.Parameters) != len(md.Parameters) {
|
||||
return fmt.Errorf("%w: '%s' (expected %d, got %d)", ErrInvalidParameterCount,
|
||||
name, len(stm.Parameters), len(md.Parameters))
|
||||
}
|
||||
for i := range stm.Parameters {
|
||||
if stm.Parameters[i].Type != md.Parameters[i].Type {
|
||||
return fmt.Errorf("%w: '%s'[%d] (expected %s, got %s)", ErrInvalidParameterType,
|
||||
name, i, stm.Parameters[i].Type, md.Parameters[i].Type)
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, ste := range st.ABI.Events {
|
||||
name := ste.Name
|
||||
ed := m.ABI.GetEvent(name)
|
||||
if ed == nil {
|
||||
return fmt.Errorf("%w: event '%s'", ErrEventMissing, name)
|
||||
} else if len(ste.Parameters) != len(ed.Parameters) {
|
||||
return fmt.Errorf("%w: event '%s' (expected %d, got %d)", ErrInvalidParameterCount,
|
||||
name, len(ste.Parameters), len(ed.Parameters))
|
||||
}
|
||||
for i := range ste.Parameters {
|
||||
if ste.Parameters[i].Type != ed.Parameters[i].Type {
|
||||
return fmt.Errorf("%w: event '%s' (expected %s, got %s)", ErrInvalidParameterType,
|
||||
name, ste.Parameters[i].Type, ed.Parameters[i].Type)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
113
pkg/smartcontract/manifest/standard/comply_test.go
Normal file
113
pkg/smartcontract/manifest/standard/comply_test.go
Normal file
|
@ -0,0 +1,113 @@
|
|||
package standard
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"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/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func fooMethodBarEvent() *manifest.Manifest {
|
||||
return &manifest.Manifest{
|
||||
ABI: manifest.ABI{
|
||||
Methods: []manifest.Method{
|
||||
{
|
||||
Name: "foo",
|
||||
Parameters: []manifest.Parameter{
|
||||
{Type: smartcontract.ByteArrayType},
|
||||
{Type: smartcontract.PublicKeyType},
|
||||
},
|
||||
ReturnType: smartcontract.IntegerType,
|
||||
},
|
||||
},
|
||||
Events: []manifest.Event{
|
||||
{
|
||||
Name: "bar",
|
||||
Parameters: []manifest.Parameter{
|
||||
{Type: smartcontract.StringType},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func TestComplyMissingMethod(t *testing.T) {
|
||||
m := fooMethodBarEvent()
|
||||
m.ABI.GetMethod("foo").Name = "notafoo"
|
||||
err := Comply(m, fooMethodBarEvent())
|
||||
require.True(t, errors.Is(err, ErrMethodMissing))
|
||||
}
|
||||
|
||||
func TestComplyInvalidReturnType(t *testing.T) {
|
||||
m := fooMethodBarEvent()
|
||||
m.ABI.GetMethod("foo").ReturnType = smartcontract.VoidType
|
||||
err := Comply(m, fooMethodBarEvent())
|
||||
require.True(t, errors.Is(err, ErrInvalidReturnType))
|
||||
}
|
||||
|
||||
func TestComplyMethodParameterCount(t *testing.T) {
|
||||
t.Run("Method", func(t *testing.T) {
|
||||
m := fooMethodBarEvent()
|
||||
f := m.ABI.GetMethod("foo")
|
||||
f.Parameters = append(f.Parameters, manifest.Parameter{Type: smartcontract.BoolType})
|
||||
err := Comply(m, fooMethodBarEvent())
|
||||
require.True(t, errors.Is(err, ErrInvalidParameterCount))
|
||||
})
|
||||
t.Run("Event", func(t *testing.T) {
|
||||
m := fooMethodBarEvent()
|
||||
ev := m.ABI.GetEvent("bar")
|
||||
ev.Parameters = append(ev.Parameters[:0])
|
||||
err := Comply(m, fooMethodBarEvent())
|
||||
require.True(t, errors.Is(err, ErrInvalidParameterCount))
|
||||
})
|
||||
}
|
||||
|
||||
func TestComplyParameterType(t *testing.T) {
|
||||
t.Run("Method", func(t *testing.T) {
|
||||
m := fooMethodBarEvent()
|
||||
m.ABI.GetMethod("foo").Parameters[0].Type = smartcontract.InteropInterfaceType
|
||||
err := Comply(m, fooMethodBarEvent())
|
||||
require.True(t, errors.Is(err, ErrInvalidParameterType))
|
||||
})
|
||||
t.Run("Event", func(t *testing.T) {
|
||||
m := fooMethodBarEvent()
|
||||
m.ABI.GetEvent("bar").Parameters[0].Type = smartcontract.InteropInterfaceType
|
||||
err := Comply(m, fooMethodBarEvent())
|
||||
require.True(t, errors.Is(err, ErrInvalidParameterType))
|
||||
})
|
||||
}
|
||||
|
||||
func TestMissingEvent(t *testing.T) {
|
||||
m := fooMethodBarEvent()
|
||||
m.ABI.GetEvent("bar").Name = "notabar"
|
||||
err := Comply(m, fooMethodBarEvent())
|
||||
require.True(t, errors.Is(err, ErrEventMissing))
|
||||
}
|
||||
|
||||
func TestComplyValid(t *testing.T) {
|
||||
m := fooMethodBarEvent()
|
||||
m.ABI.Methods = append(m.ABI.Methods, manifest.Method{
|
||||
Name: "newmethod",
|
||||
Offset: 123,
|
||||
ReturnType: smartcontract.ByteArrayType,
|
||||
})
|
||||
m.ABI.Events = append(m.ABI.Events, manifest.Event{
|
||||
Name: "otherevent",
|
||||
Parameters: []manifest.Parameter{{
|
||||
Name: "names do not matter",
|
||||
Type: smartcontract.IntegerType,
|
||||
}},
|
||||
})
|
||||
require.NoError(t, Comply(m, fooMethodBarEvent()))
|
||||
}
|
||||
|
||||
func TestCheck(t *testing.T) {
|
||||
m := manifest.NewManifest(util.Uint160{}, "Test")
|
||||
require.Error(t, Check(m, manifest.NEP17StandardName))
|
||||
|
||||
require.NoError(t, Check(nep17, manifest.NEP17StandardName))
|
||||
}
|
5
pkg/smartcontract/manifest/standard/doc.go
Normal file
5
pkg/smartcontract/manifest/standard/doc.go
Normal file
|
@ -0,0 +1,5 @@
|
|||
/*
|
||||
Package standard contains interfaces for well-defined standards
|
||||
and function for checking if arbitrary manifest complies with them.
|
||||
*/
|
||||
package standard
|
57
pkg/smartcontract/manifest/standard/nep17.go
Normal file
57
pkg/smartcontract/manifest/standard/nep17.go
Normal file
|
@ -0,0 +1,57 @@
|
|||
package standard
|
||||
|
||||
import (
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
|
||||
)
|
||||
|
||||
var nep17 = &manifest.Manifest{
|
||||
ABI: manifest.ABI{
|
||||
Methods: []manifest.Method{
|
||||
{
|
||||
Name: "balanceOf",
|
||||
Parameters: []manifest.Parameter{
|
||||
{Type: smartcontract.Hash160Type},
|
||||
},
|
||||
ReturnType: smartcontract.IntegerType,
|
||||
},
|
||||
{
|
||||
Name: "decimals",
|
||||
ReturnType: smartcontract.IntegerType,
|
||||
},
|
||||
{
|
||||
Name: "symbol",
|
||||
ReturnType: smartcontract.StringType,
|
||||
},
|
||||
{
|
||||
Name: "totalSupply",
|
||||
ReturnType: smartcontract.IntegerType,
|
||||
},
|
||||
{
|
||||
Name: "transfer",
|
||||
Parameters: []manifest.Parameter{
|
||||
{Type: smartcontract.Hash160Type},
|
||||
{Type: smartcontract.Hash160Type},
|
||||
{Type: smartcontract.IntegerType},
|
||||
{Type: smartcontract.AnyType},
|
||||
},
|
||||
ReturnType: smartcontract.BoolType,
|
||||
},
|
||||
},
|
||||
Events: []manifest.Event{
|
||||
{
|
||||
Name: "Transfer",
|
||||
Parameters: []manifest.Parameter{
|
||||
{Type: smartcontract.Hash160Type},
|
||||
{Type: smartcontract.Hash160Type},
|
||||
{Type: smartcontract.IntegerType},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// IsNEP17 checks if m is NEP-17 compliant.
|
||||
func IsNEP17(m *manifest.Manifest) error {
|
||||
return Comply(m, nep17)
|
||||
}
|
Loading…
Reference in a new issue