diff --git a/cli/smartcontract/testdata/notifications/config.yml b/cli/smartcontract/testdata/notifications/config.yml index 5bb9b8554..5fd018d53 100644 --- a/cli/smartcontract/testdata/notifications/config.yml +++ b/cli/smartcontract/testdata/notifications/config.yml @@ -5,3 +5,11 @@ events: parameters: - name: ! complicated param @#$% type: String + - name: "SomeMap" + parameters: + - name: m + type: Map + - name: "SomeStruct" + parameters: + - name: s + type: Struct \ No newline at end of file diff --git a/cli/smartcontract/testdata/notifications/notifications.go b/cli/smartcontract/testdata/notifications/notifications.go index 369d1cdb6..f679901af 100644 --- a/cli/smartcontract/testdata/notifications/notifications.go +++ b/cli/smartcontract/testdata/notifications/notifications.go @@ -1,7 +1,21 @@ package structs -import "github.com/nspcc-dev/neo-go/pkg/interop/runtime" +import ( + "github.com/nspcc-dev/neo-go/pkg/interop" + "github.com/nspcc-dev/neo-go/pkg/interop/runtime" +) func Main() { runtime.Notify("! complicated name %$#", "str1") } + +func CrazyMap() { + runtime.Notify("SomeMap", map[int][]map[string][]interop.Hash160{}) +} + +func Struct() { + runtime.Notify("SomeStruct", struct { + I int + B bool + }{I: 123, B: true}) +} diff --git a/cli/smartcontract/testdata/notifications/rpcbindings.out b/cli/smartcontract/testdata/notifications/rpcbindings.out index a3717edd8..ae0724975 100644 --- a/cli/smartcontract/testdata/notifications/rpcbindings.out +++ b/cli/smartcontract/testdata/notifications/rpcbindings.out @@ -82,6 +82,16 @@ type ComplicatedNameEvent struct { ComplicatedParam string } +// SomeMapEvent represents "SomeMap" event emitted by the contract. +type SomeMapEvent struct { + M map[any]any +} + +// SomeStructEvent represents "SomeStruct" event emitted by the contract. +type SomeStructEvent struct { + S []any +} + // Actor is used by Contract to call state-changing methods. type Actor interface { MakeCall(contract util.Uint160, method string, params ...any) (*transaction.Transaction, error) @@ -102,6 +112,28 @@ func New(actor Actor) *Contract { return &Contract{actor} } +// CrazyMap creates a transaction invoking `crazyMap` method of the contract. +// This transaction is signed and immediately sent to the network. +// The values returned are its hash, ValidUntilBlock value and error if any. +func (c *Contract) CrazyMap() (util.Uint256, uint32, error) { + return c.actor.SendCall(Hash, "crazyMap") +} + +// CrazyMapTransaction creates a transaction invoking `crazyMap` method of the contract. +// This transaction is signed, but not sent to the network, instead it's +// returned to the caller. +func (c *Contract) CrazyMapTransaction() (*transaction.Transaction, error) { + return c.actor.MakeCall(Hash, "crazyMap") +} + +// CrazyMapUnsigned creates a transaction invoking `crazyMap` method of the contract. +// This transaction is not signed, it's simply returned to the caller. +// Any fields of it that do not affect fees can be changed (ValidUntilBlock, +// Nonce), fee values (NetworkFee, SystemFee) can be increased as well. +func (c *Contract) CrazyMapUnsigned() (*transaction.Transaction, error) { + return c.actor.MakeUnsignedCall(Hash, "crazyMap", nil) +} + // Main creates a transaction invoking `main` method of the contract. // This transaction is signed and immediately sent to the network. // The values returned are its hash, ValidUntilBlock value and error if any. @@ -124,6 +156,28 @@ func (c *Contract) MainUnsigned() (*transaction.Transaction, error) { return c.actor.MakeUnsignedCall(Hash, "main", nil) } +// Struct creates a transaction invoking `struct` method of the contract. +// This transaction is signed and immediately sent to the network. +// The values returned are its hash, ValidUntilBlock value and error if any. +func (c *Contract) Struct() (util.Uint256, uint32, error) { + return c.actor.SendCall(Hash, "struct") +} + +// StructTransaction creates a transaction invoking `struct` method of the contract. +// This transaction is signed, but not sent to the network, instead it's +// returned to the caller. +func (c *Contract) StructTransaction() (*transaction.Transaction, error) { + return c.actor.MakeCall(Hash, "struct") +} + +// StructUnsigned creates a transaction invoking `struct` method of the contract. +// This transaction is not signed, it's simply returned to the caller. +// Any fields of it that do not affect fees can be changed (ValidUntilBlock, +// Nonce), fee values (NetworkFee, SystemFee) can be increased as well. +func (c *Contract) StructUnsigned() (*transaction.Transaction, error) { + return c.actor.MakeUnsignedCall(Hash, "struct", nil) +} + // itemToLedgerBlock converts stack item into *LedgerBlock. func itemToLedgerBlock(item stackitem.Item, err error) (*LedgerBlock, error) { if err != nil { @@ -747,3 +801,138 @@ func (e *ComplicatedNameEvent) FromStackItem(item *stackitem.Array) error { return nil } + +// SomeMapEventsFromApplicationLog retrieves a set of all emitted events +// with "SomeMap" name from the provided ApplicationLog. +func SomeMapEventsFromApplicationLog(log *result.ApplicationLog) ([]*SomeMapEvent, error) { + if log == nil { + return nil, errors.New("nil application log") + } + + var res []*SomeMapEvent + for i, ex := range log.Executions { + for j, e := range ex.Events { + if e.Name != "SomeMap" { + continue + } + event := new(SomeMapEvent) + err := event.FromStackItem(e.Item) + if err != nil { + return nil, fmt.Errorf("failed to deserialize SomeMapEvent from stackitem (execution %d, event %d): %w", i, j, err) + } + res = append(res, event) + } + } + + return res, nil +} + +// FromStackItem converts provided stackitem.Array to SomeMapEvent and +// returns an error if so. +func (e *SomeMapEvent) FromStackItem(item *stackitem.Array) error { + if item == nil { + return errors.New("nil item") + } + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return errors.New("not an array") + } + if len(arr) != 1 { + return errors.New("wrong number of structure elements") + } + + var ( + index = -1 + err error + ) + index++ + e.M, err = func (item stackitem.Item) (map[any]any, error) { + m, ok := item.Value().([]stackitem.MapElement) + if !ok { + return nil, fmt.Errorf("%s is not a map", item.Type().String()) + } + res := make(map[any]any) + for i := range m { + k, err := m[i].Key.Value(), error(nil) + if err != nil { + return nil, fmt.Errorf("key %d: %w", i, err) + } + v, err := m[i].Value.Value(), error(nil) + if err != nil { + return nil, fmt.Errorf("value %d: %w", i, err) + } + res[k] = v + } + return res, nil + } (arr[index]) + if err != nil { + return fmt.Errorf("field M: %w", err) + } + + return nil +} + +// SomeStructEventsFromApplicationLog retrieves a set of all emitted events +// with "SomeStruct" name from the provided ApplicationLog. +func SomeStructEventsFromApplicationLog(log *result.ApplicationLog) ([]*SomeStructEvent, error) { + if log == nil { + return nil, errors.New("nil application log") + } + + var res []*SomeStructEvent + for i, ex := range log.Executions { + for j, e := range ex.Events { + if e.Name != "SomeStruct" { + continue + } + event := new(SomeStructEvent) + err := event.FromStackItem(e.Item) + if err != nil { + return nil, fmt.Errorf("failed to deserialize SomeStructEvent from stackitem (execution %d, event %d): %w", i, j, err) + } + res = append(res, event) + } + } + + return res, nil +} + +// FromStackItem converts provided stackitem.Array to SomeStructEvent and +// returns an error if so. +func (e *SomeStructEvent) FromStackItem(item *stackitem.Array) error { + if item == nil { + return errors.New("nil item") + } + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return errors.New("not an array") + } + if len(arr) != 1 { + return errors.New("wrong number of structure elements") + } + + var ( + index = -1 + err error + ) + index++ + e.S, err = func (item stackitem.Item) ([]any, error) { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return nil, errors.New("not an array") + } + res := make([]any, len(arr)) + for i := range res { + res[i], err = arr[i].Value(), error(nil) + if err != nil { + return nil, fmt.Errorf("item %d: %w", i, err) + } + } + return res, nil + } (arr[index]) + if err != nil { + return fmt.Errorf("field S: %w", err) + } + + return nil +} diff --git a/cli/smartcontract/testdata/types/config.yml b/cli/smartcontract/testdata/types/config.yml index b97936c40..98cb40d18 100644 --- a/cli/smartcontract/testdata/types/config.yml +++ b/cli/smartcontract/testdata/types/config.yml @@ -1,3 +1,3 @@ name: "Types" sourceurl: https://github.com/nspcc-dev/neo-go/ -safemethods: ["bool", "int", "bytes", "string", "any", "hash160", "hash256", "publicKey", "signature", "bools", "ints", "bytess", "strings", "hash160s", "hash256s", "publicKeys", "signatures", "aAAStrings", "maps", "crazyMaps"] +safemethods: ["bool", "int", "bytes", "string", "any", "hash160", "hash256", "publicKey", "signature", "bools", "ints", "bytess", "strings", "hash160s", "hash256s", "publicKeys", "signatures", "aAAStrings", "maps", "crazyMaps", "anyMaps"] diff --git a/cli/smartcontract/testdata/types/rpcbindings.out b/cli/smartcontract/testdata/types/rpcbindings.out index ca4f0ce36..49f6ac7e5 100644 --- a/cli/smartcontract/testdata/types/rpcbindings.out +++ b/cli/smartcontract/testdata/types/rpcbindings.out @@ -99,6 +99,34 @@ func (c *ContractReader) Any(a any) (any, error) { } (unwrap.Item(c.invoker.Call(Hash, "any", a))) } +// AnyMaps invokes `anyMaps` method of contract. +func (c *ContractReader) AnyMaps(m map[*big.Int]any) (map[*big.Int]any, error) { + return func (item stackitem.Item, err error) (map[*big.Int]any, error) { + if err != nil { + return nil, err + } + return func (item stackitem.Item) (map[*big.Int]any, error) { + m, ok := item.Value().([]stackitem.MapElement) + if !ok { + return nil, fmt.Errorf("%s is not a map", item.Type().String()) + } + res := make(map[*big.Int]any) + for i := range m { + k, err := m[i].Key.TryInteger() + if err != nil { + return nil, fmt.Errorf("key %d: %w", i, err) + } + v, err := m[i].Value.Value(), error(nil) + if err != nil { + return nil, fmt.Errorf("value %d: %w", i, err) + } + res[k] = v + } + return res, nil + } (item) + } (unwrap.Item(c.invoker.Call(Hash, "anyMaps", m))) +} + // Bool invokes `bool` method of contract. func (c *ContractReader) Bool(b bool) (bool, error) { return unwrap.Bool(c.invoker.Call(Hash, "bool", b)) diff --git a/cli/smartcontract/testdata/types/types.go b/cli/smartcontract/testdata/types/types.go index c8d819dd5..fcf91a0be 100644 --- a/cli/smartcontract/testdata/types/types.go +++ b/cli/smartcontract/testdata/types/types.go @@ -83,3 +83,7 @@ func Maps(m map[string]string) map[string]string { func CrazyMaps(m map[int][]map[string][]interop.Hash160) map[int][]map[string][]interop.Hash160 { return m } + +func AnyMaps(m map[int]any) map[int]any { + return m +} diff --git a/examples/events/events.go b/examples/events/events.go index 28254ee4a..cd45ff15f 100644 --- a/examples/events/events.go +++ b/examples/events/events.go @@ -1,6 +1,7 @@ package events import ( + "github.com/nspcc-dev/neo-go/pkg/interop" "github.com/nspcc-dev/neo-go/pkg/interop/runtime" ) @@ -24,6 +25,11 @@ func NotifySomeMap(arg map[string]int) { runtime.Notify("SomeMap", arg) } +// NotifySomeCrazyMap emits notification with complicated Map. +func NotifySomeCrazyMap(arg map[int][]map[string][]interop.Hash160) { + runtime.Notify("SomeCrazyMap", arg) +} + // NotifySomeArray emits notification with Array. func NotifySomeArray(arg []int) { runtime.Notify("SomeArray", arg) diff --git a/examples/events/events.yml b/examples/events/events.yml index f16bc347e..7b3c05a40 100644 --- a/examples/events/events.yml +++ b/examples/events/events.yml @@ -18,6 +18,10 @@ events: parameters: - name: m type: Map + - name: SomeCrazyMap + parameters: + - name: m + type: Map - name: SomeArray parameters: - name: a diff --git a/pkg/smartcontract/rpcbinding/binding.go b/pkg/smartcontract/rpcbinding/binding.go index a3eab9576..eb38be084 100644 --- a/pkg/smartcontract/rpcbinding/binding.go +++ b/pkg/smartcontract/rpcbinding/binding.go @@ -511,7 +511,12 @@ func extendedTypeToGo(et binding.ExtendedType, named map[string]binding.Extended case smartcontract.MapType: kt, _ := extendedTypeToGo(binding.ExtendedType{Base: et.Key}, named) - vt, _ := extendedTypeToGo(*et.Value, named) + var vt string + if et.Value != nil { + vt, _ = extendedTypeToGo(*et.Value, named) + } else { + vt = "any" + } return "map[" + kt + "]" + vt, "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" case smartcontract.InteropInterfaceType: return "any", "" @@ -606,8 +611,9 @@ func etTypeConverter(et binding.ExtendedType, v string) string { }, v) case smartcontract.MapType: - at, _ := extendedTypeToGo(et, nil) - return `func (item stackitem.Item) (` + at + `, error) { + if et.Value != nil { + at, _ := extendedTypeToGo(et, nil) + return `func (item stackitem.Item) (` + at + `, error) { m, ok := item.Value().([]stackitem.MapElement) if !ok { return nil, fmt.Errorf("%s is not a map", item.Type().String()) @@ -626,6 +632,14 @@ func etTypeConverter(et binding.ExtendedType, v string) string { } return res, nil } (` + v + `)` + } + return etTypeConverter(binding.ExtendedType{ + Base: smartcontract.MapType, + Key: et.Key, + Value: &binding.ExtendedType{ + Base: smartcontract.AnyType, + }, + }, v) case smartcontract.InteropInterfaceType: return "item.Value(), nil" case smartcontract.VoidType: