forked from TrueCloudLab/neoneo-go
Merge pull request #1722 from nspcc-dev/fix/nep11
smartcontract: add NEP-11 standard check
This commit is contained in:
commit
abee3b5b05
12 changed files with 491 additions and 106 deletions
|
@ -222,13 +222,39 @@ func CompileAndSave(src string, o *Options) ([]byte, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if o.ManifestFile != "" {
|
if o.ManifestFile != "" {
|
||||||
|
m, err := CreateManifest(di, o)
|
||||||
|
if err != nil {
|
||||||
|
return b, err
|
||||||
|
}
|
||||||
|
mData, err := json.Marshal(m)
|
||||||
|
if err != nil {
|
||||||
|
return b, fmt.Errorf("failed to marshal manifest to JSON: %w", err)
|
||||||
|
}
|
||||||
|
return b, ioutil.WriteFile(o.ManifestFile, mData, os.ModePerm)
|
||||||
|
}
|
||||||
|
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateManifest creates manifest and checks that is is valid.
|
||||||
|
func CreateManifest(di *DebugInfo, o *Options) (*manifest.Manifest, error) {
|
||||||
m, err := di.ConvertToManifest(o)
|
m, err := di.ConvertToManifest(o)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return b, fmt.Errorf("failed to convert debug info to manifest: %w", err)
|
return m, fmt.Errorf("failed to convert debug info to manifest: %w", err)
|
||||||
}
|
}
|
||||||
if !o.NoStandardCheck {
|
if !o.NoStandardCheck {
|
||||||
if err := standard.Check(m, o.ContractSupportedStandards...); err != nil {
|
if err := standard.CheckABI(m, o.ContractSupportedStandards...); err != nil {
|
||||||
return b, err
|
return m, err
|
||||||
|
}
|
||||||
|
if m.ABI.GetMethod(manifest.MethodOnNEP11Payment, -1) != nil {
|
||||||
|
if err := standard.CheckABI(m, manifest.NEP11Payable); err != nil {
|
||||||
|
return m, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if m.ABI.GetMethod(manifest.MethodOnNEP17Payment, -1) != nil {
|
||||||
|
if err := standard.CheckABI(m, manifest.NEP17Payable); err != nil {
|
||||||
|
return m, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !o.NoEventsCheck {
|
if !o.NoEventsCheck {
|
||||||
|
@ -253,12 +279,5 @@ func CompileAndSave(src string, o *Options) ([]byte, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
mData, err := json.Marshal(m)
|
return m, nil
|
||||||
if err != nil {
|
|
||||||
return b, fmt.Errorf("failed to marshal manifest to JSON: %w", err)
|
|
||||||
}
|
|
||||||
return b, ioutil.WriteFile(o.ManifestFile, mData, os.ModePerm)
|
|
||||||
}
|
|
||||||
|
|
||||||
return b, nil
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/compiler"
|
"github.com/nspcc-dev/neo-go/pkg/compiler"
|
||||||
|
@ -90,3 +91,37 @@ func compileFile(src string) error {
|
||||||
_, err := compiler.Compile(src, nil)
|
_, err := compiler.Compile(src, nil)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestOnPayableChecks(t *testing.T) {
|
||||||
|
compileAndCheck := func(t *testing.T, src string) error {
|
||||||
|
_, di, err := compiler.CompileWithDebugInfo("payable", strings.NewReader(src))
|
||||||
|
require.NoError(t, err)
|
||||||
|
_, err = compiler.CreateManifest(di, &compiler.Options{})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("NEP-11, good", func(t *testing.T) {
|
||||||
|
src := `package payable
|
||||||
|
import "github.com/nspcc-dev/neo-go/pkg/interop"
|
||||||
|
func OnNEP11Payment(from interop.Hash160, amount int, tokenID []byte) {}`
|
||||||
|
require.NoError(t, compileAndCheck(t, src))
|
||||||
|
})
|
||||||
|
t.Run("NEP-11, bad", func(t *testing.T) {
|
||||||
|
src := `package payable
|
||||||
|
import "github.com/nspcc-dev/neo-go/pkg/interop"
|
||||||
|
func OnNEP11Payment(from interop.Hash160, amount int, oldParam string, tokenID []byte) {}`
|
||||||
|
require.Error(t, compileAndCheck(t, src))
|
||||||
|
})
|
||||||
|
t.Run("NEP-17, good", func(t *testing.T) {
|
||||||
|
src := `package payable
|
||||||
|
import "github.com/nspcc-dev/neo-go/pkg/interop"
|
||||||
|
func OnNEP17Payment(from interop.Hash160, amount int, data interface{}) {}`
|
||||||
|
require.NoError(t, compileAndCheck(t, src))
|
||||||
|
})
|
||||||
|
t.Run("NEP-17, bad", func(t *testing.T) {
|
||||||
|
src := `package payable
|
||||||
|
import "github.com/nspcc-dev/neo-go/pkg/interop"
|
||||||
|
func OnNEP17Payment(from interop.Hash160, amount int, data interface{}, extra int) {}`
|
||||||
|
require.Error(t, compileAndCheck(t, src))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -3,6 +3,8 @@ package native
|
||||||
import (
|
import (
|
||||||
"testing"
|
"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"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -86,3 +88,8 @@ func TestNameService_CheckName(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNameService_NEP11(t *testing.T) {
|
||||||
|
ns := newNameService()
|
||||||
|
require.NoError(t, standard.Check(&ns.Manifest, manifest.NEP11StandardName))
|
||||||
|
}
|
||||||
|
|
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,8 +16,14 @@ const (
|
||||||
|
|
||||||
// NEP10StandardName represents the name of NEP10 smartcontract standard.
|
// NEP10StandardName represents the name of NEP10 smartcontract standard.
|
||||||
NEP10StandardName = "NEP-10"
|
NEP10StandardName = "NEP-10"
|
||||||
|
// NEP11StandardName represents the name of NEP11 smartcontract standard.
|
||||||
|
NEP11StandardName = "NEP-11"
|
||||||
// NEP17StandardName represents the name of NEP17 smartcontract standard.
|
// NEP17StandardName represents the name of NEP17 smartcontract standard.
|
||||||
NEP17StandardName = "NEP-17"
|
NEP17StandardName = "NEP-17"
|
||||||
|
// NEP11Payable represents the name of contract interface which can receive NEP-11 tokens.
|
||||||
|
NEP11Payable = "NEP-11-Payable"
|
||||||
|
// NEP17Payable represents the name of contract interface which can receive NEP-17 tokens.
|
||||||
|
NEP17Payable = "NEP-17-Payable"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Manifest represens contract metadata.
|
// Manifest represens contract metadata.
|
||||||
|
|
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
|
||||||
|
}
|
|
@ -13,21 +13,40 @@ var (
|
||||||
ErrEventMissing = errors.New("event missing")
|
ErrEventMissing = errors.New("event missing")
|
||||||
ErrInvalidReturnType = errors.New("invalid return type")
|
ErrInvalidReturnType = errors.New("invalid return type")
|
||||||
ErrInvalidParameterCount = errors.New("invalid parameter count")
|
ErrInvalidParameterCount = errors.New("invalid parameter count")
|
||||||
|
ErrInvalidParameterName = errors.New("invalid parameter name")
|
||||||
ErrInvalidParameterType = errors.New("invalid parameter type")
|
ErrInvalidParameterType = errors.New("invalid parameter type")
|
||||||
ErrSafeMethodMismatch = errors.New("method has wrong safe flag")
|
ErrSafeMethodMismatch = errors.New("method has wrong safe flag")
|
||||||
)
|
)
|
||||||
|
|
||||||
var checks = map[string]*manifest.Manifest{
|
var checks = map[string][]*Standard{
|
||||||
manifest.NEP17StandardName: nep17,
|
manifest.NEP11StandardName: {nep11NonDivisible, nep11Divisible},
|
||||||
|
manifest.NEP17StandardName: {nep17},
|
||||||
|
manifest.NEP11Payable: {nep11payable},
|
||||||
|
manifest.NEP17Payable: {nep17payable},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check checks if manifest complies with all provided standards.
|
// Check checks if manifest complies with all provided standards.
|
||||||
// Currently only NEP-17 is supported.
|
// Currently only NEP-17 is supported.
|
||||||
func Check(m *manifest.Manifest, standards ...string) error {
|
func Check(m *manifest.Manifest, standards ...string) error {
|
||||||
|
return check(m, true, standards...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckABI is similar to Check but doesn't check parameter names.
|
||||||
|
func CheckABI(m *manifest.Manifest, standards ...string) error {
|
||||||
|
return check(m, false, standards...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func check(m *manifest.Manifest, checkNames bool, standards ...string) error {
|
||||||
for i := range standards {
|
for i := range standards {
|
||||||
s, ok := checks[standards[i]]
|
ss, ok := checks[standards[i]]
|
||||||
if ok {
|
if ok {
|
||||||
if err := Comply(m, s); err != nil {
|
var err error
|
||||||
|
for i := range ss {
|
||||||
|
if err = comply(m, checkNames, ss[i]); err == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
return fmt.Errorf("manifest is not compliant with '%s': %w", standards[i], err)
|
return fmt.Errorf("manifest is not compliant with '%s': %w", standards[i], err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -37,24 +56,24 @@ 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.
|
// Comply if m has all methods and event from st manifest and they have the same signature.
|
||||||
// Parameter names are ignored.
|
// Parameter names are ignored.
|
||||||
func Comply(m, st *manifest.Manifest) error {
|
func Comply(m *manifest.Manifest, st *Standard) error {
|
||||||
|
return comply(m, true, st)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ComplyABI is similar to comply but doesn't check parameter names.
|
||||||
|
func ComplyABI(m *manifest.Manifest, st *Standard) error {
|
||||||
|
return comply(m, false, st)
|
||||||
|
}
|
||||||
|
|
||||||
|
func comply(m *manifest.Manifest, checkNames bool, st *Standard) error {
|
||||||
|
if st.Base != nil {
|
||||||
|
if err := comply(m, checkNames, st.Base); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
for _, stm := range st.ABI.Methods {
|
for _, stm := range st.ABI.Methods {
|
||||||
name := stm.Name
|
if err := checkMethod(m, &stm, false, checkNames); err != nil {
|
||||||
md := m.ABI.GetMethod(name, len(stm.Parameters))
|
return err
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, ste := range st.ABI.Events {
|
for _, ste := range st.ABI.Events {
|
||||||
|
@ -67,11 +86,50 @@ func Comply(m, st *manifest.Manifest) error {
|
||||||
name, len(ste.Parameters), len(ed.Parameters))
|
name, len(ste.Parameters), len(ed.Parameters))
|
||||||
}
|
}
|
||||||
for i := range ste.Parameters {
|
for i := range ste.Parameters {
|
||||||
|
if checkNames && ste.Parameters[i].Name != ed.Parameters[i].Name {
|
||||||
|
return fmt.Errorf("%w: event '%s'[%d] (expected %s, got %s)", ErrInvalidParameterName,
|
||||||
|
name, i, ste.Parameters[i].Name, ed.Parameters[i].Name)
|
||||||
|
}
|
||||||
if ste.Parameters[i].Type != ed.Parameters[i].Type {
|
if ste.Parameters[i].Type != ed.Parameters[i].Type {
|
||||||
return fmt.Errorf("%w: event '%s' (expected %s, got %s)", ErrInvalidParameterType,
|
return fmt.Errorf("%w: event '%s' (expected %s, got %s)", ErrInvalidParameterType,
|
||||||
name, ste.Parameters[i].Type, ed.Parameters[i].Type)
|
name, ste.Parameters[i].Type, ed.Parameters[i].Type)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for _, stm := range st.Optional {
|
||||||
|
if err := checkMethod(m, &stm, true, checkNames); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkMethod(m *manifest.Manifest, expected *manifest.Method,
|
||||||
|
allowMissing bool, checkNames 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 checkNames && expected.Parameters[i].Name != actual.Parameters[i].Name {
|
||||||
|
return fmt.Errorf("%w: '%s'[%d] (expected %s, got %s)", ErrInvalidParameterName,
|
||||||
|
expected.Name, i, expected.Parameters[i].Name, actual.Parameters[i].Name)
|
||||||
|
}
|
||||||
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,14 +38,14 @@ func fooMethodBarEvent() *manifest.Manifest {
|
||||||
func TestComplyMissingMethod(t *testing.T) {
|
func TestComplyMissingMethod(t *testing.T) {
|
||||||
m := fooMethodBarEvent()
|
m := fooMethodBarEvent()
|
||||||
m.ABI.GetMethod("foo", -1).Name = "notafoo"
|
m.ABI.GetMethod("foo", -1).Name = "notafoo"
|
||||||
err := Comply(m, fooMethodBarEvent())
|
err := Comply(m, &Standard{Manifest: *fooMethodBarEvent()})
|
||||||
require.True(t, errors.Is(err, ErrMethodMissing))
|
require.True(t, errors.Is(err, ErrMethodMissing))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestComplyInvalidReturnType(t *testing.T) {
|
func TestComplyInvalidReturnType(t *testing.T) {
|
||||||
m := fooMethodBarEvent()
|
m := fooMethodBarEvent()
|
||||||
m.ABI.GetMethod("foo", -1).ReturnType = smartcontract.VoidType
|
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))
|
require.True(t, errors.Is(err, ErrInvalidReturnType))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,14 +54,14 @@ func TestComplyMethodParameterCount(t *testing.T) {
|
||||||
m := fooMethodBarEvent()
|
m := fooMethodBarEvent()
|
||||||
f := m.ABI.GetMethod("foo", -1)
|
f := m.ABI.GetMethod("foo", -1)
|
||||||
f.Parameters = append(f.Parameters, manifest.Parameter{Type: smartcontract.BoolType})
|
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))
|
require.True(t, errors.Is(err, ErrMethodMissing))
|
||||||
})
|
})
|
||||||
t.Run("Event", func(t *testing.T) {
|
t.Run("Event", func(t *testing.T) {
|
||||||
m := fooMethodBarEvent()
|
m := fooMethodBarEvent()
|
||||||
ev := m.ABI.GetEvent("bar")
|
ev := m.ABI.GetEvent("bar")
|
||||||
ev.Parameters = append(ev.Parameters[:0])
|
ev.Parameters = append(ev.Parameters[:0])
|
||||||
err := Comply(m, fooMethodBarEvent())
|
err := Comply(m, &Standard{Manifest: *fooMethodBarEvent()})
|
||||||
require.True(t, errors.Is(err, ErrInvalidParameterCount))
|
require.True(t, errors.Is(err, ErrInvalidParameterCount))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -70,28 +70,47 @@ func TestComplyParameterType(t *testing.T) {
|
||||||
t.Run("Method", func(t *testing.T) {
|
t.Run("Method", func(t *testing.T) {
|
||||||
m := fooMethodBarEvent()
|
m := fooMethodBarEvent()
|
||||||
m.ABI.GetMethod("foo", -1).Parameters[0].Type = smartcontract.InteropInterfaceType
|
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))
|
require.True(t, errors.Is(err, ErrInvalidParameterType))
|
||||||
})
|
})
|
||||||
t.Run("Event", func(t *testing.T) {
|
t.Run("Event", func(t *testing.T) {
|
||||||
m := fooMethodBarEvent()
|
m := fooMethodBarEvent()
|
||||||
m.ABI.GetEvent("bar").Parameters[0].Type = smartcontract.InteropInterfaceType
|
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))
|
require.True(t, errors.Is(err, ErrInvalidParameterType))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestComplyParameterName(t *testing.T) {
|
||||||
|
t.Run("Method", func(t *testing.T) {
|
||||||
|
m := fooMethodBarEvent()
|
||||||
|
m.ABI.GetMethod("foo", -1).Parameters[0].Name = "hehe"
|
||||||
|
s := &Standard{Manifest: *fooMethodBarEvent()}
|
||||||
|
err := Comply(m, s)
|
||||||
|
require.True(t, errors.Is(err, ErrInvalidParameterName))
|
||||||
|
require.NoError(t, ComplyABI(m, s))
|
||||||
|
})
|
||||||
|
t.Run("Event", func(t *testing.T) {
|
||||||
|
m := fooMethodBarEvent()
|
||||||
|
m.ABI.GetEvent("bar").Parameters[0].Name = "hehe"
|
||||||
|
s := &Standard{Manifest: *fooMethodBarEvent()}
|
||||||
|
err := Comply(m, s)
|
||||||
|
require.True(t, errors.Is(err, ErrInvalidParameterName))
|
||||||
|
require.NoError(t, ComplyABI(m, s))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestMissingEvent(t *testing.T) {
|
func TestMissingEvent(t *testing.T) {
|
||||||
m := fooMethodBarEvent()
|
m := fooMethodBarEvent()
|
||||||
m.ABI.GetEvent("bar").Name = "notabar"
|
m.ABI.GetEvent("bar").Name = "notabar"
|
||||||
err := Comply(m, fooMethodBarEvent())
|
err := Comply(m, &Standard{Manifest: *fooMethodBarEvent()})
|
||||||
require.True(t, errors.Is(err, ErrEventMissing))
|
require.True(t, errors.Is(err, ErrEventMissing))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSafeFlag(t *testing.T) {
|
func TestSafeFlag(t *testing.T) {
|
||||||
m := fooMethodBarEvent()
|
m := fooMethodBarEvent()
|
||||||
m.ABI.GetMethod("foo", -1).Safe = false
|
m.ABI.GetMethod("foo", -1).Safe = false
|
||||||
err := Comply(m, fooMethodBarEvent())
|
err := Comply(m, &Standard{Manifest: *fooMethodBarEvent()})
|
||||||
require.True(t, errors.Is(err, ErrSafeMethodMismatch))
|
require.True(t, errors.Is(err, ErrSafeMethodMismatch))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -109,12 +128,52 @@ func TestComplyValid(t *testing.T) {
|
||||||
Type: smartcontract.IntegerType,
|
Type: smartcontract.IntegerType,
|
||||||
}},
|
}},
|
||||||
})
|
})
|
||||||
require.NoError(t, Comply(m, fooMethodBarEvent()))
|
require.NoError(t, Comply(m, &Standard{Manifest: *fooMethodBarEvent()}))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCheck(t *testing.T) {
|
func TestCheck(t *testing.T) {
|
||||||
m := manifest.NewManifest("Test")
|
m := manifest.NewManifest("Test")
|
||||||
require.Error(t, Check(m, manifest.NEP17StandardName))
|
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))
|
||||||
|
require.NoError(t, CheckABI(m, manifest.NEP17StandardName))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOptional(t *testing.T) {
|
||||||
|
var m Standard
|
||||||
|
m.Optional = []manifest.Method{{
|
||||||
|
Name: "optMet",
|
||||||
|
Parameters: []manifest.Parameter{{Type: smartcontract.ByteArrayType}},
|
||||||
|
ReturnType: smartcontract.IntegerType,
|
||||||
|
}}
|
||||||
|
|
||||||
|
t.Run("wrong parameter count, do not check", func(t *testing.T) {
|
||||||
|
var actual manifest.Manifest
|
||||||
|
actual.ABI.Methods = []manifest.Method{{
|
||||||
|
Name: "optMet",
|
||||||
|
ReturnType: smartcontract.IntegerType,
|
||||||
|
}}
|
||||||
|
require.NoError(t, Comply(&actual, &m))
|
||||||
|
})
|
||||||
|
t.Run("good parameter count, bad return", func(t *testing.T) {
|
||||||
|
var actual manifest.Manifest
|
||||||
|
actual.ABI.Methods = []manifest.Method{{
|
||||||
|
Name: "optMet",
|
||||||
|
Parameters: []manifest.Parameter{{Type: smartcontract.ArrayType}},
|
||||||
|
ReturnType: smartcontract.IntegerType,
|
||||||
|
}}
|
||||||
|
require.Error(t, Comply(&actual, &m))
|
||||||
|
})
|
||||||
|
t.Run("good parameter count, good return", func(t *testing.T) {
|
||||||
|
var actual manifest.Manifest
|
||||||
|
actual.ABI.Methods = []manifest.Method{{
|
||||||
|
Name: "optMet",
|
||||||
|
Parameters: []manifest.Parameter{{Type: smartcontract.ByteArrayType}},
|
||||||
|
ReturnType: smartcontract.IntegerType,
|
||||||
|
}}
|
||||||
|
require.NoError(t, Comply(&actual, &m))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
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{
|
||||||
|
{Name: "owner", Type: smartcontract.Hash160Type},
|
||||||
|
},
|
||||||
|
ReturnType: smartcontract.IntegerType,
|
||||||
|
Safe: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "tokensOf",
|
||||||
|
ReturnType: smartcontract.AnyType, // Iterator
|
||||||
|
Parameters: []manifest.Parameter{
|
||||||
|
{Name: "owner", Type: smartcontract.Hash160Type},
|
||||||
|
},
|
||||||
|
Safe: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "transfer",
|
||||||
|
Parameters: []manifest.Parameter{
|
||||||
|
{Name: "to", Type: smartcontract.Hash160Type},
|
||||||
|
{Name: "tokenId", Type: smartcontract.ByteArrayType},
|
||||||
|
},
|
||||||
|
ReturnType: smartcontract.BoolType,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Events: []manifest.Event{
|
||||||
|
{
|
||||||
|
Name: "Transfer",
|
||||||
|
Parameters: []manifest.Parameter{
|
||||||
|
{Name: "from", Type: smartcontract.Hash160Type},
|
||||||
|
{Name: "to", Type: smartcontract.Hash160Type},
|
||||||
|
{Name: "amount", Type: smartcontract.IntegerType},
|
||||||
|
{Name: "tokenId", Type: smartcontract.ByteArrayType},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Optional: []manifest.Method{
|
||||||
|
{
|
||||||
|
Name: "properties",
|
||||||
|
Parameters: []manifest.Parameter{
|
||||||
|
{Name: "tokenId", 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{
|
||||||
|
{Name: "tokenId", 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{
|
||||||
|
{Name: "owner", Type: smartcontract.Hash160Type},
|
||||||
|
{Name: "tokenId", Type: smartcontract.ByteArrayType},
|
||||||
|
},
|
||||||
|
ReturnType: smartcontract.IntegerType,
|
||||||
|
Safe: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "ownerOf",
|
||||||
|
Parameters: []manifest.Parameter{
|
||||||
|
{Name: "tokenId", Type: smartcontract.ByteArrayType},
|
||||||
|
},
|
||||||
|
ReturnType: smartcontract.AnyType,
|
||||||
|
Safe: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "transfer",
|
||||||
|
Parameters: []manifest.Parameter{
|
||||||
|
{Name: "from", Type: smartcontract.Hash160Type},
|
||||||
|
{Name: "to", Type: smartcontract.Hash160Type},
|
||||||
|
{Name: "amount", Type: smartcontract.IntegerType},
|
||||||
|
{Name: "tokenId", Type: smartcontract.ByteArrayType},
|
||||||
|
},
|
||||||
|
ReturnType: smartcontract.BoolType,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
|
@ -5,39 +5,26 @@ import (
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
|
||||||
)
|
)
|
||||||
|
|
||||||
var nep17 = &manifest.Manifest{
|
var nep17 = &Standard{
|
||||||
|
Base: decimalTokenBase,
|
||||||
|
Manifest: manifest.Manifest{
|
||||||
ABI: manifest.ABI{
|
ABI: manifest.ABI{
|
||||||
Methods: []manifest.Method{
|
Methods: []manifest.Method{
|
||||||
{
|
{
|
||||||
Name: "balanceOf",
|
Name: "balanceOf",
|
||||||
Parameters: []manifest.Parameter{
|
Parameters: []manifest.Parameter{
|
||||||
{Type: smartcontract.Hash160Type},
|
{Name: "account", Type: smartcontract.Hash160Type},
|
||||||
},
|
},
|
||||||
ReturnType: smartcontract.IntegerType,
|
ReturnType: smartcontract.IntegerType,
|
||||||
Safe: true,
|
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",
|
Name: "transfer",
|
||||||
Parameters: []manifest.Parameter{
|
Parameters: []manifest.Parameter{
|
||||||
{Type: smartcontract.Hash160Type},
|
{Name: "from", Type: smartcontract.Hash160Type},
|
||||||
{Type: smartcontract.Hash160Type},
|
{Name: "to", Type: smartcontract.Hash160Type},
|
||||||
{Type: smartcontract.IntegerType},
|
{Name: "amount", Type: smartcontract.IntegerType},
|
||||||
{Type: smartcontract.AnyType},
|
{Name: "data", Type: smartcontract.AnyType},
|
||||||
},
|
},
|
||||||
ReturnType: smartcontract.BoolType,
|
ReturnType: smartcontract.BoolType,
|
||||||
},
|
},
|
||||||
|
@ -46,16 +33,12 @@ var nep17 = &manifest.Manifest{
|
||||||
{
|
{
|
||||||
Name: "Transfer",
|
Name: "Transfer",
|
||||||
Parameters: []manifest.Parameter{
|
Parameters: []manifest.Parameter{
|
||||||
{Type: smartcontract.Hash160Type},
|
{Name: "from", Type: smartcontract.Hash160Type},
|
||||||
{Type: smartcontract.Hash160Type},
|
{Name: "to", Type: smartcontract.Hash160Type},
|
||||||
{Type: smartcontract.IntegerType},
|
{Name: "amount", Type: smartcontract.IntegerType},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsNEP17 checks if m is NEP-17 compliant.
|
|
||||||
func IsNEP17(m *manifest.Manifest) error {
|
|
||||||
return Comply(m, nep17)
|
|
||||||
}
|
|
||||||
|
|
38
pkg/smartcontract/manifest/standard/payable.go
Normal file
38
pkg/smartcontract/manifest/standard/payable.go
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
package standard
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
|
||||||
|
)
|
||||||
|
|
||||||
|
var nep11payable = &Standard{
|
||||||
|
Manifest: manifest.Manifest{
|
||||||
|
ABI: manifest.ABI{
|
||||||
|
Methods: []manifest.Method{{
|
||||||
|
Name: manifest.MethodOnNEP11Payment,
|
||||||
|
Parameters: []manifest.Parameter{
|
||||||
|
{Name: "from", Type: smartcontract.Hash160Type},
|
||||||
|
{Name: "amount", Type: smartcontract.IntegerType},
|
||||||
|
{Name: "tokenid", Type: smartcontract.ByteArrayType},
|
||||||
|
},
|
||||||
|
ReturnType: smartcontract.VoidType,
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var nep17payable = &Standard{
|
||||||
|
Manifest: manifest.Manifest{
|
||||||
|
ABI: manifest.ABI{
|
||||||
|
Methods: []manifest.Method{{
|
||||||
|
Name: manifest.MethodOnNEP17Payment,
|
||||||
|
Parameters: []manifest.Parameter{
|
||||||
|
{Name: "from", Type: smartcontract.Hash160Type},
|
||||||
|
{Name: "amount", Type: smartcontract.IntegerType},
|
||||||
|
{Name: "data", Type: smartcontract.AnyType},
|
||||||
|
},
|
||||||
|
ReturnType: smartcontract.VoidType,
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
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