smartcontract: add NEP-11 standard check
The only method missing is name, because it is provided in manifest.
This commit is contained in:
parent
fe918e28f2
commit
9c0bbd0bfd
8 changed files with 273 additions and 72 deletions
14
pkg/core/native/nonfungible_test.go
Normal file
14
pkg/core/native/nonfungible_test.go
Normal file
|
@ -0,0 +1,14 @@
|
|||
package native
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest/standard"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestNonfungibleNEP11(t *testing.T) {
|
||||
n := newNonFungible("NFToken", -100, "SYM", 1)
|
||||
require.NoError(t, standard.Check(&n.ContractMD.Manifest, manifest.NEP11StandardName))
|
||||
}
|
|
@ -16,6 +16,8 @@ const (
|
|||
|
||||
// NEP10StandardName represents the name of NEP10 smartcontract standard.
|
||||
NEP10StandardName = "NEP-10"
|
||||
// NEP11StandardName represents the name of NEP11 smartcontract standard.
|
||||
NEP11StandardName = "NEP-11"
|
||||
// NEP17StandardName represents the name of NEP17 smartcontract standard.
|
||||
NEP17StandardName = "NEP-17"
|
||||
)
|
||||
|
|
15
pkg/smartcontract/manifest/standard/check.go
Normal file
15
pkg/smartcontract/manifest/standard/check.go
Normal file
|
@ -0,0 +1,15 @@
|
|||
package standard
|
||||
|
||||
import "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
|
||||
|
||||
// Standard represents smart-contract standard.
|
||||
type Standard struct {
|
||||
// Manifest describes mandatory methods and events.
|
||||
manifest.Manifest
|
||||
// Base contains base standard.
|
||||
Base *Standard
|
||||
// Optional contains optional contract methods.
|
||||
// If contract contains method with the same name and parameter count,
|
||||
// it must have signature declared by this contract.
|
||||
Optional []manifest.Method
|
||||
}
|
|
@ -17,17 +17,24 @@ var (
|
|||
ErrSafeMethodMismatch = errors.New("method has wrong safe flag")
|
||||
)
|
||||
|
||||
var checks = map[string]*manifest.Manifest{
|
||||
manifest.NEP17StandardName: nep17,
|
||||
var checks = map[string][]*Standard{
|
||||
manifest.NEP11StandardName: {nep11NonDivisible, nep11Divisible},
|
||||
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]]
|
||||
ss, ok := checks[standards[i]]
|
||||
if ok {
|
||||
if err := Comply(m, s); err != nil {
|
||||
var err error
|
||||
for i := range ss {
|
||||
if err = Comply(m, ss[i]); err == nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("manifest is not compliant with '%s': %w", standards[i], err)
|
||||
}
|
||||
}
|
||||
|
@ -37,24 +44,15 @@ func Check(m *manifest.Manifest, standards ...string) error {
|
|||
|
||||
// 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 {
|
||||
func Comply(m *manifest.Manifest, st *Standard) error {
|
||||
if st.Base != nil {
|
||||
if err := Comply(m, st.Base); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for _, stm := range st.ABI.Methods {
|
||||
name := stm.Name
|
||||
md := m.ABI.GetMethod(name, len(stm.Parameters))
|
||||
if md == nil {
|
||||
return fmt.Errorf("%w: '%s' with %d parameters", ErrMethodMissing, name, len(stm.Parameters))
|
||||
} else if stm.ReturnType != md.ReturnType {
|
||||
return fmt.Errorf("%w: '%s' (expected %s, got %s)", ErrInvalidReturnType,
|
||||
name, stm.ReturnType, md.ReturnType)
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
if stm.Safe != md.Safe {
|
||||
return fmt.Errorf("%w: expected %t", ErrSafeMethodMismatch, stm.Safe)
|
||||
if err := checkMethod(m, &stm, false); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for _, ste := range st.ABI.Events {
|
||||
|
@ -73,5 +71,35 @@ func Comply(m, st *manifest.Manifest) error {
|
|||
}
|
||||
}
|
||||
}
|
||||
for _, stm := range st.Optional {
|
||||
if err := checkMethod(m, &stm, true); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkMethod(m *manifest.Manifest, expected *manifest.Method, allowMissing bool) error {
|
||||
actual := m.ABI.GetMethod(expected.Name, len(expected.Parameters))
|
||||
if actual == nil {
|
||||
if allowMissing {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("%w: '%s' with %d parameters", ErrMethodMissing,
|
||||
expected.Name, len(expected.Parameters))
|
||||
}
|
||||
if expected.ReturnType != actual.ReturnType {
|
||||
return fmt.Errorf("%w: '%s' (expected %s, got %s)", ErrInvalidReturnType,
|
||||
expected.Name, expected.ReturnType, actual.ReturnType)
|
||||
}
|
||||
for i := range expected.Parameters {
|
||||
if expected.Parameters[i].Type != actual.Parameters[i].Type {
|
||||
return fmt.Errorf("%w: '%s'[%d] (expected %s, got %s)", ErrInvalidParameterType,
|
||||
expected.Name, i, expected.Parameters[i].Type, actual.Parameters[i].Type)
|
||||
}
|
||||
}
|
||||
if expected.Safe != actual.Safe {
|
||||
return fmt.Errorf("%w: expected %t", ErrSafeMethodMismatch, expected.Safe)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -38,14 +38,14 @@ func fooMethodBarEvent() *manifest.Manifest {
|
|||
func TestComplyMissingMethod(t *testing.T) {
|
||||
m := fooMethodBarEvent()
|
||||
m.ABI.GetMethod("foo", -1).Name = "notafoo"
|
||||
err := Comply(m, fooMethodBarEvent())
|
||||
err := Comply(m, &Standard{Manifest: *fooMethodBarEvent()})
|
||||
require.True(t, errors.Is(err, ErrMethodMissing))
|
||||
}
|
||||
|
||||
func TestComplyInvalidReturnType(t *testing.T) {
|
||||
m := fooMethodBarEvent()
|
||||
m.ABI.GetMethod("foo", -1).ReturnType = smartcontract.VoidType
|
||||
err := Comply(m, fooMethodBarEvent())
|
||||
err := Comply(m, &Standard{Manifest: *fooMethodBarEvent()})
|
||||
require.True(t, errors.Is(err, ErrInvalidReturnType))
|
||||
}
|
||||
|
||||
|
@ -54,14 +54,14 @@ func TestComplyMethodParameterCount(t *testing.T) {
|
|||
m := fooMethodBarEvent()
|
||||
f := m.ABI.GetMethod("foo", -1)
|
||||
f.Parameters = append(f.Parameters, manifest.Parameter{Type: smartcontract.BoolType})
|
||||
err := Comply(m, fooMethodBarEvent())
|
||||
err := Comply(m, &Standard{Manifest: *fooMethodBarEvent()})
|
||||
require.True(t, errors.Is(err, ErrMethodMissing))
|
||||
})
|
||||
t.Run("Event", func(t *testing.T) {
|
||||
m := fooMethodBarEvent()
|
||||
ev := m.ABI.GetEvent("bar")
|
||||
ev.Parameters = append(ev.Parameters[:0])
|
||||
err := Comply(m, fooMethodBarEvent())
|
||||
err := Comply(m, &Standard{Manifest: *fooMethodBarEvent()})
|
||||
require.True(t, errors.Is(err, ErrInvalidParameterCount))
|
||||
})
|
||||
}
|
||||
|
@ -70,13 +70,13 @@ func TestComplyParameterType(t *testing.T) {
|
|||
t.Run("Method", func(t *testing.T) {
|
||||
m := fooMethodBarEvent()
|
||||
m.ABI.GetMethod("foo", -1).Parameters[0].Type = smartcontract.InteropInterfaceType
|
||||
err := Comply(m, fooMethodBarEvent())
|
||||
err := Comply(m, &Standard{Manifest: *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())
|
||||
err := Comply(m, &Standard{Manifest: *fooMethodBarEvent()})
|
||||
require.True(t, errors.Is(err, ErrInvalidParameterType))
|
||||
})
|
||||
}
|
||||
|
@ -84,14 +84,14 @@ func TestComplyParameterType(t *testing.T) {
|
|||
func TestMissingEvent(t *testing.T) {
|
||||
m := fooMethodBarEvent()
|
||||
m.ABI.GetEvent("bar").Name = "notabar"
|
||||
err := Comply(m, fooMethodBarEvent())
|
||||
err := Comply(m, &Standard{Manifest: *fooMethodBarEvent()})
|
||||
require.True(t, errors.Is(err, ErrEventMissing))
|
||||
}
|
||||
|
||||
func TestSafeFlag(t *testing.T) {
|
||||
m := fooMethodBarEvent()
|
||||
m.ABI.GetMethod("foo", -1).Safe = false
|
||||
err := Comply(m, fooMethodBarEvent())
|
||||
err := Comply(m, &Standard{Manifest: *fooMethodBarEvent()})
|
||||
require.True(t, errors.Is(err, ErrSafeMethodMismatch))
|
||||
}
|
||||
|
||||
|
@ -109,12 +109,15 @@ func TestComplyValid(t *testing.T) {
|
|||
Type: smartcontract.IntegerType,
|
||||
}},
|
||||
})
|
||||
require.NoError(t, Comply(m, fooMethodBarEvent()))
|
||||
require.NoError(t, Comply(m, &Standard{Manifest: *fooMethodBarEvent()}))
|
||||
}
|
||||
|
||||
func TestCheck(t *testing.T) {
|
||||
m := manifest.NewManifest("Test")
|
||||
require.Error(t, Check(m, manifest.NEP17StandardName))
|
||||
|
||||
require.NoError(t, Check(nep17, manifest.NEP17StandardName))
|
||||
m.ABI.Methods = append(m.ABI.Methods, decimalTokenBase.ABI.Methods...)
|
||||
m.ABI.Methods = append(m.ABI.Methods, nep17.ABI.Methods...)
|
||||
m.ABI.Events = append(m.ABI.Events, nep17.ABI.Events...)
|
||||
require.NoError(t, Check(m, manifest.NEP17StandardName))
|
||||
}
|
||||
|
|
121
pkg/smartcontract/manifest/standard/nep11.go
Normal file
121
pkg/smartcontract/manifest/standard/nep11.go
Normal file
|
@ -0,0 +1,121 @@
|
|||
package standard
|
||||
|
||||
import (
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
|
||||
)
|
||||
|
||||
var nep11Base = &Standard{
|
||||
Base: decimalTokenBase,
|
||||
Manifest: manifest.Manifest{
|
||||
ABI: manifest.ABI{
|
||||
Methods: []manifest.Method{
|
||||
{
|
||||
Name: "balanceOf",
|
||||
Parameters: []manifest.Parameter{
|
||||
{Type: smartcontract.Hash160Type},
|
||||
},
|
||||
ReturnType: smartcontract.IntegerType,
|
||||
Safe: true,
|
||||
},
|
||||
{
|
||||
Name: "tokensOf",
|
||||
ReturnType: smartcontract.AnyType, // Iterator
|
||||
Parameters: []manifest.Parameter{
|
||||
{Type: smartcontract.Hash160Type},
|
||||
},
|
||||
Safe: true,
|
||||
},
|
||||
{
|
||||
Name: "transfer",
|
||||
Parameters: []manifest.Parameter{
|
||||
{Type: smartcontract.Hash160Type},
|
||||
{Type: smartcontract.ByteArrayType},
|
||||
},
|
||||
ReturnType: smartcontract.BoolType,
|
||||
},
|
||||
},
|
||||
Events: []manifest.Event{
|
||||
{
|
||||
Name: "Transfer",
|
||||
Parameters: []manifest.Parameter{
|
||||
{Type: smartcontract.Hash160Type},
|
||||
{Type: smartcontract.Hash160Type},
|
||||
{Type: smartcontract.IntegerType},
|
||||
{Type: smartcontract.ByteArrayType},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Optional: []manifest.Method{
|
||||
{
|
||||
Name: "properties",
|
||||
Parameters: []manifest.Parameter{
|
||||
{Type: smartcontract.ByteArrayType},
|
||||
},
|
||||
ReturnType: smartcontract.MapType,
|
||||
Safe: true,
|
||||
},
|
||||
{
|
||||
Name: "tokens",
|
||||
ReturnType: smartcontract.AnyType,
|
||||
Safe: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var nep11NonDivisible = &Standard{
|
||||
Base: nep11Base,
|
||||
Manifest: manifest.Manifest{
|
||||
ABI: manifest.ABI{
|
||||
Methods: []manifest.Method{
|
||||
{
|
||||
Name: "ownerOf",
|
||||
Parameters: []manifest.Parameter{
|
||||
{Type: smartcontract.ByteArrayType},
|
||||
},
|
||||
ReturnType: smartcontract.Hash160Type,
|
||||
Safe: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var nep11Divisible = &Standard{
|
||||
Base: nep11Base,
|
||||
Manifest: manifest.Manifest{
|
||||
ABI: manifest.ABI{
|
||||
Methods: []manifest.Method{
|
||||
{
|
||||
Name: "balanceOf",
|
||||
Parameters: []manifest.Parameter{
|
||||
{Type: smartcontract.Hash160Type},
|
||||
{Type: smartcontract.ByteArrayType},
|
||||
},
|
||||
ReturnType: smartcontract.IntegerType,
|
||||
Safe: true,
|
||||
},
|
||||
{
|
||||
Name: "ownerOf",
|
||||
Parameters: []manifest.Parameter{
|
||||
{Type: smartcontract.ByteArrayType},
|
||||
},
|
||||
ReturnType: smartcontract.AnyType,
|
||||
Safe: true,
|
||||
},
|
||||
{
|
||||
Name: "transfer",
|
||||
Parameters: []manifest.Parameter{
|
||||
{Type: smartcontract.Hash160Type},
|
||||
{Type: smartcontract.Hash160Type},
|
||||
{Type: smartcontract.IntegerType},
|
||||
{Type: smartcontract.ByteArrayType},
|
||||
},
|
||||
ReturnType: smartcontract.BoolType,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
|
@ -5,7 +5,9 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
|
||||
)
|
||||
|
||||
var nep17 = &manifest.Manifest{
|
||||
var nep17 = &Standard{
|
||||
Base: decimalTokenBase,
|
||||
Manifest: manifest.Manifest{
|
||||
ABI: manifest.ABI{
|
||||
Methods: []manifest.Method{
|
||||
{
|
||||
|
@ -16,21 +18,6 @@ var nep17 = &manifest.Manifest{
|
|||
ReturnType: smartcontract.IntegerType,
|
||||
Safe: true,
|
||||
},
|
||||
{
|
||||
Name: "decimals",
|
||||
ReturnType: smartcontract.IntegerType,
|
||||
Safe: true,
|
||||
},
|
||||
{
|
||||
Name: "symbol",
|
||||
ReturnType: smartcontract.StringType,
|
||||
Safe: true,
|
||||
},
|
||||
{
|
||||
Name: "totalSupply",
|
||||
ReturnType: smartcontract.IntegerType,
|
||||
Safe: true,
|
||||
},
|
||||
{
|
||||
Name: "transfer",
|
||||
Parameters: []manifest.Parameter{
|
||||
|
@ -53,6 +40,7 @@ var nep17 = &manifest.Manifest{
|
|||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// IsNEP17 checks if m is NEP-17 compliant.
|
||||
|
|
30
pkg/smartcontract/manifest/standard/token.go
Normal file
30
pkg/smartcontract/manifest/standard/token.go
Normal file
|
@ -0,0 +1,30 @@
|
|||
package standard
|
||||
|
||||
import (
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
|
||||
)
|
||||
|
||||
var decimalTokenBase = &Standard{
|
||||
Manifest: manifest.Manifest{
|
||||
ABI: manifest.ABI{
|
||||
Methods: []manifest.Method{
|
||||
{
|
||||
Name: "decimals",
|
||||
ReturnType: smartcontract.IntegerType,
|
||||
Safe: true,
|
||||
},
|
||||
{
|
||||
Name: "symbol",
|
||||
ReturnType: smartcontract.StringType,
|
||||
Safe: true,
|
||||
},
|
||||
{
|
||||
Name: "totalSupply",
|
||||
ReturnType: smartcontract.IntegerType,
|
||||
Safe: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
Loading…
Reference in a new issue