diff --git a/pkg/compiler/compiler.go b/pkg/compiler/compiler.go index 8086006ef..0545c716e 100644 --- a/pkg/compiler/compiler.go +++ b/pkg/compiler/compiler.go @@ -222,36 +222,9 @@ func CompileAndSave(src string, o *Options) ([]byte, error) { } if o.ManifestFile != "" { - m, err := di.ConvertToManifest(o) + m, err := CreateManifest(di, o) if err != nil { - return b, fmt.Errorf("failed to convert debug info to manifest: %w", err) - } - if !o.NoStandardCheck { - if err := standard.CheckABI(m, o.ContractSupportedStandards...); err != nil { - return b, err - } - } - if !o.NoEventsCheck { - for name := range di.EmittedEvents { - ev := m.ABI.GetEvent(name) - if ev == nil { - return nil, fmt.Errorf("event '%s' is emitted but not specified in manifest", name) - } - argsList := di.EmittedEvents[name] - for i := range argsList { - if len(argsList[i]) != len(ev.Parameters) { - return nil, fmt.Errorf("event '%s' should have %d parameters but has %d", - name, len(ev.Parameters), len(argsList[i])) - } - for j := range ev.Parameters { - expected := ev.Parameters[j].Type.String() - if argsList[i][j] != expected { - return nil, fmt.Errorf("event '%s' should have '%s' as type of %d parameter, "+ - "got: %s", name, expected, j+1, argsList[i][j]) - } - } - } - } + return b, err } mData, err := json.Marshal(m) if err != nil { @@ -262,3 +235,49 @@ func CompileAndSave(src string, o *Options) ([]byte, error) { 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) + if err != nil { + return m, fmt.Errorf("failed to convert debug info to manifest: %w", err) + } + if !o.NoStandardCheck { + if err := standard.CheckABI(m, o.ContractSupportedStandards...); err != nil { + 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 { + for name := range di.EmittedEvents { + ev := m.ABI.GetEvent(name) + if ev == nil { + return nil, fmt.Errorf("event '%s' is emitted but not specified in manifest", name) + } + argsList := di.EmittedEvents[name] + for i := range argsList { + if len(argsList[i]) != len(ev.Parameters) { + return nil, fmt.Errorf("event '%s' should have %d parameters but has %d", + name, len(ev.Parameters), len(argsList[i])) + } + for j := range ev.Parameters { + expected := ev.Parameters[j].Type.String() + if argsList[i][j] != expected { + return nil, fmt.Errorf("event '%s' should have '%s' as type of %d parameter, "+ + "got: %s", name, expected, j+1, argsList[i][j]) + } + } + } + } + } + return m, nil +} diff --git a/pkg/compiler/compiler_test.go b/pkg/compiler/compiler_test.go index c9aad096d..5ab8e614f 100644 --- a/pkg/compiler/compiler_test.go +++ b/pkg/compiler/compiler_test.go @@ -4,6 +4,7 @@ import ( "io/ioutil" "os" "path" + "strings" "testing" "github.com/nspcc-dev/neo-go/pkg/compiler" @@ -90,3 +91,37 @@ func compileFile(src string) error { _, err := compiler.Compile(src, nil) 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)) + }) +} diff --git a/pkg/smartcontract/manifest/manifest.go b/pkg/smartcontract/manifest/manifest.go index 4603cac00..a1a2d4c9f 100644 --- a/pkg/smartcontract/manifest/manifest.go +++ b/pkg/smartcontract/manifest/manifest.go @@ -20,6 +20,10 @@ const ( NEP11StandardName = "NEP-11" // NEP17StandardName represents the name of NEP17 smartcontract standard. 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. diff --git a/pkg/smartcontract/manifest/standard/comply.go b/pkg/smartcontract/manifest/standard/comply.go index fd7eda2af..35986b225 100644 --- a/pkg/smartcontract/manifest/standard/comply.go +++ b/pkg/smartcontract/manifest/standard/comply.go @@ -21,6 +21,8 @@ var ( var checks = map[string][]*Standard{ manifest.NEP11StandardName: {nep11NonDivisible, nep11Divisible}, manifest.NEP17StandardName: {nep17}, + manifest.NEP11Payable: {nep11payable}, + manifest.NEP17Payable: {nep17payable}, } // Check checks if manifest complies with all provided standards. diff --git a/pkg/smartcontract/manifest/standard/payable.go b/pkg/smartcontract/manifest/standard/payable.go new file mode 100644 index 000000000..a588b45a8 --- /dev/null +++ b/pkg/smartcontract/manifest/standard/payable.go @@ -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, + }}, + }, + }, +}