diff --git a/pkg/core/native/nonfungible_test.go b/pkg/core/native/nonfungible_test.go new file mode 100644 index 000000000..9b7e9a25a --- /dev/null +++ b/pkg/core/native/nonfungible_test.go @@ -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)) +} diff --git a/pkg/smartcontract/manifest/manifest.go b/pkg/smartcontract/manifest/manifest.go index 31a3011a4..4603cac00 100644 --- a/pkg/smartcontract/manifest/manifest.go +++ b/pkg/smartcontract/manifest/manifest.go @@ -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" ) diff --git a/pkg/smartcontract/manifest/standard/check.go b/pkg/smartcontract/manifest/standard/check.go new file mode 100644 index 000000000..2957c265d --- /dev/null +++ b/pkg/smartcontract/manifest/standard/check.go @@ -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 +} diff --git a/pkg/smartcontract/manifest/standard/comply.go b/pkg/smartcontract/manifest/standard/comply.go index 1d03c55f1..fbd6f4625 100644 --- a/pkg/smartcontract/manifest/standard/comply.go +++ b/pkg/smartcontract/manifest/standard/comply.go @@ -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 } diff --git a/pkg/smartcontract/manifest/standard/comply_test.go b/pkg/smartcontract/manifest/standard/comply_test.go index da05947c5..9d2f6bf76 100644 --- a/pkg/smartcontract/manifest/standard/comply_test.go +++ b/pkg/smartcontract/manifest/standard/comply_test.go @@ -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)) } diff --git a/pkg/smartcontract/manifest/standard/nep11.go b/pkg/smartcontract/manifest/standard/nep11.go new file mode 100644 index 000000000..676209b1d --- /dev/null +++ b/pkg/smartcontract/manifest/standard/nep11.go @@ -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, + }, + }, + }, + }, +} diff --git a/pkg/smartcontract/manifest/standard/nep17.go b/pkg/smartcontract/manifest/standard/nep17.go index 098ce6cfc..685afba3d 100644 --- a/pkg/smartcontract/manifest/standard/nep17.go +++ b/pkg/smartcontract/manifest/standard/nep17.go @@ -5,50 +5,38 @@ import ( "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}, +var nep17 = &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, }, - 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{ - {Type: smartcontract.Hash160Type}, - {Type: smartcontract.Hash160Type}, - {Type: smartcontract.IntegerType}, - {Type: smartcontract.AnyType}, + { + Name: "transfer", + Parameters: []manifest.Parameter{ + {Type: smartcontract.Hash160Type}, + {Type: smartcontract.Hash160Type}, + {Type: smartcontract.IntegerType}, + {Type: smartcontract.AnyType}, + }, + ReturnType: smartcontract.BoolType, }, - ReturnType: smartcontract.BoolType, }, - }, - Events: []manifest.Event{ - { - Name: "Transfer", - Parameters: []manifest.Parameter{ - {Type: smartcontract.Hash160Type}, - {Type: smartcontract.Hash160Type}, - {Type: smartcontract.IntegerType}, + Events: []manifest.Event{ + { + Name: "Transfer", + Parameters: []manifest.Parameter{ + {Type: smartcontract.Hash160Type}, + {Type: smartcontract.Hash160Type}, + {Type: smartcontract.IntegerType}, + }, }, }, }, diff --git a/pkg/smartcontract/manifest/standard/token.go b/pkg/smartcontract/manifest/standard/token.go new file mode 100644 index 000000000..877050131 --- /dev/null +++ b/pkg/smartcontract/manifest/standard/token.go @@ -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, + }, + }, + }, + }, +}