From 8cccd960df395706404e9c4b2f35b80345c98d4f Mon Sep 17 00:00:00 2001 From: Tural Devrishev Date: Thu, 9 Oct 2025 00:41:01 +0400 Subject: [PATCH] smartcontract: add support NEP-25 Close #3595. Signed-off-by: Tural Devrishev --- cli/smartcontract/generate_test.go | 2 +- cli/smartcontract/smart_contract.go | 23 +- .../testdata/rpcbindings/invalid5/invalid.yml | 10 +- .../testdata/rpcbindings/invalid6/invalid.yml | 9 - .../notifications/config_extended.yml | 23 +- .../testdata/verify.bindings.yml | 105 ----- internal/testchain/transaction.go | 1 - pkg/compiler/codegen.go | 66 +++- pkg/compiler/compiler.go | 53 +-- pkg/compiler/compiler_test.go | 22 +- pkg/compiler/debug.go | 147 +++++-- pkg/compiler/inline.go | 4 +- pkg/compiler/interop_test.go | 8 +- pkg/core/blockchain_neotest_test.go | 9 +- pkg/core/interop/contract/call_test.go | 12 +- pkg/neotest/compile.go | 1 - pkg/smartcontract/binding/generate.go | 55 +-- pkg/smartcontract/binding/generate_test.go | 173 +++++---- pkg/smartcontract/manifest/abi.go | 5 +- pkg/smartcontract/manifest/extended_type.go | 340 ++++++++++++++++ .../manifest/extended_type_test.go | 367 ++++++++++++++++++ pkg/smartcontract/manifest/method.go | 11 +- pkg/smartcontract/manifest/parameter.go | 128 +++++- pkg/smartcontract/manifest/parameter_test.go | 155 +++++++- pkg/smartcontract/rpcbinding/binding.go | 148 +++---- 25 files changed, 1397 insertions(+), 480 deletions(-) create mode 100644 pkg/smartcontract/manifest/extended_type.go create mode 100644 pkg/smartcontract/manifest/extended_type_test.go diff --git a/cli/smartcontract/generate_test.go b/cli/smartcontract/generate_test.go index 000de8d428..f6ea23a66c 100644 --- a/cli/smartcontract/generate_test.go +++ b/cli/smartcontract/generate_test.go @@ -686,7 +686,7 @@ func TestGenerateRPCBindings_Errors(t *testing.T) { } t.Run("event", func(t *testing.T) { - check(t, "invalid6", false, "error during generation: named type `SomeStruct` has two fields with identical resulting binding name `Field`") + check(t, "invalid6", false, "error during generation: named type `invalid6.SomeStruct` has two fields with identical resulting binding name `Field`") }) t.Run("autogen event", func(t *testing.T) { check(t, "invalid7", true, "error during generation: named type `invalid7.SomeStruct` has two fields with identical resulting binding name `Field`") diff --git a/cli/smartcontract/smart_contract.go b/cli/smartcontract/smart_contract.go index 605b37a258..7a8108e8de 100644 --- a/cli/smartcontract/smart_contract.go +++ b/cli/smartcontract/smart_contract.go @@ -21,7 +21,6 @@ import ( "github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker" "github.com/nspcc-dev/neo-go/pkg/rpcclient/management" "github.com/nspcc-dev/neo-go/pkg/smartcontract" - "github.com/nspcc-dev/neo-go/pkg/smartcontract/binding" "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" "github.com/nspcc-dev/neo-go/pkg/smartcontract/nef" "github.com/nspcc-dev/neo-go/pkg/util" @@ -419,15 +418,13 @@ func initSmartContract(ctx *cli.Context) error { SourceURL: "http://example.com/", SupportedStandards: []string{}, SafeMethods: []string{}, - Events: []compiler.HybridEvent{ + Events: []manifest.Event{ { Name: "Hello world!", - Parameters: []compiler.HybridParameter{ + Parameters: []manifest.Parameter{ { - Parameter: manifest.Parameter{ - Name: "args", - Type: smartcontract.ArrayType, - }, + Name: "args", + Type: smartcontract.ArrayType, }, }, }, @@ -517,7 +514,10 @@ func contractCompile(ctx *cli.Context) error { NoEventsCheck: ctx.Bool("no-events"), NoPermissionsCheck: ctx.Bool("no-permissions"), - GuessEventTypes: ctx.Bool("guess-eventtypes"), + CollectedNamedTypes: make(map[string]manifest.ExtendedType), + } + if ctx.Bool("guess-eventtypes") { + o.GuessedNamedTypes = make(map[string]manifest.ExtendedType) } if len(confFile) != 0 { @@ -528,7 +528,6 @@ func contractCompile(ctx *cli.Context) error { o.Name = conf.Name o.SourceURL = conf.SourceURL o.ContractEvents = conf.Events - o.DeclaredNamedTypes = conf.NamedTypes o.ContractSupportedStandards = conf.SupportedStandards o.Permissions = make([]manifest.Permission, len(conf.Permissions)) for i := range conf.Permissions { @@ -760,10 +759,10 @@ type ProjectConfig struct { SourceURL string SafeMethods []string SupportedStandards []string - Events []compiler.HybridEvent + Events []manifest.Event Permissions []permission - Overloads map[string]string `yaml:"overloads,omitempty"` - NamedTypes map[string]binding.ExtendedType `yaml:"namedtypes,omitempty"` + Overloads map[string]string `yaml:"overloads,omitempty"` + NamedTypes map[string]manifest.ExtendedType `yaml:"namedtypes,omitempty"` } func inspect(ctx *cli.Context) error { diff --git a/cli/smartcontract/testdata/rpcbindings/invalid5/invalid.yml b/cli/smartcontract/testdata/rpcbindings/invalid5/invalid.yml index ccd05f4c21..3ea46e561c 100644 --- a/cli/smartcontract/testdata/rpcbindings/invalid5/invalid.yml +++ b/cli/smartcontract/testdata/rpcbindings/invalid5/invalid.yml @@ -7,10 +7,6 @@ events: extendedtype: base: Array name: invalid5.NamedStruct -namedtypes: - invalid5.NamedStruct: - base: Array - name: invalid5.NamedStruct - fields: - - field: SomeInt - base: Integer + fields: + - field: SomeInt + base: Integer diff --git a/cli/smartcontract/testdata/rpcbindings/invalid6/invalid.yml b/cli/smartcontract/testdata/rpcbindings/invalid6/invalid.yml index c9fe64b23f..40a86f2267 100644 --- a/cli/smartcontract/testdata/rpcbindings/invalid6/invalid.yml +++ b/cli/smartcontract/testdata/rpcbindings/invalid6/invalid.yml @@ -7,12 +7,3 @@ events: extendedtype: base: Struct name: SomeStruct -namedtypes: - SomeStruct: - base: Struct - name: SomeStruct - fields: - - field: Field - base: Integer - - field: field - base: Integer diff --git a/cli/smartcontract/testdata/rpcbindings/notifications/config_extended.yml b/cli/smartcontract/testdata/rpcbindings/notifications/config_extended.yml index 6c6e418c9d..f007a74687 100644 --- a/cli/smartcontract/testdata/rpcbindings/notifications/config_extended.yml +++ b/cli/smartcontract/testdata/rpcbindings/notifications/config_extended.yml @@ -26,6 +26,11 @@ events: extendedtype: base: Struct name: crazyStruct + fields: + - field: I + base: Integer + - field: B + base: Boolean - name: "SomeArray" parameters: - name: a @@ -43,18 +48,6 @@ events: extendedtype: base: Struct name: simpleStruct -namedtypes: - crazyStruct: - base: Struct - name: crazyStruct - fields: - - field: I - base: Integer - - field: B - base: Boolean - simpleStruct: - base: Struct - name: simpleStruct - fields: - - field: i - base: Integer \ No newline at end of file + fields: + - field: i + base: Integer \ No newline at end of file diff --git a/cli/smartcontract/testdata/verify.bindings.yml b/cli/smartcontract/testdata/verify.bindings.yml index 927064b60a..8aaa2a1bdb 100755 --- a/cli/smartcontract/testdata/verify.bindings.yml +++ b/cli/smartcontract/testdata/verify.bindings.yml @@ -125,111 +125,6 @@ overrides: syscall4.name: string toBlockSR: '*github.com/nspcc-dev/neo-go/pkg/interop/native/ledger.BlockSR' verify: bool -namedtypes: - ledger.Block: - base: Array - name: ledger.Block - fields: - - field: Hash - base: Hash256 - - field: Version - base: Integer - - field: PrevHash - base: Hash256 - - field: MerkleRoot - base: Hash256 - - field: Timestamp - base: Integer - - field: Nonce - base: Integer - - field: Index - base: Integer - - field: NextConsensus - base: Hash160 - - field: TransactionsLength - base: Integer - ledger.BlockSR: - base: Array - name: ledger.BlockSR - fields: - - field: Hash - base: Hash256 - - field: Version - base: Integer - - field: PrevHash - base: Hash256 - - field: MerkleRoot - base: Hash256 - - field: Timestamp - base: Integer - - field: Nonce - base: Integer - - field: Index - base: Integer - - field: NextConsensus - base: Hash160 - - field: TransactionsLength - base: Integer - - field: PrevStateRoot - base: Hash256 - ledger.Transaction: - base: Array - name: ledger.Transaction - fields: - - field: Hash - base: Hash256 - - field: Version - base: Integer - - field: Nonce - base: Integer - - field: Sender - base: Hash160 - - field: SysFee - base: Integer - - field: NetFee - base: Integer - - field: ValidUntilBlock - base: Integer - - field: Script - base: ByteArray - ledger.TransactionSigner: - base: Array - name: ledger.TransactionSigner - fields: - - field: Account - base: Hash160 - - field: Scopes - base: Integer - - field: AllowedContracts - base: Array - value: - base: Hash160 - - field: AllowedGroups - base: Array - value: - base: PublicKey - - field: Rules - base: Array - value: - base: Array - name: ledger.WitnessRule - ledger.WitnessCondition: - base: Array - name: ledger.WitnessCondition - fields: - - field: Type - base: Integer - - field: Value - base: Any - ledger.WitnessRule: - base: Array - name: ledger.WitnessRule - fields: - - field: Action - base: Integer - - field: Condition - base: Array - name: ledger.WitnessCondition types: call.args: base: Array diff --git a/internal/testchain/transaction.go b/internal/testchain/transaction.go index 1b33db59cb..e04b589d38 100644 --- a/internal/testchain/transaction.go +++ b/internal/testchain/transaction.go @@ -78,7 +78,6 @@ func NewDeployTx(bc Ledger, name string, sender util.Uint160, r gio.Reader, conf o.Name = conf.Name o.SourceURL = conf.SourceURL o.ContractEvents = conf.Events - o.DeclaredNamedTypes = conf.NamedTypes o.ContractSupportedStandards = conf.SupportedStandards o.Permissions = make([]manifest.Permission, len(conf.Permissions)) for i := range conf.Permissions { diff --git a/pkg/compiler/codegen.go b/pkg/compiler/codegen.go index 3139f6c7d7..63d7430644 100644 --- a/pkg/compiler/codegen.go +++ b/pkg/compiler/codegen.go @@ -671,8 +671,11 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { if id.Name != "_" { if !isMapKeyCheck { if len(t.Values) == 0 { - c.emitDefault(c.typeOf(t.Type)) + typ := c.typeOf(t.Type) + c.collectNamedTypes(typ) + c.emitDefault(typ) } else if i == 0 || !multiRet { + c.collectNamedTypes(c.typeOf(t.Values[i])) ast.Walk(c, t.Values[i]) } } @@ -725,6 +728,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { } if !isAssignOp && !isMapKeyCheck { for i := range n.Rhs { + c.collectNamedTypes(c.typeOf(n.Rhs[i])) ast.Walk(c, n.Rhs[i]) } } @@ -972,6 +976,20 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { t := c.typeOf(n) switch typ := t.Underlying().(type) { case *types.Struct: + named, isNamed := t.(*types.Named) + if c.buildInfo.options != nil { + if !isNamed { + name := "unnamed" + if c.buildInfo.options.CollectedNamedTypes != nil { + for c.buildInfo.options.CollectedNamedTypes[name].Name == name { + name = name + "X" + } + _ = c.genStructExtended(typ, name, c.buildInfo.options.CollectedNamedTypes) + } + } else if !isInteropPath(named.String()) { + _ = c.genStructExtended(typ, named.Obj().Pkg().Name()+"."+named.Obj().Name(), c.buildInfo.options.CollectedNamedTypes) + } + } c.convertStruct(n, false) case *types.Map: c.convertMap(n) @@ -2161,6 +2179,52 @@ func (c *codegen) checkGetMapValueWithOKFlag(expr ast.Expr) bool { return ok } +func (c *codegen) collectNamedTypes(t types.Type) { + if c.buildInfo.options == nil { + return + } + var ( + seen = make(map[types.Type]bool) + walk func(types.Type) + ) + walk = func(tt types.Type) { + if tt == nil || seen[tt] { + return + } + seen[tt] = true + + switch x := tt.(type) { + case *types.Pointer: + walk(x.Elem()) + case *types.Slice: + walk(x.Elem()) + case *types.Map: + walk(x.Key()) + walk(x.Elem()) + case *types.Struct: + name := "unnamed" + if c.buildInfo.options.CollectedNamedTypes != nil { + for c.buildInfo.options.CollectedNamedTypes[name].Name == name { + name = name + "X" + } + _ = c.genStructExtended(x, name, c.buildInfo.options.CollectedNamedTypes) + } + for i := range x.NumFields() { + walk(x.Field(i).Type()) + } + case *types.Named: + if typ, ok := x.Underlying().(*types.Struct); ok && !isInteropPath(x.String()) { + _ = c.genStructExtended(typ, x.Obj().Pkg().Path()+"."+x.Obj().Name(), c.buildInfo.options.CollectedNamedTypes) + for i := range typ.NumFields() { + walk(typ.Field(i).Type()) + } + } + } + } + + walk(t) +} + func (c *codegen) emitGetMapValueWithOKFlag(expr ast.Expr) { var ( idxExpr = expr.(*ast.IndexExpr) diff --git a/pkg/compiler/compiler.go b/pkg/compiler/compiler.go index 6222c67ca9..e9aba178a2 100644 --- a/pkg/compiler/compiler.go +++ b/pkg/compiler/compiler.go @@ -54,13 +54,16 @@ type Options struct { // This setting has effect only if manifest is emitted. NoPermissionsCheck bool - // GuessEventTypes specifies if types of runtime notifications need to be guessed - // from the usage context. These types are used for RPC binding generation only and + // GuessedNamedTypes specifies guessed from the usage context named types of + // runtime notifications. These types are used for RPC binding generation only and // can be defined for events with name known at the compilation time and without - // variadic args usages. If some type is specified via config file, then the config's - // one is preferable. Currently, event's parameter type is defined from the first + // variadic args usages. Currently, event's parameter type is defined from the first // occurrence of event call. - GuessEventTypes bool + GuessedNamedTypes map[string]manifest.ExtendedType + // CollectedNamedTypes specifies named types collected from contract sources. + // These types are used for RPC binding generation and contain definitions for + // struct types discovered in the code. + CollectedNamedTypes map[string]manifest.ExtendedType // Name is a contract's name to be written to manifest. Name string @@ -69,11 +72,7 @@ type Options struct { SourceURL string // Runtime notifications declared in the contract configuration file. - ContractEvents []HybridEvent - - // DeclaredNamedTypes is the set of named types that were declared in the - // contract configuration type and are the part of manifest events. - DeclaredNamedTypes map[string]binding.ExtendedType + ContractEvents []manifest.Event // The list of standards supported by the contract. ContractSupportedStandards []string @@ -92,23 +91,6 @@ type Options struct { BindingsFile string } -// HybridEvent represents the description of event emitted by the contract squashed -// with extended event's parameters description. We have it as a separate type for -// the user's convenience. It is applied for the smart contract configuration file -// only. -type HybridEvent struct { - Name string `json:"name"` - Parameters []HybridParameter `json:"parameters"` -} - -// HybridParameter contains the manifest's event parameter description united with -// the extended type description for this parameter. It is applied for the smart -// contract configuration file only. -type HybridParameter struct { - manifest.Parameter `yaml:",inline"` - ExtendedType *binding.ExtendedType `yaml:"extendedtype,omitempty"` -} - type buildInfo struct { config *packages.Config program []*packages.Package @@ -337,15 +319,6 @@ func CompileAndSave(src string, o *Options) ([]byte, error) { cfg.Types[m.Name.Name] = *m.ReturnTypeExtended } } - if len(di.NamedTypes) > 0 { - cfg.NamedTypes = di.NamedTypes - } - for name, et := range o.DeclaredNamedTypes { - if _, ok := cfg.NamedTypes[name]; ok { - return nil, fmt.Errorf("configured declared named type intersects with the contract's one: `%s`", name) - } - cfg.NamedTypes[name] = et - } for _, e := range o.ContractEvents { eStructName := rpcbinding.ToEventBindingName(e.Name) for _, p := range e.Parameters { @@ -356,7 +329,7 @@ func CompileAndSave(src string, o *Options) ([]byte, error) { } } } - if o.GuessEventTypes { + if o.GuessedNamedTypes != nil { if len(di.EmittedEvents) > 0 { var keys = make([]string, 0, len(di.EmittedEvents)) for k := range di.EmittedEvents { @@ -366,7 +339,7 @@ func CompileAndSave(src string, o *Options) ([]byte, error) { for _, eventName := range keys { var ( eventUsages = di.EmittedEvents[eventName] - manifestEvent HybridEvent + manifestEvent manifest.Event ) for _, e := range o.ContractEvents { if e.Name == eventName { @@ -408,12 +381,12 @@ func CompileAndSave(src string, o *Options) ([]byte, error) { if p.ExtendedType != nil { typeName := p.ExtendedType.Name if extType, ok := exampleUsage.ExtTypes[typeName]; ok { - for _, ok := cfg.NamedTypes[typeName]; ok; _, ok = cfg.NamedTypes[typeName] { + for _, ok := o.GuessedNamedTypes[typeName]; ok; _, ok = o.GuessedNamedTypes[typeName] { typeName = typeName + "X" } extType.Name = typeName p.ExtendedType.Name = typeName - cfg.NamedTypes[typeName] = extType + o.GuessedNamedTypes[typeName] = extType } if _, ok := cfg.Types[pname]; !ok { cfg.Types[pname] = *p.ExtendedType diff --git a/pkg/compiler/compiler_test.go b/pkg/compiler/compiler_test.go index c70ac643ae..6cc9595e3c 100644 --- a/pkg/compiler/compiler_test.go +++ b/pkg/compiler/compiler_test.go @@ -188,16 +188,16 @@ func TestEventWarnings(t *testing.T) { }) t.Run("wrong parameter number", func(t *testing.T) { _, err = compiler.CreateManifest(di, &compiler.Options{ - ContractEvents: []compiler.HybridEvent{{Name: "Event"}}, + ContractEvents: []manifest.Event{{Name: "Event"}}, Name: "payable", }) require.Error(t, err) }) t.Run("wrong parameter type", func(t *testing.T) { _, err = compiler.CreateManifest(di, &compiler.Options{ - ContractEvents: []compiler.HybridEvent{{ + ContractEvents: []manifest.Event{{ Name: "Event", - Parameters: []compiler.HybridParameter{{Parameter: manifest.NewParameter("number", smartcontract.StringType)}}, + Parameters: []manifest.Parameter{manifest.NewParameter("number", smartcontract.StringType)}, }}, Name: "payable", }) @@ -205,9 +205,9 @@ func TestEventWarnings(t *testing.T) { }) t.Run("any parameter type", func(t *testing.T) { _, err = compiler.CreateManifest(di, &compiler.Options{ - ContractEvents: []compiler.HybridEvent{{ + ContractEvents: []manifest.Event{{ Name: "Event", - Parameters: []compiler.HybridParameter{{Parameter: manifest.NewParameter("number", smartcontract.AnyType)}}, + Parameters: []manifest.Parameter{manifest.NewParameter("number", smartcontract.AnyType)}, }}, Name: "payable", }) @@ -215,9 +215,9 @@ func TestEventWarnings(t *testing.T) { }) t.Run("good", func(t *testing.T) { _, err = compiler.CreateManifest(di, &compiler.Options{ - ContractEvents: []compiler.HybridEvent{{ + ContractEvents: []manifest.Event{{ Name: "Event", - Parameters: []compiler.HybridParameter{{Parameter: manifest.NewParameter("number", smartcontract.IntegerType)}}, + Parameters: []manifest.Parameter{manifest.NewParameter("number", smartcontract.IntegerType)}, }}, Name: "payable", }) @@ -253,7 +253,7 @@ func TestEventWarnings(t *testing.T) { require.Error(t, err) _, err = compiler.CreateManifest(di, &compiler.Options{ - ContractEvents: []compiler.HybridEvent{{Name: "Event"}}, + ContractEvents: []manifest.Event{{Name: "Event"}}, Name: "eventTest", }) require.NoError(t, err) @@ -271,9 +271,9 @@ func TestEventWarnings(t *testing.T) { _, err = compiler.CreateManifest(di, &compiler.Options{ Name: "eventTest", - ContractEvents: []compiler.HybridEvent{{ + ContractEvents: []manifest.Event{{ Name: "Event", - Parameters: []compiler.HybridParameter{{Parameter: manifest.NewParameter("number", smartcontract.IntegerType)}}, + Parameters: []manifest.Parameter{manifest.NewParameter("number", smartcontract.IntegerType)}, }}, }) require.NoError(t, err) @@ -289,7 +289,7 @@ func TestNotifyInVerify(t *testing.T) { t.Run(name, func(t *testing.T) { src := fmt.Sprintf(srcTmpl, name) _, _, err := compiler.CompileWithOptions("eventTest.go", strings.NewReader(src), - &compiler.Options{ContractEvents: []compiler.HybridEvent{{Name: "Event"}}}) + &compiler.Options{ContractEvents: []manifest.Event{{Name: "Event"}}}) require.Error(t, err) t.Run("suppress", func(t *testing.T) { diff --git a/pkg/compiler/debug.go b/pkg/compiler/debug.go index 8923a686a5..1254655fba 100644 --- a/pkg/compiler/debug.go +++ b/pkg/compiler/debug.go @@ -28,7 +28,7 @@ type DebugInfo struct { Methods []MethodDebugInfo `json:"methods"` // NamedTypes are exported structured types that have some name (even // if the original structure doesn't) and a number of internal fields. - NamedTypes map[string]binding.ExtendedType `json:"-"` + NamedTypes map[string]manifest.ExtendedType `json:"-"` // Events are the events that contract is allowed to emit and that have to // be presented in the resulting contract manifest and debug info file. Events []EventDebugInfo `json:"events"` @@ -64,7 +64,7 @@ type MethodDebugInfo struct { // ReturnTypeReal is the method's return type as specified in Go code. ReturnTypeReal binding.Override `json:"-"` // ReturnTypeExtended is the method's return type with additional data. - ReturnTypeExtended *binding.ExtendedType `json:"-"` + ReturnTypeExtended *manifest.ExtendedType `json:"-"` // ReturnTypeSC is a return type to use in manifest. ReturnTypeSC smartcontract.ParamType `json:"-"` Variables []string `json:"variables"` @@ -113,7 +113,7 @@ type DebugParam struct { Name string `json:"name"` Type string `json:"type"` RealType binding.Override `json:"-"` - ExtendedType *binding.ExtendedType `json:"-"` + ExtendedType *manifest.ExtendedType `json:"-"` TypeSC smartcontract.ParamType `json:"-"` } @@ -121,7 +121,7 @@ type DebugParam struct { // the contract code. It has the map of extended types used as the parameters to // runtime.Notify(...) call (if any) and the parameters info itself. type EmittedEventInfo struct { - ExtTypes map[string]binding.ExtendedType + ExtTypes map[string]manifest.ExtendedType Params []DebugParam } @@ -211,7 +211,7 @@ func (c *codegen) emitDebugInfo(contract []byte) *DebugInfo { fnames = append(fnames, name) } slices.Sort(fnames) - d.NamedTypes = make(map[string]binding.ExtendedType) + d.NamedTypes = make(map[string]manifest.ExtendedType) for _, name := range fnames { m := c.methodInfoFromScope(name, c.funcs[name], d.NamedTypes) d.Methods = append(d.Methods, *m) @@ -230,7 +230,7 @@ func (c *codegen) registerDebugVariable(name string, expr ast.Expr) { c.scope.variables = append(c.scope.variables, name+","+vt.String()) } -func (c *codegen) methodInfoFromScope(name string, scope *funcScope, exts map[string]binding.ExtendedType) *MethodDebugInfo { +func (c *codegen) methodInfoFromScope(name string, scope *funcScope, exts map[string]manifest.ExtendedType) *MethodDebugInfo { ps := scope.decl.Type.Params params := make([]DebugParam, 0, ps.NumFields()) for i := range ps.List { @@ -269,7 +269,7 @@ func (c *codegen) methodInfoFromScope(name string, scope *funcScope, exts map[st } } -func (c *codegen) scAndVMReturnTypeFromScope(scope *funcScope, exts map[string]binding.ExtendedType) (smartcontract.ParamType, string, binding.Override, *binding.ExtendedType) { +func (c *codegen) scAndVMReturnTypeFromScope(scope *funcScope, exts map[string]manifest.ExtendedType) (smartcontract.ParamType, string, binding.Override, *manifest.ExtendedType) { results := scope.decl.Type.Results switch results.NumFields() { case 0: @@ -283,7 +283,7 @@ func (c *codegen) scAndVMReturnTypeFromScope(scope *funcScope, exts map[string]b } } -func scAndVMInteropTypeFromExpr(named *types.Named, isPointer bool) (smartcontract.ParamType, stackitem.Type, binding.Override, *binding.ExtendedType) { +func scAndVMInteropTypeFromExpr(named *types.Named, isPointer bool) (smartcontract.ParamType, stackitem.Type, binding.Override, *manifest.ExtendedType) { name := named.Obj().Name() pkg := named.Obj().Pkg().Name() switch pkg { @@ -294,7 +294,7 @@ func scAndVMInteropTypeFromExpr(named *types.Named, isPointer bool) (smartcontra } // Block, Transaction, Contract. typeName := pkg + "." + name - et := &binding.ExtendedType{Base: smartcontract.ArrayType, Name: typeName} + et := &manifest.ExtendedType{Type: smartcontract.ArrayType, Name: typeName} if isPointer { typeName = "*" + typeName } @@ -323,14 +323,14 @@ func scAndVMInteropTypeFromExpr(named *types.Named, isPointer bool) (smartcontra return smartcontract.InteropInterfaceType, stackitem.InteropT, binding.Override{TypeName: "any"}, - &binding.ExtendedType{Base: smartcontract.InteropInterfaceType, Interface: "iterator"} // Temporarily all interops are iterators. + &manifest.ExtendedType{Type: smartcontract.InteropInterfaceType, Interface: "iterator"} // Temporarily all interops are iterators. } -func (c *codegen) scAndVMTypeFromExpr(typ ast.Expr, exts map[string]binding.ExtendedType) (smartcontract.ParamType, stackitem.Type, binding.Override, *binding.ExtendedType) { +func (c *codegen) scAndVMTypeFromExpr(typ ast.Expr, exts map[string]manifest.ExtendedType) (smartcontract.ParamType, stackitem.Type, binding.Override, *manifest.ExtendedType) { return c.scAndVMTypeFromType(c.typeOf(typ), exts) } -func (c *codegen) scAndVMTypeFromType(t types.Type, exts map[string]binding.ExtendedType) (smartcontract.ParamType, stackitem.Type, binding.Override, *binding.ExtendedType) { +func (c *codegen) scAndVMTypeFromType(t types.Type, exts map[string]manifest.ExtendedType) (smartcontract.ParamType, stackitem.Type, binding.Override, *manifest.ExtendedType) { if t == nil { return smartcontract.AnyType, stackitem.AnyT, binding.Override{TypeName: "any"}, nil } @@ -347,7 +347,7 @@ func (c *codegen) scAndVMTypeFromType(t types.Type, exts map[string]binding.Exte if isNamed { if isInteropPath(named.String()) { st, vt, over, et := scAndVMInteropTypeFromExpr(named, isPtr) - if et != nil && et.Base == smartcontract.ArrayType && exts != nil && exts[et.Name].Name != et.Name { + if et != nil && et.Type == smartcontract.ArrayType && exts != nil && exts[et.Name].Name != et.Name { _ = c.genStructExtended(named.Underlying().(*types.Struct), et.Name, exts) } return st, vt, over, et @@ -375,14 +375,14 @@ func (c *codegen) scAndVMTypeFromType(t types.Type, exts map[string]binding.Exte return smartcontract.AnyType, stackitem.AnyT, over, nil } case *types.Map: - et := &binding.ExtendedType{ - Base: smartcontract.MapType, + et := &manifest.ExtendedType{ + Type: smartcontract.MapType, } et.Key, _, _, _ = c.scAndVMTypeFromType(t.Key(), exts) vt, _, over, vet := c.scAndVMTypeFromType(t.Elem(), exts) et.Value = vet if et.Value == nil { - et.Value = &binding.ExtendedType{Base: vt} + et.Value = &manifest.ExtendedType{Type: vt} } over.TypeName = "map[" + t.Key().String() + "]" + over.TypeName return smartcontract.MapType, stackitem.MapT, over, et @@ -406,8 +406,8 @@ func (c *codegen) scAndVMTypeFromType(t types.Type, exts map[string]binding.Exte extName = name } return smartcontract.ArrayType, stackitem.StructT, over, - &binding.ExtendedType{ // Value-less, refer to exts. - Base: smartcontract.ArrayType, + &manifest.ExtendedType{ // Value-less, refer to exts. + Type: smartcontract.ArrayType, Name: extName, } @@ -416,14 +416,14 @@ func (c *codegen) scAndVMTypeFromType(t types.Type, exts map[string]binding.Exte over.TypeName = "[]byte" return smartcontract.ByteArrayType, stackitem.ByteArrayT, over, nil } - et := &binding.ExtendedType{ - Base: smartcontract.ArrayType, + et := &manifest.ExtendedType{ + Type: smartcontract.ArrayType, } vt, _, over, vet := c.scAndVMTypeFromType(t.Elem(), exts) et.Value = vet if et.Value == nil { - et.Value = &binding.ExtendedType{ - Base: vt, + et.Value = &manifest.ExtendedType{ + Type: vt, } } if over.TypeName != "" { @@ -436,29 +436,30 @@ func (c *codegen) scAndVMTypeFromType(t types.Type, exts map[string]binding.Exte } } -func (c *codegen) genStructExtended(t *types.Struct, name string, exts map[string]binding.ExtendedType) *binding.ExtendedType { - var et *binding.ExtendedType +func (c *codegen) genStructExtended(t *types.Struct, name string, exts map[string]manifest.ExtendedType) *manifest.ExtendedType { + var et *manifest.ExtendedType if exts != nil { if exts[name].Name != name { - et = &binding.ExtendedType{ - Base: smartcontract.ArrayType, + et = &manifest.ExtendedType{ + Type: smartcontract.ArrayType, Name: name, - Fields: make([]binding.FieldExtendedType, t.NumFields()), + Fields: make([]manifest.Parameter, t.NumFields()), } exts[name] = *et // Prefill to solve recursive structures. for i := range et.Fields { field := t.Field(i) ft, _, _, fet := c.scAndVMTypeFromType(field.Type(), exts) + et.Fields[i].Type = ft if fet == nil { - et.Fields[i].Base = ft + et.Fields[i].ExtendedType = &manifest.ExtendedType{Type: ft} } else { - et.Fields[i].ExtendedType = *fet + et.Fields[i].ExtendedType = fet } - et.Fields[i].Field = field.Name() + et.Fields[i].Name = field.Name() } exts[name] = *et // Set real structure data. } else { - et = new(binding.ExtendedType) + et = new(manifest.ExtendedType) *et = exts[name] } } @@ -594,14 +595,28 @@ func (di *DebugInfo) ConvertToManifest(o *Options) (*manifest.Manifest, error) { if o.ContractSupportedStandards != nil { result.SupportedStandards = o.ContractSupportedStandards } + namedTypes := make(map[string]manifest.ExtendedType) + for name, et := range o.GuessedNamedTypes { + namedTypes[di.MainPkg+"."+name] = et + } + for name, et := range di.NamedTypes { + namedTypes[name] = et + } events := make([]manifest.Event, len(o.ContractEvents)) for i, e := range o.ContractEvents { params := make([]manifest.Parameter, len(e.Parameters)) for j, p := range e.Parameters { - params[j] = p.Parameter + params[j] = p + if err := collectNamedTypes(p.ExtendedType, namedTypes, o.CollectedNamedTypes, di.MainPkg); err != nil { + return nil, err + } + if p.ExtendedType != nil { + params[j].ExtendedType = &manifest.ExtendedType{} + buildManifestExtendedType(params[j].ExtendedType, p.ExtendedType) + } } events[i] = manifest.Event{ - Name: o.ContractEvents[i].Name, + Name: e.Name, Parameters: params, } } @@ -609,8 +624,8 @@ func (di *DebugInfo) ConvertToManifest(o *Options) (*manifest.Manifest, error) { Methods: methods, Events: events, } - if result.ABI.Events == nil { - result.ABI.Events = make([]manifest.Event, 0) + if len(namedTypes) > 0 { + result.ABI.NamedTypes = namedTypes } result.Permissions = o.Permissions for name, emitName := range o.Overloads { @@ -633,3 +648,65 @@ func (di *DebugInfo) ConvertToManifest(o *Options) (*manifest.Manifest, error) { } return result, nil } + +func collectNamedTypes(et *manifest.ExtendedType, abiNamedTypes, opNamedTypes map[string]manifest.ExtendedType, mainPkg string) error { + if et == nil { + return nil + } + if et.Name != "" { + var ( + ok bool + ext manifest.ExtendedType + name = et.Name + ) + if !strings.Contains(name, ".") { + name = mainPkg + "." + name + } + if ext, ok = opNamedTypes[name]; ok { + if et.Fields == nil { + if _, ok := abiNamedTypes[name]; !ok { + abiNamedTypes[name] = ext + } + return nil + } + return fmt.Errorf("configured declared named type intersects with the contract's one: `%s`", name) + } + for name, e := range opNamedTypes { + if strings.HasPrefix(e.Name, "unnamed") { + e.Name = et.Name + if et.Equals(&e) { + abiNamedTypes[name] = e + return nil + } + } + } + abiNamedTypes[mainPkg+"."+et.Name] = *et + for i := range et.Fields { + if err := collectNamedTypes(et.Fields[i].ExtendedType, abiNamedTypes, opNamedTypes, mainPkg); err != nil { + return err + } + } + return nil + } + if err := collectNamedTypes(et.Value, abiNamedTypes, opNamedTypes, mainPkg); err != nil { + return err + } + return nil +} + +func buildManifestExtendedType(dst, src *manifest.ExtendedType) { + if src.Name != "" { + dst.Name = src.Name + dst.Type = smartcontract.ArrayType + return + } + dst.Type = src.Type + dst.Length = src.Length + dst.ForbidNull = src.ForbidNull + dst.Interface = src.Interface + dst.Key = src.Key + if src.Value != nil { + dst.Value = &manifest.ExtendedType{} + buildManifestExtendedType(dst.Value, src.Value) + } +} diff --git a/pkg/compiler/inline.go b/pkg/compiler/inline.go index ae9622dd1c..5ea7915670 100644 --- a/pkg/compiler/inline.go +++ b/pkg/compiler/inline.go @@ -8,8 +8,8 @@ import ( "slices" "github.com/nspcc-dev/neo-go/pkg/core/interop/runtime" - "github.com/nspcc-dev/neo-go/pkg/smartcontract/binding" "github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag" + "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/vm/emit" "github.com/nspcc-dev/neo-go/pkg/vm/opcode" @@ -176,7 +176,7 @@ func (c *codegen) processNotify(f *funcScope, args []ast.Expr, hasEllipsis bool) // extMap holds the extended parameter types used for the given event call. // It will be unified with the common extMap later during bindings config // generation. - extMap := make(map[string]binding.ExtendedType) + extMap := make(map[string]manifest.ExtendedType) for _, p := range args[1:] { st, vt, over, extT := c.scAndVMTypeFromExpr(p, extMap) params = append(params, DebugParam{ diff --git a/pkg/compiler/interop_test.go b/pkg/compiler/interop_test.go index ce5fecde9b..7fc336ae5c 100644 --- a/pkg/compiler/interop_test.go +++ b/pkg/compiler/interop_test.go @@ -652,13 +652,13 @@ func TestForcedNotifyArgumentsConversion(t *testing.T) { if count != len(expectedVMParamTypes) { t.Fatalf("parameters count mismatch: %d vs %d", count, len(expectedVMParamTypes)) } - scParams := make([]compiler.HybridParameter, len(targetSCParamTypes)) + scParams := make([]manifest.Parameter, len(targetSCParamTypes)) vmParams := make([]stackitem.Item, len(expectedVMParamTypes)) for i := range scParams { - scParams[i] = compiler.HybridParameter{Parameter: manifest.Parameter{ + scParams[i] = manifest.Parameter{ Name: strconv.Itoa(i), Type: targetSCParamTypes[i], - }} + } defaultValue := stackitem.NewBigInteger(big.NewInt(int64(i))) var ( val stackitem.Item @@ -674,7 +674,7 @@ func TestForcedNotifyArgumentsConversion(t *testing.T) { } ctr := neotest.CompileSource(t, e.CommitteeHash, strings.NewReader(src), &compiler.Options{ Name: "Helper", - ContractEvents: []compiler.HybridEvent{ + ContractEvents: []manifest.Event{ { Name: methodWithoutEllipsis, Parameters: scParams, diff --git a/pkg/core/blockchain_neotest_test.go b/pkg/core/blockchain_neotest_test.go index 58a4e41e2e..91d373ec47 100644 --- a/pkg/core/blockchain_neotest_test.go +++ b/pkg/core/blockchain_neotest_test.go @@ -41,6 +41,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/neotest/chain" "github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag" + "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" "github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger" "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/vm" @@ -2913,14 +2914,14 @@ func TestEngineLimits(t *testing.T) { } return len(res) }`, args) - eParams := make([]compiler.HybridParameter, eArgsCount) + eParams := make([]manifest.Parameter, eArgsCount) for i := range eParams { eParams[i].Name = fmt.Sprintf("str%d", i) eParams[i].Type = smartcontract.ByteArrayType } c := neotest.CompileSource(t, acc.ScriptHash(), strings.NewReader(src), &compiler.Options{ Name: "test_contract", - ContractEvents: []compiler.HybridEvent{ + ContractEvents: []manifest.Event{ { Name: "LargeEvent", Parameters: eParams, @@ -2980,14 +2981,14 @@ func TestRuntimeNotifyRefcounting(t *testing.T) { } }`, args) - eParams := make([]compiler.HybridParameter, eArgsCount) + eParams := make([]manifest.Parameter, eArgsCount) for i := range eParams { eParams[i].Name = fmt.Sprintf("str%d", i) eParams[i].Type = smartcontract.ByteArrayType } c := neotest.CompileSource(t, acc.ScriptHash(), strings.NewReader(src), &compiler.Options{ Name: "test_contract", - ContractEvents: []compiler.HybridEvent{ + ContractEvents: []manifest.Event{ { Name: "LargeEvent", Parameters: eParams, diff --git a/pkg/core/interop/contract/call_test.go b/pkg/core/interop/contract/call_test.go index 685937525e..0115c1a006 100644 --- a/pkg/core/interop/contract/call_test.go +++ b/pkg/core/interop/contract/call_test.go @@ -360,8 +360,8 @@ func TestSnapshotIsolation_Exceptions(t *testing.T) { NoPermissionsCheck: true, Name: "contractA", Permissions: []manifest.Permission{{Methods: manifest.WildStrings{Value: nil}}}, - ContractEvents: []compiler.HybridEvent{ - {Name: "NotificationFromA", Parameters: []compiler.HybridParameter{{Parameter: manifest.Parameter{Name: "i", Type: smartcontract.IntegerType}}}}, + ContractEvents: []manifest.Event{ + {Name: "NotificationFromA", Parameters: []manifest.Parameter{{Name: "i", Type: smartcontract.IntegerType}}}, }, }) e.DeployContract(t, ctrA, nil) @@ -435,9 +435,9 @@ func TestSnapshotIsolation_Exceptions(t *testing.T) { NoEventsCheck: true, NoPermissionsCheck: true, Permissions: []manifest.Permission{{Methods: manifest.WildStrings{Value: nil}}}, - ContractEvents: []compiler.HybridEvent{ - {Name: "NotificationFromB before panic", Parameters: []compiler.HybridParameter{{Parameter: manifest.Parameter{Name: "i", Type: smartcontract.IntegerType}}}}, - {Name: "NotificationFromB after panic", Parameters: []compiler.HybridParameter{{Parameter: manifest.Parameter{Name: "i", Type: smartcontract.IntegerType}}}}, + ContractEvents: []manifest.Event{ + {Name: "NotificationFromB before panic", Parameters: []manifest.Parameter{{Name: "i", Type: smartcontract.IntegerType}}}, + {Name: "NotificationFromB after panic", Parameters: []manifest.Parameter{{Name: "i", Type: smartcontract.IntegerType}}}, }, }) e.DeployContract(t, ctrB, nil) @@ -502,7 +502,7 @@ func TestSnapshotIsolation_NestedContextException(t *testing.T) { NoPermissionsCheck: true, Name: "contractA", Permissions: []manifest.Permission{{Methods: manifest.WildStrings{Value: nil}}}, - ContractEvents: []compiler.HybridEvent{ + ContractEvents: []manifest.Event{ {Name: "Calling A"}, {Name: "Finish"}, {Name: "Caught"}, diff --git a/pkg/neotest/compile.go b/pkg/neotest/compile.go index e727f1b5f5..0251b88975 100644 --- a/pkg/neotest/compile.go +++ b/pkg/neotest/compile.go @@ -71,7 +71,6 @@ func CompileFile(t testing.TB, sender util.Uint160, srcPath string, configPath s o := &compiler.Options{} o.Name = conf.Name o.ContractEvents = conf.Events - o.DeclaredNamedTypes = conf.NamedTypes o.ContractSupportedStandards = conf.SupportedStandards o.Permissions = make([]manifest.Permission, len(conf.Permissions)) for i := range conf.Permissions { diff --git a/pkg/smartcontract/binding/generate.go b/pkg/smartcontract/binding/generate.go index e94648526e..2a4711ef01 100644 --- a/pkg/smartcontract/binding/generate.go +++ b/pkg/smartcontract/binding/generate.go @@ -91,28 +91,14 @@ type ( // if the original structure doesn't) and a number of internal fields. The // map key is in the form of `namespace.name`, the value is fully-qualified // and possibly nested description of the type structure. - NamedTypes map[string]ExtendedType `yaml:"namedtypes,omitempty"` + NamedTypes map[string]manifest.ExtendedType `yaml:"namedtypes,omitempty"` // Types contains type structure description for various types used in // smartcontract. The map key has one of the following forms: // - `methodName` for method return value; // - `mathodName.paramName` for method's parameter value. // - `eventName.paramName` for event's parameter value. - Types map[string]ExtendedType `yaml:"types,omitempty"` - Output io.Writer `yaml:"-"` - } - - ExtendedType struct { - Base smartcontract.ParamType `yaml:"base"` - Name string `yaml:"name,omitempty"` // Structure name, omitted for arrays, interfaces and maps. - Interface string `yaml:"interface,omitempty"` // Interface type name, "iterator" only for now. - Key smartcontract.ParamType `yaml:"key,omitempty"` // Key type (only simple types can be used for keys) for maps. - Value *ExtendedType `yaml:"value,omitempty"` // Value type for iterators, arrays and maps. - Fields []FieldExtendedType `yaml:"fields,omitempty"` // Ordered type data for structure fields. - } - - FieldExtendedType struct { - Field string `yaml:"field"` - ExtendedType `yaml:",inline"` + Types map[string]manifest.ExtendedType `yaml:"types,omitempty"` + Output io.Writer `yaml:"-"` } ContractTmpl struct { @@ -145,8 +131,8 @@ func NewConfig() Config { return Config{ Overrides: make(map[string]Override), CallFlags: make(map[string]callflag.CallFlag), - NamedTypes: make(map[string]ExtendedType), - Types: make(map[string]ExtendedType), + NamedTypes: make(map[string]manifest.ExtendedType), + Types: make(map[string]manifest.ExtendedType), } } @@ -323,34 +309,3 @@ func TemplateFromManifest(cfg Config, scTypeConverter func(string, smartcontract func upperFirst(s string) string { return strings.ToUpper(s[0:1]) + s[1:] } - -// Equals compares two extended types field-by-field and returns true if they are -// equal. -func (e *ExtendedType) Equals(other *ExtendedType) bool { - if e == nil && other == nil { - return true - } - if e != nil && other == nil || - e == nil && other != nil { - return false - } - if e.Base != other.Base && (e.Base != smartcontract.ByteArrayType && e.Base != smartcontract.StringType || - other.Base != smartcontract.ByteArrayType && other.Base != smartcontract.StringType) || - e.Name != other.Name || - e.Interface != other.Interface || - e.Key != other.Key { - return false - } - if len(e.Fields) != len(other.Fields) { - return false - } - for i := range e.Fields { - if e.Fields[i].Field != other.Fields[i].Field { - return false - } - if !e.Fields[i].Equals(&other.Fields[i].ExtendedType) { - return false - } - } - return (e.Value == nil && other.Value == nil) || (e.Value != nil && other.Value != nil && e.Value.Equals(other.Value)) -} diff --git a/pkg/smartcontract/binding/generate_test.go b/pkg/smartcontract/binding/generate_test.go index 64522d28c0..ac77fcf10a 100644 --- a/pkg/smartcontract/binding/generate_test.go +++ b/pkg/smartcontract/binding/generate_test.go @@ -4,50 +4,51 @@ import ( "testing" "github.com/nspcc-dev/neo-go/pkg/smartcontract" + "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" "github.com/stretchr/testify/assert" ) func TestExtendedType_Equals(t *testing.T) { - crazyT := ExtendedType{ - Base: smartcontract.StringType, + crazyT := manifest.ExtendedType{ + Type: smartcontract.StringType, Name: "qwertyu", Interface: "qwerty", Key: smartcontract.BoolType, - Value: &ExtendedType{ - Base: smartcontract.IntegerType, + Value: &manifest.ExtendedType{ + Type: smartcontract.IntegerType, }, - Fields: []FieldExtendedType{ + Fields: []manifest.Parameter{ { - Field: "qwe", - ExtendedType: ExtendedType{ - Base: smartcontract.IntegerType, + Name: "qwe", + ExtendedType: &manifest.ExtendedType{ + Type: smartcontract.IntegerType, Name: "qwer", Interface: "qw", Key: smartcontract.ArrayType, - Fields: []FieldExtendedType{ + Fields: []manifest.Parameter{ { - Field: "as", + Name: "as", }, }, }, }, { - Field: "asf", - ExtendedType: ExtendedType{ - Base: smartcontract.BoolType, + Name: "asf", + ExtendedType: &manifest.ExtendedType{ + Type: smartcontract.BoolType, }, }, { - Field: "sffg", - ExtendedType: ExtendedType{ - Base: smartcontract.AnyType, + Name: "sffg", + ExtendedType: &manifest.ExtendedType{ + Type: smartcontract.AnyType, }, }, }, } tcs := map[string]struct { - a *ExtendedType - b *ExtendedType + a *manifest.ExtendedType + b *manifest.ExtendedType expectedRes bool }{ "both nil": { @@ -57,160 +58,160 @@ func TestExtendedType_Equals(t *testing.T) { }, "a is nil": { a: nil, - b: &ExtendedType{}, + b: &manifest.ExtendedType{}, expectedRes: false, }, "b is nil": { - a: &ExtendedType{}, + a: &manifest.ExtendedType{}, b: nil, expectedRes: false, }, "base mismatch": { - a: &ExtendedType{ - Base: smartcontract.StringType, + a: &manifest.ExtendedType{ + Type: smartcontract.StringType, }, - b: &ExtendedType{ - Base: smartcontract.IntegerType, + b: &manifest.ExtendedType{ + Type: smartcontract.IntegerType, }, expectedRes: false, }, "name mismatch": { - a: &ExtendedType{ - Base: smartcontract.ArrayType, + a: &manifest.ExtendedType{ + Type: smartcontract.ArrayType, Name: "q", }, - b: &ExtendedType{ - Base: smartcontract.ArrayType, + b: &manifest.ExtendedType{ + Type: smartcontract.ArrayType, Name: "w", }, expectedRes: false, }, "number of fields mismatch": { - a: &ExtendedType{ - Base: smartcontract.ArrayType, + a: &manifest.ExtendedType{ + Type: smartcontract.ArrayType, Name: "q", - Fields: []FieldExtendedType{ + Fields: []manifest.Parameter{ { - Field: "IntField", - ExtendedType: ExtendedType{Base: smartcontract.IntegerType}, + Name: "IntField", + ExtendedType: &manifest.ExtendedType{Type: smartcontract.IntegerType}, }, }, }, - b: &ExtendedType{ - Base: smartcontract.ArrayType, + b: &manifest.ExtendedType{ + Type: smartcontract.ArrayType, Name: "w", - Fields: []FieldExtendedType{ + Fields: []manifest.Parameter{ { - Field: "IntField", - ExtendedType: ExtendedType{Base: smartcontract.IntegerType}, + Name: "IntField", + ExtendedType: &manifest.ExtendedType{Type: smartcontract.IntegerType}, }, { - Field: "BoolField", - ExtendedType: ExtendedType{Base: smartcontract.BoolType}, + Name: "BoolField", + ExtendedType: &manifest.ExtendedType{Type: smartcontract.BoolType}, }, }, }, expectedRes: false, }, "field names mismatch": { - a: &ExtendedType{ - Base: smartcontract.ArrayType, - Fields: []FieldExtendedType{ + a: &manifest.ExtendedType{ + Type: smartcontract.ArrayType, + Fields: []manifest.Parameter{ { - Field: "IntField", - ExtendedType: ExtendedType{Base: smartcontract.IntegerType}, + Name: "IntField", + ExtendedType: &manifest.ExtendedType{Type: smartcontract.IntegerType}, }, }, }, - b: &ExtendedType{ - Base: smartcontract.ArrayType, - Fields: []FieldExtendedType{ + b: &manifest.ExtendedType{ + Type: smartcontract.ArrayType, + Fields: []manifest.Parameter{ { - Field: "BoolField", - ExtendedType: ExtendedType{Base: smartcontract.BoolType}, + Name: "BoolField", + ExtendedType: &manifest.ExtendedType{Type: smartcontract.BoolType}, }, }, }, expectedRes: false, }, "field types mismatch": { - a: &ExtendedType{ - Base: smartcontract.ArrayType, - Fields: []FieldExtendedType{ + a: &manifest.ExtendedType{ + Type: smartcontract.ArrayType, + Fields: []manifest.Parameter{ { - Field: "Field", - ExtendedType: ExtendedType{Base: smartcontract.IntegerType}, + Name: "Field", + ExtendedType: &manifest.ExtendedType{Type: smartcontract.IntegerType}, }, }, }, - b: &ExtendedType{ - Base: smartcontract.ArrayType, - Fields: []FieldExtendedType{ + b: &manifest.ExtendedType{ + Type: smartcontract.ArrayType, + Fields: []manifest.Parameter{ { - Field: "Field", - ExtendedType: ExtendedType{Base: smartcontract.BoolType}, + Name: "Field", + ExtendedType: &manifest.ExtendedType{Type: smartcontract.BoolType}, }, }, }, expectedRes: false, }, "interface mismatch": { - a: &ExtendedType{Interface: "iterator"}, - b: &ExtendedType{Interface: "unknown"}, + a: &manifest.ExtendedType{Interface: "iterator"}, + b: &manifest.ExtendedType{Interface: "unknown"}, expectedRes: false, }, "value is nil": { - a: &ExtendedType{ - Base: smartcontract.StringType, + a: &manifest.ExtendedType{ + Type: smartcontract.StringType, }, - b: &ExtendedType{ - Base: smartcontract.StringType, + b: &manifest.ExtendedType{ + Type: smartcontract.StringType, }, expectedRes: true, }, "a value is not nil": { - a: &ExtendedType{ - Base: smartcontract.ArrayType, - Value: &ExtendedType{}, + a: &manifest.ExtendedType{ + Type: smartcontract.ArrayType, + Value: &manifest.ExtendedType{}, }, - b: &ExtendedType{ - Base: smartcontract.ArrayType, + b: &manifest.ExtendedType{ + Type: smartcontract.ArrayType, }, expectedRes: false, }, "b value is not nil": { - a: &ExtendedType{ - Base: smartcontract.ArrayType, + a: &manifest.ExtendedType{ + Type: smartcontract.ArrayType, }, - b: &ExtendedType{ - Base: smartcontract.ArrayType, - Value: &ExtendedType{}, + b: &manifest.ExtendedType{ + Type: smartcontract.ArrayType, + Value: &manifest.ExtendedType{}, }, expectedRes: false, }, "byte array tolerance for a": { - a: &ExtendedType{ - Base: smartcontract.StringType, + a: &manifest.ExtendedType{ + Type: smartcontract.StringType, }, - b: &ExtendedType{ - Base: smartcontract.ByteArrayType, + b: &manifest.ExtendedType{ + Type: smartcontract.ByteArrayType, }, expectedRes: true, }, "byte array tolerance for b": { - a: &ExtendedType{ - Base: smartcontract.ByteArrayType, + a: &manifest.ExtendedType{ + Type: smartcontract.ByteArrayType, }, - b: &ExtendedType{ - Base: smartcontract.StringType, + b: &manifest.ExtendedType{ + Type: smartcontract.StringType, }, expectedRes: true, }, "key mismatch": { - a: &ExtendedType{ + a: &manifest.ExtendedType{ Key: smartcontract.StringType, }, - b: &ExtendedType{ + b: &manifest.ExtendedType{ Key: smartcontract.IntegerType, }, expectedRes: false, diff --git a/pkg/smartcontract/manifest/abi.go b/pkg/smartcontract/manifest/abi.go index de85abcae7..34d3ff2616 100644 --- a/pkg/smartcontract/manifest/abi.go +++ b/pkg/smartcontract/manifest/abi.go @@ -27,8 +27,9 @@ const ( // ABI represents a contract application binary interface. type ABI struct { - Methods []Method `json:"methods"` - Events []Event `json:"events"` + Methods []Method `json:"methods"` + Events []Event `json:"events"` + NamedTypes map[string]ExtendedType `json:"namedtypes,omitempty"` } // GetMethod returns methods with the specified name. diff --git a/pkg/smartcontract/manifest/extended_type.go b/pkg/smartcontract/manifest/extended_type.go new file mode 100644 index 0000000000..035f847ac1 --- /dev/null +++ b/pkg/smartcontract/manifest/extended_type.go @@ -0,0 +1,340 @@ +package manifest + +import ( + "encoding/json" + "errors" + "fmt" + "math/big" + "regexp" + "unicode" + + "github.com/nspcc-dev/neo-go/pkg/smartcontract" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" + "gopkg.in/yaml.v3" +) + +var ( + alphaNumDot = regexp.MustCompile(`^[A-Za-z0-9.]+$`) +) + +type ExtendedType struct { + Type smartcontract.ParamType `json:"type" yaml:"type"` + Name string `json:"namedtype,omitempty" yaml:"namedtype,omitempty"` + Length uint32 `json:"length,omitempty" yaml:"length,omitempty"` + ForbidNull bool `json:"forbidnull,omitempty" yaml:"forbidnull,omitempty"` + Interface string `json:"interface,omitempty" yaml:"interface,omitempty"` + Key smartcontract.ParamType `json:"key,omitempty" yaml:"key,omitempty"` + Value *ExtendedType `json:"value,omitempty" yaml:"value,omitempty"` + Fields []Parameter `json:"fields,omitempty" yaml:"fields,omitempty"` +} + +// Equals compares two extended types field-by-field and returns true if they are +// equal. +func (e *ExtendedType) Equals(other *ExtendedType) bool { + if e == other { + return true + } + if e == nil || other == nil { + return false + } + if e.Type != other.Type && (e.Type != smartcontract.ByteArrayType && e.Type != smartcontract.StringType || + other.Type != smartcontract.ByteArrayType && other.Type != smartcontract.StringType) { + return false + } + if e.Name != other.Name || e.Interface != other.Interface || + e.Key != other.Key || e.Length != other.Length || e.ForbidNull != other.ForbidNull { + return false + } + if len(e.Fields) != len(other.Fields) { + return false + } + for i := range e.Fields { + pa := &e.Fields[i] + pb := &other.Fields[i] + if pa.Name != pb.Name { + return false + } + if pa.Type != pb.Type && (pa.Type != smartcontract.ByteArrayType && pa.Type != smartcontract.StringType || + pb.Type != smartcontract.ByteArrayType && pb.Type != smartcontract.StringType) { + return false + } + if !pa.ExtendedType.Equals(pb.ExtendedType) { + return false + } + } + return (e.Value == nil && other.Value == nil) || (e.Value != nil && other.Value != nil && e.Value.Equals(other.Value)) +} + +// UnmarshalYAML implements yaml.Unmarshaler. +func (e *ExtendedType) UnmarshalYAML(node *yaml.Node) error { + var raw map[string]any + if err := node.Decode(&raw); err != nil { + return err + } + if v, ok := raw["base"]; ok { + if _, ok := raw["type"]; !ok { + raw["type"] = v + } + delete(raw, "base") + } + if v, ok := raw["name"]; ok { + if _, ok := raw["namedtype"]; !ok { + raw["namedtype"] = v + } + delete(raw, "name") + } + b, _ := yaml.Marshal(raw) + type ext ExtendedType + var tmp ext + if err := yaml.Unmarshal(b, &tmp); err != nil { + return err + } + *e = ExtendedType(tmp) + return nil +} + +// UnmarshalJSON implements json.Unmarshaler. +func (e *ExtendedType) UnmarshalJSON(data []byte) error { + var raw map[string]json.RawMessage + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + if v, ok := raw["base"]; ok { + if _, ok := raw["type"]; !ok { + raw["type"] = v + } + delete(raw, "base") + } + if v, ok := raw["name"]; ok { + if _, ok := raw["namedtype"]; !ok { + raw["namedtype"] = v + } + delete(raw, "name") + } + b, _ := json.Marshal(raw) + type ext ExtendedType + var tmp ext + if err := json.Unmarshal(b, &tmp); err != nil { + return err + } + *e = ExtendedType(tmp) + return nil +} + +func (e *ExtendedType) ToStackItem() stackitem.Item { + m := stackitem.NewMap() + m.Add(stackitem.Make("type"), stackitem.Make(int(e.Type))) + if e.Name != "" { + m.Add(stackitem.Make("namedtype"), stackitem.Make(e.Name)) + } + if e.Length != 0 { + m.Add(stackitem.Make("length"), stackitem.Make(int(e.Length))) + } + if e.ForbidNull { + m.Add(stackitem.Make("forbidnull"), stackitem.Make(true)) + } + if e.Interface != "" { + m.Add(stackitem.Make("interface"), stackitem.Make(e.Interface)) + } + if e.Key != smartcontract.AnyType { + m.Add(stackitem.Make("key"), stackitem.Make(int(e.Key))) + } + if e.Value != nil { + m.Add(stackitem.Make("value"), e.Value.ToStackItem()) + } + if e.Fields != nil { + fields := make([]stackitem.Item, len(e.Fields)) + for i := range e.Fields { + fields[i] = e.Fields[i].ToStackItem() + } + m.Add(stackitem.Make("fields"), stackitem.NewArray(fields)) + } + return m +} + +func (e *ExtendedType) FromStackItem(item stackitem.Item) error { + if item == nil { + return errors.New("expected non-nil item") + } + if item.Type() != stackitem.MapT { + return errors.New("invalid ExtendedType stackitem type") + } + raw := item.Value().([]stackitem.MapElement) + m := make(map[string]stackitem.Item, len(raw)) + for _, mElem := range raw { + ks, err := stackitem.ToString(mElem.Key) + if err != nil { + continue + } + m[ks] = mElem.Value + } + ti, ok := m["type"] + if !ok { + return errors.New("incorrect type") + } + if ti.Type() != stackitem.IntegerT { + return errors.New("type must be integer") + } + e.Type = smartcontract.ParamType(ti.Value().(*big.Int).Int64()) + nti, ok := m["namedtype"] + if ok { + var err error + if e.Name, err = stackitem.ToString(nti); err != nil { + return fmt.Errorf("can't get namedtype: %w", err) + } + } else { + e.Name = "" + } + li, ok := m["length"] + if ok { + if li.Type() != stackitem.IntegerT { + return errors.New("length must be integer or null") + } + e.Length = uint32(li.Value().(*big.Int).Uint64()) + } else { + e.Length = 0 + } + fni, ok := m["forbidnull"] + if ok { + if fni.Type() != stackitem.BooleanT { + return errors.New("forbidnull must be boolean or null") + } + e.ForbidNull = fni.Value().(bool) + } else { + e.ForbidNull = false + } + ii, ok := m["interface"] + if ok { + if ii.Type() != stackitem.ByteArrayT { + return errors.New("interface must be bytearray or null") + } + e.Interface = string(ii.Value().([]byte)) + } else { + e.Interface = "" + } + ki, ok := m["key"] + if ok { + if ki.Type() != stackitem.IntegerT { + return errors.New("key must be integer or null") + } + e.Key = smartcontract.ParamType(ki.Value().(*big.Int).Int64()) + } else { + e.Key = smartcontract.AnyType + } + vi, ok := m["value"] + if ok { + e.Value = new(ExtendedType) + if err := e.Value.FromStackItem(vi); err != nil { + return fmt.Errorf("can't get value: %w", err) + } + } else { + e.Value = nil + } + fi, ok := m["fields"] + if ok { + if fi.Type() != stackitem.ArrayT { + return errors.New("fields must be array or null") + } + fields := fi.Value().([]stackitem.Item) + e.Fields = make([]Parameter, len(fields)) + for i := range fields { + if err := e.Fields[i].FromStackItem(fields[i]); err != nil { + return err + } + } + } else { + e.Fields = nil + } + return nil +} + +func (e *ExtendedType) IsValid() error { + if _, err := smartcontract.ConvertToParamType(int(e.Type)); err != nil { + return err + } + if e.Name != "" { + if e.Type != smartcontract.ArrayType { + return fmt.Errorf("`ExtendedType.Name` field can not be specified for %s", e.Type) + } + if len(e.Name) > 64 { + return errors.New("`ExtendedType.Name` must not be longer than 64 characters") + } + if !unicode.IsLetter(rune(e.Name[0])) { + return errors.New("`ExtendedType.Name` must start with a letter") + } + if !alphaNumDot.MatchString(e.Name) { + return errors.New("`ExtendedType.Name` can contain alphanumeric characters and dots") + } + } + if e.Length != 0 { + switch e.Type { + case smartcontract.IntegerType, smartcontract.ByteArrayType, + smartcontract.StringType, smartcontract.ArrayType: + default: + return fmt.Errorf("`ExtendedType.Length` field can not be specified for %s", e.Type) + } + } + if e.ForbidNull { + switch e.Type { + case smartcontract.Hash160Type, smartcontract.Hash256Type, smartcontract.ByteArrayType, + smartcontract.StringType, smartcontract.ArrayType, smartcontract.MapType, + smartcontract.InteropInterfaceType: + default: + return fmt.Errorf("`ExtendedType.ForbidNull` field can not be specified for %s", e.Type) + } + } + if e.Interface != "" { + if e.Type != smartcontract.InteropInterfaceType { + return fmt.Errorf("`ExtendedType.Interface` field can not be specified for %s", e.Type) + } + if e.Interface != "IIterator" { + return fmt.Errorf("invalid value for `ExtendedType.Interface` field: %s", e.Interface) + } + } else if e.Type == smartcontract.InteropInterfaceType { + return fmt.Errorf("`ExtendedType.Interface` field is required for %s", e.Type) + } + if e.Key != smartcontract.AnyType { + if e.Type != smartcontract.MapType { + return fmt.Errorf("`ExtendedType.Key` field can not be specified for %s", e.Type) + } + switch e.Key { + case smartcontract.SignatureType, smartcontract.BoolType, smartcontract.IntegerType, + smartcontract.Hash160Type, smartcontract.Hash256Type, smartcontract.ByteArrayType, + smartcontract.PublicKeyType, smartcontract.StringType: + default: + return fmt.Errorf("key %s is not allowed for map definitions", e.Key) + } + } else if e.Type == smartcontract.MapType { + return fmt.Errorf("`ExtendedType.Key` field is required for %s", e.Type) + } + if e.Value != nil { + switch e.Type { + case smartcontract.ArrayType, smartcontract.InteropInterfaceType, smartcontract.MapType: + if err := e.Value.IsValid(); err != nil { + return err + } + default: + return fmt.Errorf("`ExtendedType.Value` field can not be specified for %s", e.Type) + } + } else { + if e.Type == smartcontract.ArrayType && e.Name == "" { + return fmt.Errorf("`ExtendedType.Value` field is required for %s", e.Type) + } + switch e.Type { + case smartcontract.InteropInterfaceType, smartcontract.MapType: + return fmt.Errorf("`ExtendedType.Value` field is required for %s", e.Type) + default: + } + } + if e.Fields != nil { + if e.Type != smartcontract.ArrayType { + return fmt.Errorf("`ExtendedType.Fields` field can not be specified for %s", e.Type) + } + for i := range e.Fields { + if err := e.Fields[i].IsValid(); err != nil { + return err + } + } + } + return nil +} diff --git a/pkg/smartcontract/manifest/extended_type_test.go b/pkg/smartcontract/manifest/extended_type_test.go new file mode 100644 index 0000000000..20ebebed0b --- /dev/null +++ b/pkg/smartcontract/manifest/extended_type_test.go @@ -0,0 +1,367 @@ +package manifest + +import ( + "encoding/json" + "strings" + "testing" + + "github.com/nspcc-dev/neo-go/pkg/smartcontract" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" + "github.com/stretchr/testify/require" + "gopkg.in/yaml.v3" +) + +func TestExtendedType_UnmarshalYAML(t *testing.T) { + testCases := []struct { + name string + src string + expected ExtendedType + expectedErr string + }{ + { + name: "invalid yaml", + expectedErr: "line 1: cannot unmarshal !!str `invalid` into map[string]interface", + src: `invalid`, + }, + { + name: "invalid field type", + expectedErr: "bad parameter type: invalid", + src: `type: invalid`, + }, + { + name: "difficult extended type", + expected: ExtendedType{ + Type: smartcontract.MapType, + Key: smartcontract.ArrayType, + ForbidNull: true, + Value: &ExtendedType{ + Type: smartcontract.ArrayType, + Name: "SomeStruct", + Fields: []Parameter{ + { + Name: "N", + Type: smartcontract.IntegerType, + }, + { + Name: "S", + Type: smartcontract.ArrayType, + ExtendedType: &ExtendedType{ + Type: smartcontract.ArrayType, + Name: "InternalStruct", + Fields: []Parameter{ + { + Name: "B", + Type: smartcontract.BoolType, + }, + }, + }, + }, + }, + }, + }, + src: ` +base: Map +forbidnull: true +key: Array +value: + base: Array + name: SomeStruct + fields: + - name: "N" + type: Integer + - name: S + type: Array + extendedtype: + base: Array + name: InternalStruct + fields: + - name: B + type: Boolean +`, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + var ( + et ExtendedType + err = yaml.Unmarshal([]byte(tc.src), &et) + ) + if tc.expectedErr != "" { + require.Contains(t, err.Error(), tc.expectedErr) + } else { + require.NoError(t, err) + require.True(t, et.Equals(&tc.expected)) + } + }) + } +} + +func TestExtendedType_UnmarshalJSON(t *testing.T) { + testCases := []struct { + name string + src string + expected ExtendedType + expectedErr string + }{ + { + name: "invalid json", + expectedErr: "invalid character", + src: `invalid`, + }, + { + name: "invalid field type", + expectedErr: "bad parameter type: invalid", + src: `{"type":"invalid"}`, + }, + { + name: "difficult extended type", + expected: ExtendedType{ + Type: smartcontract.MapType, + Key: smartcontract.ArrayType, + ForbidNull: true, + Value: &ExtendedType{ + Type: smartcontract.ArrayType, + Name: "SomeStruct", + Fields: []Parameter{ + { + Name: "N", + Type: smartcontract.IntegerType, + }, + { + Name: "S", + Type: smartcontract.ArrayType, + ExtendedType: &ExtendedType{ + Type: smartcontract.ArrayType, + Name: "InternalStruct", + Fields: []Parameter{ + { + Name: "B", + Type: smartcontract.BoolType, + }, + }, + }, + }, + }, + }, + }, + src: ` +{ + "base": "Map", + "forbidnull": true, + "key": "Array", + "value": { + "base": "Array", + "name": "SomeStruct", + "fields": [ + { + "name": "N", + "type": "Integer" + }, + { + "name": "S", + "type": "Array", + "extendedtype": { + "base": "Array", + "name": "InternalStruct", + "fields": [ + { + "name": "B", + "type": "Boolean" + } + ] + } + } + ] + } +}`, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + var ( + et ExtendedType + err = json.Unmarshal([]byte(tc.src), &et) + ) + if tc.expectedErr != "" { + require.ErrorContains(t, err, tc.expectedErr) + } else { + require.NoError(t, err) + require.True(t, et.Equals(&tc.expected)) + } + }) + } +} + +func TestExtendedType_ToStackItemFromStackItem(t *testing.T) { + testCases := []struct { + et *ExtendedType + expected stackitem.Item + }{ + { + et: &ExtendedType{ + Type: smartcontract.MapType, + Key: smartcontract.IntegerType, + ForbidNull: true, + Value: &ExtendedType{ + Type: smartcontract.ByteArrayType, + Length: 30, + }, + }, + expected: stackitem.NewMapWithValue([]stackitem.MapElement{ + {Key: stackitem.Make("type"), Value: stackitem.Make(int(smartcontract.MapType))}, + {Key: stackitem.Make("forbidnull"), Value: stackitem.Make(true)}, + {Key: stackitem.Make("key"), Value: stackitem.Make(int(smartcontract.IntegerType))}, + {Key: stackitem.Make("value"), Value: stackitem.NewMapWithValue([]stackitem.MapElement{ + {Key: stackitem.Make("type"), Value: stackitem.Make(int(smartcontract.ByteArrayType))}, + {Key: stackitem.Make("length"), Value: stackitem.Make(30)}, + })}, + }), + }, + { + et: &ExtendedType{ + Type: smartcontract.ArrayType, + Name: "SomeStruct", + Fields: []Parameter{ + { + Name: "S", + Type: smartcontract.ArrayType, + ExtendedType: &ExtendedType{ + Type: smartcontract.ArrayType, + Name: "InternalStruct", + Fields: []Parameter{ + { + Name: "B", + Type: smartcontract.BoolType, + }, + }, + }, + }, + }, + }, + expected: stackitem.NewMapWithValue([]stackitem.MapElement{ + {Key: stackitem.Make("type"), Value: stackitem.Make(int(smartcontract.ArrayType))}, + {Key: stackitem.Make("namedtype"), Value: stackitem.Make("SomeStruct")}, + {Key: stackitem.Make("fields"), Value: stackitem.NewArray([]stackitem.Item{ + stackitem.NewStruct([]stackitem.Item{ + stackitem.Make("S"), + stackitem.Make(int(smartcontract.ArrayType)), + stackitem.NewMapWithValue([]stackitem.MapElement{ + {Key: stackitem.Make("type"), Value: stackitem.Make(int(smartcontract.ArrayType))}, + {Key: stackitem.Make("namedtype"), Value: stackitem.Make("InternalStruct")}, + {Key: stackitem.Make("fields"), Value: stackitem.NewArray([]stackitem.Item{ + stackitem.NewStruct([]stackitem.Item{ + stackitem.Make("B"), + stackitem.Make(int(smartcontract.BoolType)), + }), + })}, + }), + }), + })}, + }), + }, + } + for _, tc := range testCases { + CheckToFromStackItem(t, tc.et, tc.expected) + } +} + +func TestExtendedType_FromStackItemErrors(t *testing.T) { + errCases := map[string]stackitem.Item{ + "expected non-nil item": nil, + "invalid ExtendedType stackitem type": stackitem.Make(nil), + "incorrect type": stackitem.NewMap(), + "type must be integer": stackitem.NewMapWithValue([]stackitem.MapElement{ + {Key: stackitem.Make("type"), Value: stackitem.Make("invalidtype")}}, + ), + "can't get namedtype": stackitem.NewMapWithValue([]stackitem.MapElement{ + {Key: stackitem.Make("type"), Value: stackitem.Make(0)}, + {Key: stackitem.Make("namedtype"), Value: stackitem.Make(nil)}}, + ), + "length must be integer or null": stackitem.NewMapWithValue([]stackitem.MapElement{ + {Key: stackitem.Make("type"), Value: stackitem.Make(0)}, + {Key: stackitem.Make("length"), Value: stackitem.Make(nil)}}, + ), + "forbidnull must be boolean or null": stackitem.NewMapWithValue([]stackitem.MapElement{ + {Key: stackitem.Make("type"), Value: stackitem.Make(0)}, + {Key: stackitem.Make("forbidnull"), Value: stackitem.Make(nil)}}, + ), + "interface must be bytearray or null": stackitem.NewMapWithValue([]stackitem.MapElement{ + {Key: stackitem.Make("type"), Value: stackitem.Make(0)}, + {Key: stackitem.Make("interface"), Value: stackitem.Make(nil)}}, + ), + "key must be integer or null": stackitem.NewMapWithValue([]stackitem.MapElement{ + {Key: stackitem.Make("type"), Value: stackitem.Make(0)}, + {Key: stackitem.Make("key"), Value: stackitem.Make(nil)}}, + ), + "can't get value": stackitem.NewMapWithValue([]stackitem.MapElement{ + {Key: stackitem.Make("type"), Value: stackitem.Make(0)}, + {Key: stackitem.Make("value"), Value: stackitem.Make(nil)}}, + ), + "fields must be array or null": stackitem.NewMapWithValue([]stackitem.MapElement{ + {Key: stackitem.Make("type"), Value: stackitem.Make(0)}, + {Key: stackitem.Make("fields"), Value: stackitem.Make(nil)}}, + ), + "invalid Parameter stackitem type": stackitem.NewMapWithValue([]stackitem.MapElement{ + {Key: stackitem.Make("type"), Value: stackitem.Make(0)}, + {Key: stackitem.Make("fields"), Value: stackitem.NewArray([]stackitem.Item{ + stackitem.Make(nil), + })}}, + ), + } + for err, errCase := range errCases { + t.Run(err, func(t *testing.T) { + e := new(ExtendedType) + require.ErrorContains(t, e.FromStackItem(errCase), err) + }) + } +} + +func TestExtendedType_IsValid(t *testing.T) { + et := ExtendedType{Type: smartcontract.ParamType(-42)} + require.Error(t, et.IsValid()) + et.Type = smartcontract.UnknownType + et.Name = strings.Repeat("a", 65) + require.ErrorContains(t, et.IsValid(), "`ExtendedType.Name` field can not be specified") + et.Type = smartcontract.ArrayType + require.ErrorContains(t, et.IsValid(), "`ExtendedType.Name` must not be longer than 64 characters") + et.Name = "42" + require.ErrorContains(t, et.IsValid(), "`ExtendedType.Name` must start with a letter") + et.Name = "a_" + require.ErrorContains(t, et.IsValid(), "`ExtendedType.Name` can contain alphanumeric characters and dots") + et.Type = smartcontract.UnknownType + et.Name = "" + et.Length = 42 + require.ErrorContains(t, et.IsValid(), "`ExtendedType.Length` field can not be specified") + et.Length = 0 + et.ForbidNull = true + require.ErrorContains(t, et.IsValid(), "`ExtendedType.ForbidNull` field can not be specified") + et.ForbidNull = false + et.Interface = "SomeInterface" + require.ErrorContains(t, et.IsValid(), "`ExtendedType.Interface` field can not be specified") + et.Type = smartcontract.InteropInterfaceType + require.ErrorContains(t, et.IsValid(), "invalid value for `ExtendedType.Interface` field") + et.Interface = "" + require.ErrorContains(t, et.IsValid(), "`ExtendedType.Interface` field is required") + et.Type = smartcontract.UnknownType + et.Key = smartcontract.UnknownType + require.ErrorContains(t, et.IsValid(), "`ExtendedType.Key` field can not be specified") + et.Type = smartcontract.MapType + require.ErrorContains(t, et.IsValid(), "not allowed for map definitions") + et.Key = smartcontract.AnyType + require.ErrorContains(t, et.IsValid(), "`ExtendedType.Key` field is required") + et.Type = smartcontract.ArrayType + require.ErrorContains(t, et.IsValid(), "`ExtendedType.Value` field is required") + et.Value = &ExtendedType{Type: smartcontract.ParamType(-42)} + require.Error(t, et.IsValid()) + et.Type = smartcontract.UnknownType + require.ErrorContains(t, et.IsValid(), "`ExtendedType.Value` field can not be specified") + et.Value = nil + et.Fields = []Parameter{{Name: "p", Type: smartcontract.ParamType(-42)}} + require.ErrorContains(t, et.IsValid(), "`ExtendedType.Fields` field can not be specified") + et.Type = smartcontract.ArrayType + et.Value = &ExtendedType{Type: smartcontract.UnknownType} + require.Error(t, et.IsValid()) + et.Fields = nil + require.Nil(t, et.IsValid()) +} diff --git a/pkg/smartcontract/manifest/method.go b/pkg/smartcontract/manifest/method.go index 85f512fd0c..c1efcb8bcd 100644 --- a/pkg/smartcontract/manifest/method.go +++ b/pkg/smartcontract/manifest/method.go @@ -9,11 +9,12 @@ import ( // Method represents method's metadata. type Method struct { - Name string `json:"name"` - Offset int `json:"offset"` - Parameters []Parameter `json:"parameters"` - ReturnType smartcontract.ParamType `json:"returntype"` - Safe bool `json:"safe"` + Name string `json:"name"` + Offset int `json:"offset"` + Parameters []Parameter `json:"parameters"` + ReturnType smartcontract.ParamType `json:"returntype"` + ExtendedReturnType *ExtendedType `json:"extendedreturntype,omitempty"` + Safe bool `json:"safe"` } // IsValid checks Method consistency and correctness. diff --git a/pkg/smartcontract/manifest/parameter.go b/pkg/smartcontract/manifest/parameter.go index 7cccb2b222..40f5d3471c 100644 --- a/pkg/smartcontract/manifest/parameter.go +++ b/pkg/smartcontract/manifest/parameter.go @@ -2,18 +2,21 @@ package manifest import ( "cmp" + "encoding/json" "errors" "fmt" "slices" "github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" + "gopkg.in/yaml.v3" ) // Parameter represents smartcontract's parameter's definition. type Parameter struct { - Name string `json:"name"` - Type smartcontract.ParamType `json:"type"` + Name string `json:"name"` + Type smartcontract.ParamType `json:"type"` + ExtendedType *ExtendedType `json:"extendedtype,omitempty" yaml:"extendedtype,omitempty"` } // Parameters is just an array of Parameter. @@ -35,16 +38,25 @@ func (p *Parameter) IsValid() error { if p.Type == smartcontract.VoidType { return errors.New("void parameter") } + if p.ExtendedType != nil { + if err := p.ExtendedType.IsValid(); err != nil { + return err + } + } _, err := smartcontract.ConvertToParamType(int(p.Type)) return err } // ToStackItem converts Parameter to stackitem.Item. func (p *Parameter) ToStackItem() stackitem.Item { - return stackitem.NewStruct([]stackitem.Item{ + items := []stackitem.Item{ stackitem.Make(p.Name), stackitem.Make(int(p.Type)), - }) + } + if p.ExtendedType != nil { + items = append(items, p.ExtendedType.ToStackItem()) + } + return stackitem.NewStruct(items) } // FromStackItem converts stackitem.Item to Parameter. @@ -54,7 +66,7 @@ func (p *Parameter) FromStackItem(item stackitem.Item) error { return errors.New("invalid Parameter stackitem type") } param := item.Value().([]stackitem.Item) - if len(param) != 2 { + if len(param) < 2 { return errors.New("invalid Parameter stackitem length") } p.Name, err = stackitem.ToString(param[0]) @@ -69,6 +81,13 @@ func (p *Parameter) FromStackItem(item stackitem.Item) error { if err != nil { return err } + if len(param) == 2 || param[2].Value() == nil { + return nil + } + p.ExtendedType = &ExtendedType{} + if err = p.ExtendedType.FromStackItem(param[2]); err != nil { + return err + } return nil } @@ -107,3 +126,102 @@ func sliceHasDups[S ~[]E, E any](x S, cmp func(a, b E) int) bool { } return false } + +// UnmarshalYAML implements yaml.Unmarshaler. +func (p *Parameter) UnmarshalYAML(node *yaml.Node) error { + var raw map[string]any + if err := node.Decode(&raw); err != nil { + return err + } + if _, ok := raw["extendedtype"]; !ok { + m := make(map[string]any) + _, fieldExist := raw["field"] + for k, v := range raw { + if k != "field" && k != "type" && (k != "name" || fieldExist) { + m[k] = v + delete(raw, k) + } + } + if len(m) > 0 { + raw["extendedtype"] = m + } + } + if v, ok := raw["field"]; ok { + if _, ok := raw["name"]; !ok { + raw["name"] = v + } + delete(raw, "field") + } + var tmpET *ExtendedType + if et, ok := raw["extendedtype"]; ok { + b, _ := yaml.Marshal(et) + tmpET = &ExtendedType{} + if err := yaml.Unmarshal(b, tmpET); err != nil { + return err + } + delete(raw, "extendedtype") + if _, ok := raw["type"]; !ok { + raw["type"] = tmpET.Type.String() + } + } + b, _ := yaml.Marshal(raw) + type param Parameter + var tmpParam param + if err := yaml.Unmarshal(b, &tmpParam); err != nil { + return err + } + if tmpET != nil && tmpParam.Type != tmpET.Type { + return fmt.Errorf("conflicting types: parameter type %v vs extendedtype type %v", tmpParam.Type, tmpET.Type) + } + *p = Parameter(tmpParam) + p.ExtendedType = tmpET + return nil +} + +// UnmarshalJSON implements json.Unmarshaler. +func (p *Parameter) UnmarshalJSON(data []byte) error { + var raw map[string]json.RawMessage + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + if _, ok := raw["extendedtype"]; !ok { + m := make(map[string]json.RawMessage) + _, fieldExist := raw["field"] + for k, v := range raw { + if k != "field" && k != "type" && (k != "name" || fieldExist) { + m[k] = v + delete(raw, k) + } + } + if len(m) > 0 { + b, err := json.Marshal(m) + if err != nil { + return err + } + raw["extendedtype"] = b + } + } + var tmpET *ExtendedType + if etRaw, ok := raw["extendedtype"]; ok { + tmpET = &ExtendedType{} + if err := json.Unmarshal(etRaw, tmpET); err != nil { + return err + } + delete(raw, "extendedtype") + if _, ok := raw["type"]; !ok { + raw["type"] = json.RawMessage(tmpET.Type.String()) + } + } + b, _ := json.Marshal(raw) + type param Parameter + var tmpParam param + if err := json.Unmarshal(b, &tmpParam); err != nil { + return err + } + if tmpET != nil && tmpParam.Type != tmpET.Type { + return fmt.Errorf("conflicting types: parameter type %v vs extendedtype type %v", tmpParam.Type, tmpET.Type) + } + *p = Parameter(tmpParam) + p.ExtendedType = tmpET + return nil +} diff --git a/pkg/smartcontract/manifest/parameter_test.go b/pkg/smartcontract/manifest/parameter_test.go index 9826e5f2a0..79c549d826 100644 --- a/pkg/smartcontract/manifest/parameter_test.go +++ b/pkg/smartcontract/manifest/parameter_test.go @@ -7,6 +7,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" "github.com/stretchr/testify/require" + "gopkg.in/yaml.v3" ) func TestParametersAreValid(t *testing.T) { @@ -28,6 +29,12 @@ func TestParametersAreValid(t *testing.T) { ps[0].Type = smartcontract.BoolType require.NoError(t, ps.AreValid()) + ps[0].ExtendedType = &ExtendedType{Type: 0x42} + require.Error(t, ps.AreValid()) + + ps[0].ExtendedType = &ExtendedType{Type: smartcontract.IntegerType} + require.NoError(t, ps.AreValid()) + ps = append(ps, Parameter{Name: "qwerty"}) require.Error(t, ps.AreValid()) } @@ -46,11 +53,12 @@ func TestParameter_ToStackItemFromStackItem(t *testing.T) { func TestParameter_FromStackItemErrors(t *testing.T) { errCases := map[string]stackitem.Item{ - "not a struct": stackitem.NewArray([]stackitem.Item{}), - "invalid length": stackitem.NewStruct([]stackitem.Item{}), - "invalid name type": stackitem.NewStruct([]stackitem.Item{stackitem.NewInterop(nil), stackitem.Null{}}), - "invalid type type": stackitem.NewStruct([]stackitem.Item{stackitem.NewByteArray([]byte{}), stackitem.Null{}}), - "invalid type value": stackitem.NewStruct([]stackitem.Item{stackitem.NewByteArray([]byte{}), stackitem.NewBigInteger(big.NewInt(-100500))}), + "not a struct": stackitem.NewArray([]stackitem.Item{}), + "invalid length": stackitem.NewStruct([]stackitem.Item{}), + "invalid name type": stackitem.NewStruct([]stackitem.Item{stackitem.NewInterop(nil), stackitem.Null{}}), + "invalid type type": stackitem.NewStruct([]stackitem.Item{stackitem.NewByteArray([]byte{}), stackitem.Null{}}), + "invalid type value": stackitem.NewStruct([]stackitem.Item{stackitem.NewByteArray([]byte{}), stackitem.NewBigInteger(big.NewInt(-100500))}), + "invalid ExtendedType stackitem type": stackitem.NewStruct([]stackitem.Item{stackitem.Make([]byte{}), stackitem.Make(int(smartcontract.IntegerType)), stackitem.NewMap()}), } for name, errCase := range errCases { t.Run(name, func(t *testing.T) { @@ -59,3 +67,140 @@ func TestParameter_FromStackItemErrors(t *testing.T) { }) } } + +func TestParameter_UnmarshalYAML(t *testing.T) { + testCases := []struct { + name string + src string + expected Parameter + expectedErr string + }{ + { + name: "invalid yaml", + src: `invalid`, + expectedErr: "cannot unmarshal", + }, + { + name: "invalid field type", + src: `type: invalid`, + expectedErr: "bad parameter type", + }, + { + name: "invalid extended type", + src: `extendedtype: invalid`, + expectedErr: "cannot unmarshal !!str `invalid`", + }, + { + name: "difficult parameter", + src: ` +name: p +extendedtype: + base: Array + name: SomeStruct + fields: + - field: S + base: Array + name: InternalStruct + fields: + - name: B + type: Boolean +`, + expectedErr: "", + expected: Parameter{ + Name: "p", + Type: smartcontract.ArrayType, // should be set from extendedtype.base + ExtendedType: &ExtendedType{ + Type: smartcontract.ArrayType, + Name: "SomeStruct", + Fields: []Parameter{ + { + Name: "S", + Type: smartcontract.ArrayType, + ExtendedType: &ExtendedType{ + Type: smartcontract.ArrayType, + Name: "InternalStruct", + Fields: []Parameter{ + { + Name: "B", + Type: smartcontract.BoolType, + }, + }, + }, + }, + }, + }, + }, + }, + { + name: "conflicting type and extendedtype", + src: ` +type: Integer +extendedtype: + base: String +`, + expectedErr: "conflicting types", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + var p Parameter + err := yaml.Unmarshal([]byte(tc.src), &p) + + if tc.expectedErr != "" { + require.ErrorContains(t, err, tc.expectedErr) + } else { + require.NoError(t, err) + require.Equal(t, tc.expected, p) + } + }) + } +} + +func TestParameter_UnmarshalJSON(t *testing.T) { + testCases := []struct { + name string + src string + expected Parameters + expectedErr string + }{ + { + name: "invalid json", + src: `invalid`, + expectedErr: "cannot unmarshal", + }, + { + name: "invalid field type", + src: `{"type":"invalid"}`, + expectedErr: "bad parameter type", + }, + { + name: "invalid extended type", + src: `{"extendedtype":"invalid"}`, + expectedErr: "cannot unmarshal !!str `invalid`", + }, + { + name: "conflicting type and extendedtype", + src: ` +type: Integer +extendedtype: + base: String +`, + expectedErr: "conflicting types", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + var p Parameter + err := yaml.Unmarshal([]byte(tc.src), &p) + + if tc.expectedErr != "" { + require.ErrorContains(t, err, tc.expectedErr) + } else { + require.NoError(t, err) + require.Equal(t, tc.expected, p) + } + }) + } +} diff --git a/pkg/smartcontract/rpcbinding/binding.go b/pkg/smartcontract/rpcbinding/binding.go index 8348f76afe..afce952fbe 100644 --- a/pkg/smartcontract/rpcbinding/binding.go +++ b/pkg/smartcontract/rpcbinding/binding.go @@ -133,7 +133,7 @@ type {{toTypeNameUpper $name}}Contract {{toTypeNameLower $name}}.Contract // {{toTypeName $typ.Name}} is a contract-specific {{$typ.Name}} type used by its methods. type {{toTypeName $typ.Name}} struct { {{- range $m := $typ.Fields}} - {{ upperFirst .Field}} {{etTypeToStr .ExtendedType}} + {{ upperFirst .Name}} {{etTypeToStr .ExtendedType}} {{- end}} } {{end}} @@ -294,9 +294,9 @@ func (res *{{toTypeName $typ.Name}}) FromStackItem(item stackitem.Item) error { ) {{- range $m := $typ.Fields}} index++ - res.{{ upperFirst .Field}}, err = {{etTypeConverter .ExtendedType "arr[index]"}} + res.{{ upperFirst .Name}}, err = {{etTypeConverter .ExtendedType "arr[index]"}} if err != nil { - return fmt.Errorf("field {{ upperFirst .Field}}: %w", err) + return fmt.Errorf("field {{ upperFirst .Name}}: %w", err) } {{end}} {{- end}} @@ -317,9 +317,9 @@ func (res *{{toTypeName $typ.Name}}) ToStackItem() (stackitem.Item, error) { ) {{- range $m := $typ.Fields}} - itm, err = {{goTypeConverter .ExtendedType (print "res." (upperFirst .Field))}} + itm, err = {{goTypeConverter .ExtendedType (print "res." (upperFirst .Name))}} if err != nil { - return nil, fmt.Errorf("field {{ upperFirst .Field}}: %w", err) + return nil, fmt.Errorf("field {{ upperFirst .Name}}: %w", err) } items = append(items, itm) {{end}} @@ -341,9 +341,9 @@ func (res *{{toTypeName $typ.Name}}) ToSCParameter() (smartcontract.Parameter, e ) {{- range $m := $typ.Fields}} - prm, err = {{scTypeConverter .ExtendedType (print "res." (upperFirst .Field))}} + prm, err = {{scTypeConverter .ExtendedType (print "res." (upperFirst .Name))}} if err != nil { - return smartcontract.Parameter{}, fmt.Errorf("field {{ upperFirst .Field}}: %w", err) + return smartcontract.Parameter{}, fmt.Errorf("field {{ upperFirst .Name}}: %w", err) } prms = append(prms, prm) {{end}} @@ -418,7 +418,7 @@ type ( SafeMethods []SafeMethodTmpl CustomEvents []CustomEventTemplate - NamedTypes []binding.ExtendedType + NamedTypes []manifest.ExtendedType IsNep11D bool IsNep11ND bool @@ -439,7 +439,7 @@ type ( binding.MethodTmpl Unwrapper string ItemTo string - ExtendedReturn binding.ExtendedType + ExtendedReturn manifest.ExtendedType } CustomEventTemplate struct { @@ -458,7 +458,7 @@ type ( // ExtType holds the event parameter's type information provided by Manifest, // i.e. simple types only. - ExtType binding.ExtendedType + ExtType manifest.ExtendedType } ) @@ -565,17 +565,17 @@ func Generate(cfg binding.Config) error { ctr.ContractTmpl = binding.TemplateFromManifest(cfg, scTypeToGo) ctr = scTemplateToRPC(cfg, ctr, imports, scTypeToGo) - ctr.NamedTypes = make([]binding.ExtendedType, 0, len(cfg.NamedTypes)) - for k := range cfg.NamedTypes { - ctr.NamedTypes = append(ctr.NamedTypes, cfg.NamedTypes[k]) + ctr.NamedTypes = make([]manifest.ExtendedType, 0, len(cfg.Manifest.ABI.NamedTypes)) + for k := range cfg.Manifest.ABI.NamedTypes { + ctr.NamedTypes = append(ctr.NamedTypes, cfg.Manifest.ABI.NamedTypes[k]) } - slices.SortFunc(ctr.NamedTypes, func(a, b binding.ExtendedType) int { return cmp.Compare(a.Name, b.Name) }) + slices.SortFunc(ctr.NamedTypes, func(a, b manifest.ExtendedType) int { return cmp.Compare(a.Name, b.Name) }) // Check resulting named types and events don't have duplicating field names. for _, t := range ctr.NamedTypes { fDict := make(map[string]struct{}) for _, n := range t.Fields { - name := upperFirst(n.Field) + name := upperFirst(n.Name) if _, ok := fDict[name]; ok { return fmt.Errorf("named type `%s` has two fields with identical resulting binding name `%s`", t.Name, name) } @@ -596,8 +596,8 @@ func Generate(cfg binding.Config) error { var srcTemplate = template.Must(template.New("generate").Funcs(template.FuncMap{ "addIndent": addIndent, "etTypeConverter": etTypeConverter, - "etTypeToStr": func(et binding.ExtendedType) string { - r, _ := extendedTypeToGo(et, cfg.NamedTypes) + "etTypeToStr": func(et manifest.ExtendedType) string { + r, _ := extendedTypeToGo(et, cfg.Manifest.ABI.NamedTypes) return r }, "goTypeConverter": goTypeConverter, @@ -664,10 +664,10 @@ func dropNep24Types(cfg binding.Config) binding.Config { // Find structure returned by standard.MethodRoyaltyInfo method // and remove it from binding.Config.NamedTypes as it will be imported from nep24 package. if royaltyInfo, ok := cfg.Types[standard.MethodRoyaltyInfo]; ok && royaltyInfo.Value != nil { - returnType, exists := cfg.NamedTypes[royaltyInfo.Value.Name] + returnType, exists := cfg.Manifest.ABI.NamedTypes[royaltyInfo.Value.Name] if !exists || returnType.Fields == nil || len(returnType.Fields) != 2 || - returnType.Fields[0].Base != smartcontract.Hash160Type || - returnType.Fields[1].Base != smartcontract.IntegerType { + returnType.Fields[0].Type != smartcontract.Hash160Type || + returnType.Fields[1].Type != smartcontract.IntegerType { return cfg } targetTypeName = royaltyInfo.Value.Name @@ -685,13 +685,13 @@ func dropNep24Types(cfg binding.Config) binding.Config { } if found { - delete(cfg.NamedTypes, targetTypeName) + delete(cfg.Manifest.ABI.NamedTypes, targetTypeName) } return cfg } -func extendedTypeToGo(et binding.ExtendedType, named map[string]binding.ExtendedType) (string, string) { - switch et.Base { +func extendedTypeToGo(et manifest.ExtendedType, named map[string]manifest.ExtendedType) (string, string) { + switch et.Type { case smartcontract.AnyType: return "any", "" case smartcontract.BoolType: @@ -714,7 +714,7 @@ func extendedTypeToGo(et binding.ExtendedType, named map[string]binding.Extended if len(et.Name) > 0 { return "*" + toTypeName(et.Name), "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" } else if et.Value != nil { - if et.Value.Base == smartcontract.PublicKeyType { // Special array wrapper. + if et.Value.Type == smartcontract.PublicKeyType { // Special array wrapper. return "keys.PublicKeys", "github.com/nspcc-dev/neo-go/pkg/crypto/keys" } sub, pkg := extendedTypeToGo(*et.Value, named) @@ -723,7 +723,7 @@ func extendedTypeToGo(et binding.ExtendedType, named map[string]binding.Extended return "[]any", "" case smartcontract.MapType: - kt, _ := extendedTypeToGo(binding.ExtendedType{Base: et.Key}, named) + kt, _ := extendedTypeToGo(manifest.ExtendedType{Type: et.Key}, named) var vt string if et.Value != nil { vt, _ = extendedTypeToGo(*et.Value, named) @@ -740,8 +740,8 @@ func extendedTypeToGo(et binding.ExtendedType, named map[string]binding.Extended } } -func etTypeConverter(et binding.ExtendedType, v string) string { - switch et.Base { +func etTypeConverter(et manifest.ExtendedType, v string) string { + switch et.Type { case smartcontract.AnyType: return v + ".Value(), error(nil)" case smartcontract.BoolType: @@ -817,10 +817,10 @@ func etTypeConverter(et binding.ExtendedType, v string) string { return res, nil }(` + v + `)` } - return etTypeConverter(binding.ExtendedType{ - Base: smartcontract.ArrayType, - Value: &binding.ExtendedType{ - Base: smartcontract.AnyType, + return etTypeConverter(manifest.ExtendedType{ + Type: smartcontract.ArrayType, + Value: &manifest.ExtendedType{ + Type: smartcontract.AnyType, }, }, v) @@ -834,7 +834,7 @@ func etTypeConverter(et binding.ExtendedType, v string) string { } res := make(` + at + `) for i := range m { - k, err := ` + addIndent(etTypeConverter(binding.ExtendedType{Base: et.Key}, "m[i].Key"), "\t\t") + ` + k, err := ` + addIndent(etTypeConverter(manifest.ExtendedType{Type: et.Key}, "m[i].Key"), "\t\t") + ` if err != nil { return nil, fmt.Errorf("key %d: %w", i, err) } @@ -847,11 +847,11 @@ func etTypeConverter(et binding.ExtendedType, v string) string { return res, nil }(` + v + `)` } - return etTypeConverter(binding.ExtendedType{ - Base: smartcontract.MapType, + return etTypeConverter(manifest.ExtendedType{ + Type: smartcontract.MapType, Key: et.Key, - Value: &binding.ExtendedType{ - Base: smartcontract.AnyType, + Value: &manifest.ExtendedType{ + Type: smartcontract.AnyType, }, }, v) case smartcontract.InteropInterfaceType: @@ -863,8 +863,8 @@ func etTypeConverter(et binding.ExtendedType, v string) string { } } -func goTypeConverter(et binding.ExtendedType, v string) string { - switch et.Base { +func goTypeConverter(et manifest.ExtendedType, v string) string { + switch et.Type { case smartcontract.AnyType: return "stackitem.TryMake(" + v + ")" case smartcontract.BoolType: @@ -902,10 +902,10 @@ func goTypeConverter(et binding.ExtendedType, v string) string { return stackitem.NewArray(items), nil }(` + v + `)` } - return goTypeConverter(binding.ExtendedType{ - Base: smartcontract.ArrayType, - Value: &binding.ExtendedType{ - Base: smartcontract.AnyType, + return goTypeConverter(manifest.ExtendedType{ + Type: smartcontract.ArrayType, + Value: &manifest.ExtendedType{ + Type: smartcontract.AnyType, }, }, v) @@ -919,7 +919,7 @@ func goTypeConverter(et binding.ExtendedType, v string) string { var m = stackitem.NewMap() for k, v := range in { - iKey, err := ` + goTypeConverter(binding.ExtendedType{Base: et.Key}, "k") + ` + iKey, err := ` + goTypeConverter(manifest.ExtendedType{Type: et.Key}, "k") + ` if err != nil { return nil, fmt.Errorf("key %v: %w", k, err) } @@ -933,11 +933,11 @@ func goTypeConverter(et binding.ExtendedType, v string) string { }(` + v + `)` } - return goTypeConverter(binding.ExtendedType{ - Base: smartcontract.MapType, + return goTypeConverter(manifest.ExtendedType{ + Type: smartcontract.MapType, Key: et.Key, - Value: &binding.ExtendedType{ - Base: smartcontract.AnyType, + Value: &manifest.ExtendedType{ + Type: smartcontract.AnyType, }, }, v) case smartcontract.InteropInterfaceType: @@ -949,8 +949,8 @@ func goTypeConverter(et binding.ExtendedType, v string) string { } } -func scTypeConverter(et binding.ExtendedType, v string) string { - switch et.Base { +func scTypeConverter(et manifest.ExtendedType, v string) string { + switch et.Type { case smartcontract.AnyType, smartcontract.BoolType, smartcontract.IntegerType, smartcontract.ByteArrayType, smartcontract.SignatureType, smartcontract.StringType, smartcontract.Hash160Type, smartcontract.Hash256Type, smartcontract.PublicKeyType, @@ -977,10 +977,10 @@ func scTypeConverter(et binding.ExtendedType, v string) string { return smartcontract.Parameter{Type: smartcontract.ArrayType, Value: prms}, nil }(` + v + `)` } - return scTypeConverter(binding.ExtendedType{ - Base: smartcontract.ArrayType, - Value: &binding.ExtendedType{ - Base: smartcontract.AnyType, + return scTypeConverter(manifest.ExtendedType{ + Type: smartcontract.ArrayType, + Value: &manifest.ExtendedType{ + Type: smartcontract.AnyType, }, }, v) @@ -994,7 +994,7 @@ func scTypeConverter(et binding.ExtendedType, v string) string { var prms = make([]smartcontract.ParameterPair, 0, len(in)) for k, v := range in { - iKey, err := ` + scTypeConverter(binding.ExtendedType{Base: et.Key}, "k") + ` + iKey, err := ` + scTypeConverter(manifest.ExtendedType{Type: et.Key}, "k") + ` if err != nil { return smartcontract.Parameter{}, fmt.Errorf("key %v: %w", k, err) } @@ -1008,11 +1008,11 @@ func scTypeConverter(et binding.ExtendedType, v string) string { }(` + v + `)` } - return goTypeConverter(binding.ExtendedType{ - Base: smartcontract.MapType, + return goTypeConverter(manifest.ExtendedType{ + Type: smartcontract.MapType, Key: et.Key, - Value: &binding.ExtendedType{ - Base: smartcontract.AnyType, + Value: &manifest.ExtendedType{ + Type: smartcontract.AnyType, }, }, v) default: @@ -1023,9 +1023,9 @@ func scTypeConverter(et binding.ExtendedType, v string) string { func scTypeToGo(name string, typ smartcontract.ParamType, cfg *binding.Config) (string, string) { et, ok := cfg.Types[name] if !ok { - et = binding.ExtendedType{Base: typ} + et = manifest.ExtendedType{Type: typ} } - return extendedTypeToGo(et, cfg.NamedTypes) + return extendedTypeToGo(et, cfg.Manifest.ABI.NamedTypes) } func scTemplateToRPC(cfg binding.Config, ctr ContractTmpl, imports map[string]struct{}, scTypeConverter func(string, smartcontract.ParamType, *binding.Config) (string, string)) ContractTmpl { @@ -1055,10 +1055,10 @@ func scTemplateToRPC(cfg binding.Config, ctr ContractTmpl, imports map[string]st } } } - for _, et := range cfg.NamedTypes { - addETImports(et, cfg.NamedTypes, imports) + for _, et := range cfg.Manifest.ABI.NamedTypes { + addETImports(et, cfg.Manifest.ABI.NamedTypes, imports) } - if len(cfg.NamedTypes) > 0 { + if len(cfg.Manifest.ABI.NamedTypes) > 0 { imports["errors"] = struct{}{} imports["github.com/nspcc-dev/neo-go/pkg/smartcontract"] = struct{}{} } @@ -1077,14 +1077,14 @@ func scTemplateToRPC(cfg binding.Config, ctr ContractTmpl, imports map[string]st } var ( - extType binding.ExtendedType + extType manifest.ExtendedType ok bool ) if extType, ok = cfg.Types[fullPName]; !ok { - extType = binding.ExtendedType{ - Base: abiEvent.Parameters[i].Type, + extType = manifest.ExtendedType{ + Type: abiEvent.Parameters[i].Type, } - addETImports(extType, cfg.NamedTypes, imports) + addETImports(extType, cfg.Manifest.ABI.NamedTypes, imports) } eTmp.Parameters = append(eTmp.Parameters, EventParamTmpl{ ParamTmpl: binding.ParamTmpl{ @@ -1155,7 +1155,7 @@ func scTemplateToRPC(cfg binding.Config, ctr ContractTmpl, imports map[string]st case "keys.PublicKeys": ctr.SafeMethods[i].Unwrapper = "ArrayOfPublicKeys" default: - addETImports(ctr.SafeMethods[i].ExtendedReturn, cfg.NamedTypes, imports) + addETImports(ctr.SafeMethods[i].ExtendedReturn, cfg.Manifest.ABI.NamedTypes, imports) ctr.SafeMethods[i].Unwrapper = "Item" } } @@ -1184,13 +1184,13 @@ func scTemplateToRPC(cfg binding.Config, ctr ContractTmpl, imports map[string]st return ctr } -func addETImports(et binding.ExtendedType, named map[string]binding.ExtendedType, imports map[string]struct{}) { +func addETImports(et manifest.ExtendedType, named map[string]manifest.ExtendedType, imports map[string]struct{}) { _, pkg := extendedTypeToGo(et, named) if pkg != "" { imports[pkg] = struct{}{} } // Additional packages used during decoding. - switch et.Base { + switch et.Type { case smartcontract.StringType: imports["unicode/utf8"] = struct{}{} imports["errors"] = struct{}{} @@ -1206,11 +1206,13 @@ func addETImports(et binding.ExtendedType, named map[string]binding.ExtendedType if et.Value != nil { addETImports(*et.Value, named, imports) } - if et.Base == smartcontract.MapType { - addETImports(binding.ExtendedType{Base: et.Key}, named, imports) + if et.Type == smartcontract.MapType { + addETImports(manifest.ExtendedType{Type: et.Key}, named, imports) } for i := range et.Fields { - addETImports(et.Fields[i].ExtendedType, named, imports) + if et.Fields[i].ExtendedType != nil { + addETImports(*et.Fields[i].ExtendedType, named, imports) + } } }