diff --git a/.gitignore b/.gitignore index fbc04a091..3262aa4e4 100644 --- a/.gitignore +++ b/.gitignore @@ -7,9 +7,6 @@ # Test binary, build with `go test -c` *.test -# Output of the go coverage tool, specifically when used with LiteIDE -*.out - # Added by CoZ developers vendor/ bin/ @@ -54,6 +51,7 @@ testdata/ !pkg/services/notary/testdata !pkg/services/oracle/testdata !pkg/smartcontract/testdata +!cli/smartcontract/testdata pkg/vm/testdata/fuzz !pkg/vm/testdata !pkg/wallet/testdata diff --git a/cli/smartcontract/generate_test.go b/cli/smartcontract/generate_test.go index ae0ef741f..6c6a63f90 100644 --- a/cli/smartcontract/generate_test.go +++ b/cli/smartcontract/generate_test.go @@ -316,7 +316,6 @@ func NewReader(invoker Invoker) *ContractReader { return &ContractReader{invoker} } - // Get invokes `+"`get`"+` method of contract. func (c *ContractReader) Get() (*big.Int, error) { return unwrap.BigInt(c.invoker.Call(Hash, "get")) @@ -324,6 +323,10 @@ func (c *ContractReader) Get() (*big.Int, error) { `, string(data)) } +// rewriteExpectedOutputs denotes whether expected output files should be rewritten +// for TestGenerateRPCBindings and TestAssistedRPCBindings. +const rewriteExpectedOutputs = false + func TestGenerateRPCBindings(t *testing.T) { tmpDir := t.TempDir() app := cli.NewApp() @@ -341,10 +344,14 @@ func TestGenerateRPCBindings(t *testing.T) { data, err := os.ReadFile(outFile) require.NoError(t, err) data = bytes.ReplaceAll(data, []byte("\r"), []byte{}) // Windows. - expected, err := os.ReadFile(good) - require.NoError(t, err) - expected = bytes.ReplaceAll(expected, []byte("\r"), []byte{}) // Windows. - require.Equal(t, string(expected), string(data)) + if rewriteExpectedOutputs { + require.NoError(t, os.WriteFile(good, data, os.ModePerm)) + } else { + expected, err := os.ReadFile(good) + require.NoError(t, err) + expected = bytes.ReplaceAll(expected, []byte("\r"), []byte{}) // Windows. + require.Equal(t, string(expected), string(data)) + } }) } @@ -363,6 +370,8 @@ func TestGenerateRPCBindings(t *testing.T) { checkBinding(filepath.Join("testdata", "nonepiter", "iter.manifest.json"), "0x00112233445566778899aabbccddeeff00112233", filepath.Join("testdata", "nonepiter", "iter.go")) + + require.False(t, rewriteExpectedOutputs) } func TestAssistedRPCBindings(t *testing.T) { @@ -370,18 +379,32 @@ func TestAssistedRPCBindings(t *testing.T) { app := cli.NewApp() app.Commands = NewCommands() - var checkBinding = func(source string) { - t.Run(source, func(t *testing.T) { + var checkBinding = func(source string, guessEventTypes bool, suffix ...string) { + testName := source + if len(suffix) != 0 { + testName += suffix[0] + } + t.Run(testName, func(t *testing.T) { + configFile := filepath.Join(source, "config.yml") + expectedFile := filepath.Join(source, "rpcbindings.out") + if len(suffix) != 0 { + configFile = filepath.Join(source, "config"+suffix[0]+".yml") + expectedFile = filepath.Join(source, "rpcbindings"+suffix[0]+".out") + } manifestF := filepath.Join(tmpDir, "manifest.json") bindingF := filepath.Join(tmpDir, "binding.yml") nefF := filepath.Join(tmpDir, "out.nef") - require.NoError(t, app.Run([]string{"", "contract", "compile", + cmd := []string{"", "contract", "compile", "--in", source, - "--config", filepath.Join(source, "config.yml"), + "--config", configFile, "--manifest", manifestF, "--bindings", bindingF, "--out", nefF, - })) + } + if guessEventTypes { + cmd = append(cmd, "--guess-eventtypes") + } + require.NoError(t, app.Run(cmd)) outFile := filepath.Join(tmpDir, "out.go") require.NoError(t, app.Run([]string{"", "contract", "generate-rpcwrapper", "--config", bindingF, @@ -393,15 +416,24 @@ func TestAssistedRPCBindings(t *testing.T) { data, err := os.ReadFile(outFile) require.NoError(t, err) data = bytes.ReplaceAll(data, []byte("\r"), []byte{}) // Windows. - expected, err := os.ReadFile(filepath.Join(source, "rpcbindings.out")) - require.NoError(t, err) - expected = bytes.ReplaceAll(expected, []byte("\r"), []byte{}) // Windows. - require.Equal(t, string(expected), string(data)) + if rewriteExpectedOutputs { + require.NoError(t, os.WriteFile(expectedFile, data, os.ModePerm)) + } else { + expected, err := os.ReadFile(expectedFile) + require.NoError(t, err) + expected = bytes.ReplaceAll(expected, []byte("\r"), []byte{}) // Windows. + require.Equal(t, string(expected), string(data)) + } }) } - checkBinding(filepath.Join("testdata", "types")) - checkBinding(filepath.Join("testdata", "structs")) + checkBinding(filepath.Join("testdata", "types"), false) + checkBinding(filepath.Join("testdata", "structs"), false) + checkBinding(filepath.Join("testdata", "notifications"), false) + checkBinding(filepath.Join("testdata", "notifications"), false, "_extended") + checkBinding(filepath.Join("testdata", "notifications"), true, "_guessed") + + require.False(t, rewriteExpectedOutputs) } func TestGenerate_Errors(t *testing.T) { @@ -467,3 +499,55 @@ callflags: "--config", cfgPath, "--out", "zzz") }) } + +func TestCompile_GuessEventTypes(t *testing.T) { + app := cli.NewApp() + app.Commands = NewCommands() + app.ExitErrHandler = func(*cli.Context, error) {} + + checkError := func(t *testing.T, msg string, args ...string) { + // cli.ExitError doesn't implement wraping properly, so we check for an error message. + err := app.Run(args) + require.Error(t, err) + require.True(t, strings.Contains(err.Error(), msg), "got: %v", err) + } + check := func(t *testing.T, source string, expectedErrText string) { + tmpDir := t.TempDir() + configFile := filepath.Join(source, "invalid.yml") + manifestF := filepath.Join(tmpDir, "invalid.manifest.json") + bindingF := filepath.Join(tmpDir, "invalid.binding.yml") + nefF := filepath.Join(tmpDir, "invalid.out.nef") + cmd := []string{"", "contract", "compile", + "--in", source, + "--config", configFile, + "--manifest", manifestF, + "--bindings", bindingF, + "--out", nefF, + "--guess-eventtypes", + } + checkError(t, expectedErrText, cmd...) + } + + t.Run("not declared in manifest", func(t *testing.T) { + check(t, filepath.Join("testdata", "invalid5"), "inconsistent usages of event `Non declared event`: not declared in the contract config") + }) + t.Run("invalid number of params", func(t *testing.T) { + check(t, filepath.Join("testdata", "invalid6"), "inconsistent usages of event `SomeEvent` against config: number of params mismatch: 2 vs 1") + }) + /* + // TODO: this on is a controversial one. If event information is provided in the config file, then conversion code + // will be emitted by the compiler according to the parameter type provided via config. Thus, we can be sure that + // either event parameter has the type specified in the config file or the execution of the contract will fail. + // Thus, this testcase is always failing (no compilation error occures). + // Question: do we want to compare `RealType` of the emitted parameter with the one expected in the manifest? + t.Run("SC parameter type mismatch", func(t *testing.T) { + check(t, filepath.Join("testdata", "invalid7"), "inconsistent usages of event `SomeEvent` against config: number of params mismatch: 2 vs 1") + }) + */ + t.Run("extended types mismatch", func(t *testing.T) { + check(t, filepath.Join("testdata", "invalid8"), "inconsistent usages of event `SomeEvent`: extended type of param #0 mismatch") + }) + t.Run("named types redeclare", func(t *testing.T) { + check(t, filepath.Join("testdata", "invalid9"), "configured declared named type intersects with the contract's one: `invalid9.NamedStruct`") + }) +} diff --git a/cli/smartcontract/smart_contract.go b/cli/smartcontract/smart_contract.go index 5a2af4685..38dea5f13 100644 --- a/cli/smartcontract/smart_contract.go +++ b/cli/smartcontract/smart_contract.go @@ -24,6 +24,7 @@ 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" @@ -125,7 +126,7 @@ func NewCommands() []cli.Command { { Name: "compile", Usage: "compile a smart contract to a .nef file", - UsageText: "neo-go contract compile -i path [-o nef] [-v] [-d] [-m manifest] [-c yaml] [--bindings file] [--no-standards] [--no-events] [--no-permissions]", + UsageText: "neo-go contract compile -i path [-o nef] [-v] [-d] [-m manifest] [-c yaml] [--bindings file] [--no-standards] [--no-events] [--no-permissions] [--guess-eventtypes]", Description: `Compiles given smart contract to a .nef file and emits other associated information (manifest, bindings configuration, debug information files) if asked to. If none of --out, --manifest, --config, --bindings flags are specified, @@ -171,6 +172,10 @@ func NewCommands() []cli.Command { Name: "no-permissions", Usage: "do not check if invoked contracts are allowed in manifest", }, + cli.BoolFlag{ + Name: "guess-eventtypes", + Usage: "guess event types for smart-contract bindings configuration from the code usages", + }, cli.StringFlag{ Name: "bindings", Usage: "output file for smart-contract bindings configuration", @@ -352,13 +357,15 @@ func initSmartContract(ctx *cli.Context) error { SourceURL: "http://example.com/", SupportedStandards: []string{}, SafeMethods: []string{}, - Events: []manifest.Event{ + Events: []compiler.HybridEvent{ { Name: "Hello world!", - Parameters: []manifest.Parameter{ + Parameters: []compiler.HybridParameter{ { - Name: "args", - Type: smartcontract.ArrayType, + Parameter: manifest.Parameter{ + Name: "args", + Type: smartcontract.ArrayType, + }, }, }, }, @@ -447,6 +454,8 @@ func contractCompile(ctx *cli.Context) error { NoStandardCheck: ctx.Bool("no-standards"), NoEventsCheck: ctx.Bool("no-events"), NoPermissionsCheck: ctx.Bool("no-permissions"), + + GuessEventTypes: ctx.Bool("guess-eventtypes"), } if len(confFile) != 0 { @@ -457,6 +466,7 @@ 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 { @@ -705,9 +715,10 @@ type ProjectConfig struct { SourceURL string SafeMethods []string SupportedStandards []string - Events []manifest.Event + Events []compiler.HybridEvent Permissions []permission - Overloads map[string]string `yaml:"overloads,omitempty"` + Overloads map[string]string `yaml:"overloads,omitempty"` + NamedTypes map[string]binding.ExtendedType `yaml:"namedtypes,omitempty"` } func inspect(ctx *cli.Context) error { diff --git a/cli/smartcontract/testdata/gas/gas.go b/cli/smartcontract/testdata/gas/gas.go index d3c938c29..d12bbb8ee 100644 --- a/cli/smartcontract/testdata/gas/gas.go +++ b/cli/smartcontract/testdata/gas/gas.go @@ -44,4 +44,3 @@ func New(actor Actor) *Contract { var nep17t = nep17.New(actor, Hash) return &Contract{ContractReader{nep17t.TokenReader, actor}, nep17t.TokenWriter, actor} } - diff --git a/cli/smartcontract/testdata/invalid5/invalid.go b/cli/smartcontract/testdata/invalid5/invalid.go new file mode 100644 index 000000000..0cae2ed71 --- /dev/null +++ b/cli/smartcontract/testdata/invalid5/invalid.go @@ -0,0 +1,7 @@ +package invalid5 + +import "github.com/nspcc-dev/neo-go/pkg/interop/runtime" + +func Main() { + runtime.Notify("Non declared event") +} diff --git a/cli/smartcontract/testdata/invalid5/invalid.yml b/cli/smartcontract/testdata/invalid5/invalid.yml new file mode 100644 index 000000000..eda948267 --- /dev/null +++ b/cli/smartcontract/testdata/invalid5/invalid.yml @@ -0,0 +1 @@ +name: Test undeclared event \ No newline at end of file diff --git a/cli/smartcontract/testdata/invalid6/invalid.go b/cli/smartcontract/testdata/invalid6/invalid.go new file mode 100644 index 000000000..dd3a3ecdd --- /dev/null +++ b/cli/smartcontract/testdata/invalid6/invalid.go @@ -0,0 +1,7 @@ +package invalid6 + +import "github.com/nspcc-dev/neo-go/pkg/interop/runtime" + +func Main() { + runtime.Notify("SomeEvent", "p1", "p2") +} diff --git a/cli/smartcontract/testdata/invalid6/invalid.yml b/cli/smartcontract/testdata/invalid6/invalid.yml new file mode 100644 index 000000000..13933803e --- /dev/null +++ b/cli/smartcontract/testdata/invalid6/invalid.yml @@ -0,0 +1,6 @@ +name: Test undeclared event +events: + - name: SomeEvent + parameters: + - name: p1 + type: String \ No newline at end of file diff --git a/cli/smartcontract/testdata/invalid7/invalid.go b/cli/smartcontract/testdata/invalid7/invalid.go new file mode 100644 index 000000000..41bc20c47 --- /dev/null +++ b/cli/smartcontract/testdata/invalid7/invalid.go @@ -0,0 +1,7 @@ +package invalid7 + +import "github.com/nspcc-dev/neo-go/pkg/interop/runtime" + +func Main() { + runtime.Notify("SomeEvent", "p1", 5) +} diff --git a/cli/smartcontract/testdata/invalid7/invalid.yml b/cli/smartcontract/testdata/invalid7/invalid.yml new file mode 100644 index 000000000..a217deefd --- /dev/null +++ b/cli/smartcontract/testdata/invalid7/invalid.yml @@ -0,0 +1,8 @@ +name: Test undeclared event +events: + - name: SomeEvent + parameters: + - name: p1 + type: String + - name: p2 + type: String \ No newline at end of file diff --git a/cli/smartcontract/testdata/invalid8/invalid.go b/cli/smartcontract/testdata/invalid8/invalid.go new file mode 100644 index 000000000..dba9173ca --- /dev/null +++ b/cli/smartcontract/testdata/invalid8/invalid.go @@ -0,0 +1,17 @@ +package invalid8 + +import "github.com/nspcc-dev/neo-go/pkg/interop/runtime" + +type SomeStruct1 struct { + Field1 int +} + +type SomeStruct2 struct { + Field2 string +} + +func Main() { + // Inconsistent event params usages (different named types throughout the usages). + runtime.Notify("SomeEvent", SomeStruct1{Field1: 123}) + runtime.Notify("SomeEvent", SomeStruct2{Field2: "str"}) +} diff --git a/cli/smartcontract/testdata/invalid8/invalid.yml b/cli/smartcontract/testdata/invalid8/invalid.yml new file mode 100644 index 000000000..6c0c34748 --- /dev/null +++ b/cli/smartcontract/testdata/invalid8/invalid.yml @@ -0,0 +1,6 @@ +name: Test undeclared event +events: + - name: SomeEvent + parameters: + - name: p1 + type: Array \ No newline at end of file diff --git a/cli/smartcontract/testdata/invalid9/invalid.go b/cli/smartcontract/testdata/invalid9/invalid.go new file mode 100644 index 000000000..5036bca9f --- /dev/null +++ b/cli/smartcontract/testdata/invalid9/invalid.go @@ -0,0 +1,12 @@ +package invalid9 + +import "github.com/nspcc-dev/neo-go/pkg/interop/runtime" + +type NamedStruct struct { + SomeInt int +} + +func Main() NamedStruct { + runtime.Notify("SomeEvent", []interface{}{123}) + return NamedStruct{SomeInt: 123} +} diff --git a/cli/smartcontract/testdata/invalid9/invalid.yml b/cli/smartcontract/testdata/invalid9/invalid.yml new file mode 100644 index 000000000..40bbf66db --- /dev/null +++ b/cli/smartcontract/testdata/invalid9/invalid.yml @@ -0,0 +1,16 @@ +name: Test undeclared event +events: + - name: SomeEvent + parameters: + - name: p1 + type: Array + extendedtype: + base: Array + name: invalid9.NamedStruct +namedtypes: + invalid9.NamedStruct: + base: Array + name: invalid9.NamedStruct + fields: + - field: SomeInt + base: Integer diff --git a/cli/smartcontract/testdata/nameservice/nns.go b/cli/smartcontract/testdata/nameservice/nns.go index efdfcabfe..3636fd333 100644 --- a/cli/smartcontract/testdata/nameservice/nns.go +++ b/cli/smartcontract/testdata/nameservice/nns.go @@ -2,6 +2,8 @@ package nameservice import ( + "errors" + "fmt" "github.com/google/uuid" "github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/neorpc/result" @@ -11,11 +13,26 @@ import ( "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" "math/big" + "unicode/utf8" ) // Hash contains contract hash. var Hash = util.Uint160{0xde, 0x46, 0x5f, 0x5d, 0x50, 0x57, 0xcf, 0x33, 0x28, 0x47, 0x94, 0xc5, 0xcf, 0xc2, 0xc, 0x69, 0x37, 0x1c, 0xac, 0x50} +// SetAdminEvent represents "SetAdmin" event emitted by the contract. +type SetAdminEvent struct { + Name string + OldAdmin util.Uint160 + NewAdmin util.Uint160 +} + +// RenewEvent represents "Renew" event emitted by the contract. +type RenewEvent struct { + Name string + OldExpiration *big.Int + NewExpiration *big.Int +} + // Invoker is used by ContractReader to call various safe methods. type Invoker interface { nep11.Invoker @@ -59,7 +76,6 @@ func New(actor Actor) *Contract { return &Contract{ContractReader{nep11ndt.NonDivisibleReader, actor}, nep11ndt.BaseWriter, actor} } - // Roots invokes `roots` method of contract. func (c *ContractReader) Roots() (uuid.UUID, result.Iterator, error) { return unwrap.SessionIterator(c.invoker.Call(Hash, "roots")) @@ -321,3 +337,169 @@ func (c *Contract) DeleteRecordTransaction(name string, typev *big.Int) (*transa func (c *Contract) DeleteRecordUnsigned(name string, typev *big.Int) (*transaction.Transaction, error) { return c.actor.MakeUnsignedCall(Hash, "deleteRecord", nil, name, typev) } + +// SetAdminEventsFromApplicationLog retrieves a set of all emitted events +// with "SetAdmin" name from the provided [result.ApplicationLog]. +func SetAdminEventsFromApplicationLog(log *result.ApplicationLog) ([]*SetAdminEvent, error) { + if log == nil { + return nil, errors.New("nil application log") + } + + var res []*SetAdminEvent + for i, ex := range log.Executions { + for j, e := range ex.Events { + if e.Name != "SetAdmin" { + continue + } + event := new(SetAdminEvent) + err := event.FromStackItem(e.Item) + if err != nil { + return nil, fmt.Errorf("failed to deserialize SetAdminEvent from stackitem (execution #%d, event #%d): %w", i, j, err) + } + res = append(res, event) + } + } + + return res, nil +} + +// FromStackItem converts provided [stackitem.Array] to SetAdminEvent or +// returns an error if it's not possible to do to so. +func (e *SetAdminEvent) 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) != 3 { + return errors.New("wrong number of structure elements") + } + + var ( + index = -1 + err error + ) + index++ + e.Name, err = func (item stackitem.Item) (string, error) { + b, err := item.TryBytes() + if err != nil { + return "", err + } + if !utf8.Valid(b) { + return "", errors.New("not a UTF-8 string") + } + return string(b), nil + } (arr[index]) + if err != nil { + return fmt.Errorf("field Name: %w", err) + } + + index++ + e.OldAdmin, err = func (item stackitem.Item) (util.Uint160, error) { + b, err := item.TryBytes() + if err != nil { + return util.Uint160{}, err + } + u, err := util.Uint160DecodeBytesBE(b) + if err != nil { + return util.Uint160{}, err + } + return u, nil + } (arr[index]) + if err != nil { + return fmt.Errorf("field OldAdmin: %w", err) + } + + index++ + e.NewAdmin, err = func (item stackitem.Item) (util.Uint160, error) { + b, err := item.TryBytes() + if err != nil { + return util.Uint160{}, err + } + u, err := util.Uint160DecodeBytesBE(b) + if err != nil { + return util.Uint160{}, err + } + return u, nil + } (arr[index]) + if err != nil { + return fmt.Errorf("field NewAdmin: %w", err) + } + + return nil +} + +// RenewEventsFromApplicationLog retrieves a set of all emitted events +// with "Renew" name from the provided [result.ApplicationLog]. +func RenewEventsFromApplicationLog(log *result.ApplicationLog) ([]*RenewEvent, error) { + if log == nil { + return nil, errors.New("nil application log") + } + + var res []*RenewEvent + for i, ex := range log.Executions { + for j, e := range ex.Events { + if e.Name != "Renew" { + continue + } + event := new(RenewEvent) + err := event.FromStackItem(e.Item) + if err != nil { + return nil, fmt.Errorf("failed to deserialize RenewEvent from stackitem (execution #%d, event #%d): %w", i, j, err) + } + res = append(res, event) + } + } + + return res, nil +} + +// FromStackItem converts provided [stackitem.Array] to RenewEvent or +// returns an error if it's not possible to do to so. +func (e *RenewEvent) 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) != 3 { + return errors.New("wrong number of structure elements") + } + + var ( + index = -1 + err error + ) + index++ + e.Name, err = func (item stackitem.Item) (string, error) { + b, err := item.TryBytes() + if err != nil { + return "", err + } + if !utf8.Valid(b) { + return "", errors.New("not a UTF-8 string") + } + return string(b), nil + } (arr[index]) + if err != nil { + return fmt.Errorf("field Name: %w", err) + } + + index++ + e.OldExpiration, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field OldExpiration: %w", err) + } + + index++ + e.NewExpiration, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field NewExpiration: %w", err) + } + + return nil +} diff --git a/cli/smartcontract/testdata/nex/nex.go b/cli/smartcontract/testdata/nex/nex.go index 4a4a46de4..32e7526c8 100644 --- a/cli/smartcontract/testdata/nex/nex.go +++ b/cli/smartcontract/testdata/nex/nex.go @@ -2,17 +2,29 @@ package nextoken import ( + "errors" + "fmt" "github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" + "github.com/nspcc-dev/neo-go/pkg/neorpc/result" "github.com/nspcc-dev/neo-go/pkg/rpcclient/nep17" "github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap" "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" "math/big" ) // Hash contains contract hash. var Hash = util.Uint160{0xa8, 0x1a, 0xa1, 0xf0, 0x4b, 0xf, 0xdc, 0x4a, 0xa2, 0xce, 0xd5, 0xbf, 0xc6, 0x22, 0xcf, 0xe8, 0x9, 0x7f, 0xa6, 0xa2} +// OnMintEvent represents "OnMint" event emitted by the contract. +type OnMintEvent struct { + From util.Uint160 + To util.Uint160 + Amount *big.Int + SwapId *big.Int +} + // Invoker is used by ContractReader to call various safe methods. type Invoker interface { nep17.Invoker @@ -56,7 +68,6 @@ func New(actor Actor) *Contract { return &Contract{ContractReader{nep17t.TokenReader, actor}, nep17t.TokenWriter, actor} } - // Cap invokes `cap` method of contract. func (c *ContractReader) Cap() (*big.Int, error) { return unwrap.BigInt(c.invoker.Call(Hash, "cap")) @@ -230,3 +241,93 @@ func (c *Contract) UpdateCapTransaction(newCap *big.Int) (*transaction.Transacti func (c *Contract) UpdateCapUnsigned(newCap *big.Int) (*transaction.Transaction, error) { return c.actor.MakeUnsignedCall(Hash, "updateCap", nil, newCap) } + +// OnMintEventsFromApplicationLog retrieves a set of all emitted events +// with "OnMint" name from the provided [result.ApplicationLog]. +func OnMintEventsFromApplicationLog(log *result.ApplicationLog) ([]*OnMintEvent, error) { + if log == nil { + return nil, errors.New("nil application log") + } + + var res []*OnMintEvent + for i, ex := range log.Executions { + for j, e := range ex.Events { + if e.Name != "OnMint" { + continue + } + event := new(OnMintEvent) + err := event.FromStackItem(e.Item) + if err != nil { + return nil, fmt.Errorf("failed to deserialize OnMintEvent from stackitem (execution #%d, event #%d): %w", i, j, err) + } + res = append(res, event) + } + } + + return res, nil +} + +// FromStackItem converts provided [stackitem.Array] to OnMintEvent or +// returns an error if it's not possible to do to so. +func (e *OnMintEvent) 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) != 4 { + return errors.New("wrong number of structure elements") + } + + var ( + index = -1 + err error + ) + index++ + e.From, err = func (item stackitem.Item) (util.Uint160, error) { + b, err := item.TryBytes() + if err != nil { + return util.Uint160{}, err + } + u, err := util.Uint160DecodeBytesBE(b) + if err != nil { + return util.Uint160{}, err + } + return u, nil + } (arr[index]) + if err != nil { + return fmt.Errorf("field From: %w", err) + } + + index++ + e.To, err = func (item stackitem.Item) (util.Uint160, error) { + b, err := item.TryBytes() + if err != nil { + return util.Uint160{}, err + } + u, err := util.Uint160DecodeBytesBE(b) + if err != nil { + return util.Uint160{}, err + } + return u, nil + } (arr[index]) + if err != nil { + return fmt.Errorf("field To: %w", err) + } + + index++ + e.Amount, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field Amount: %w", err) + } + + index++ + e.SwapId, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field SwapId: %w", err) + } + + return nil +} diff --git a/cli/smartcontract/testdata/nonepiter/iter.go b/cli/smartcontract/testdata/nonepiter/iter.go index e04f6953c..f298ab8de 100644 --- a/cli/smartcontract/testdata/nonepiter/iter.go +++ b/cli/smartcontract/testdata/nonepiter/iter.go @@ -30,7 +30,6 @@ func NewReader(invoker Invoker) *ContractReader { return &ContractReader{invoker} } - // Tokens invokes `tokens` method of contract. func (c *ContractReader) Tokens() (uuid.UUID, result.Iterator, error) { return unwrap.SessionIterator(c.invoker.Call(Hash, "tokens")) diff --git a/cli/smartcontract/testdata/notifications/config.yml b/cli/smartcontract/testdata/notifications/config.yml new file mode 100644 index 000000000..ef2866318 --- /dev/null +++ b/cli/smartcontract/testdata/notifications/config.yml @@ -0,0 +1,19 @@ +name: "Notifications" +sourceurl: https://github.com/nspcc-dev/neo-go/ +events: + - name: "! complicated name %$#" + parameters: + - name: ! complicated param @#$% + type: String + - name: "SomeMap" + parameters: + - name: m + type: Map + - name: "SomeStruct" + parameters: + - name: s + type: Struct + - name: "SomeArray" + parameters: + - name: a + type: Array \ No newline at end of file diff --git a/cli/smartcontract/testdata/notifications/config_extended.yml b/cli/smartcontract/testdata/notifications/config_extended.yml new file mode 100644 index 000000000..ac8f06d6d --- /dev/null +++ b/cli/smartcontract/testdata/notifications/config_extended.yml @@ -0,0 +1,47 @@ +name: "Notifications" +sourceurl: https://github.com/nspcc-dev/neo-go/ +events: + - name: "! complicated name %$#" + parameters: + - name: ! complicated param @#$% + type: String + - name: "SomeMap" + parameters: + - name: m + type: Map + extendedtype: + base: Map + key: Integer + value: + base: Map + key: String + value: + base: Array + value: + base: Hash160 + - name: "SomeStruct" + parameters: + - name: s + type: Struct + extendedtype: + base: Struct + name: crazyStruct + - name: "SomeArray" + parameters: + - name: a + type: Array + extendedtype: + base: Array + value: + base: Array + value: + base: Integer +namedtypes: + crazyStruct: + base: Struct + name: crazyStruct + fields: + - field: I + base: Integer + - field: B + base: Boolean \ No newline at end of file diff --git a/cli/smartcontract/testdata/notifications/config_guessed.yml b/cli/smartcontract/testdata/notifications/config_guessed.yml new file mode 100644 index 000000000..ef2866318 --- /dev/null +++ b/cli/smartcontract/testdata/notifications/config_guessed.yml @@ -0,0 +1,19 @@ +name: "Notifications" +sourceurl: https://github.com/nspcc-dev/neo-go/ +events: + - name: "! complicated name %$#" + parameters: + - name: ! complicated param @#$% + type: String + - name: "SomeMap" + parameters: + - name: m + type: Map + - name: "SomeStruct" + parameters: + - name: s + type: Struct + - name: "SomeArray" + parameters: + - name: a + type: Array \ No newline at end of file diff --git a/cli/smartcontract/testdata/notifications/notifications.go b/cli/smartcontract/testdata/notifications/notifications.go new file mode 100644 index 000000000..80564a4e4 --- /dev/null +++ b/cli/smartcontract/testdata/notifications/notifications.go @@ -0,0 +1,25 @@ +package structs + +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}) +} + +func Array() { + runtime.Notify("SomeArray", [][]int{}) +} diff --git a/cli/smartcontract/testdata/notifications/rpcbindings.out b/cli/smartcontract/testdata/notifications/rpcbindings.out new file mode 100644 index 000000000..a95cc1ea0 --- /dev/null +++ b/cli/smartcontract/testdata/notifications/rpcbindings.out @@ -0,0 +1,1030 @@ +// Package structs contains RPC wrappers for Notifications contract. +package structs + +import ( + "crypto/elliptic" + "errors" + "fmt" + "github.com/nspcc-dev/neo-go/pkg/core/transaction" + "github.com/nspcc-dev/neo-go/pkg/crypto/keys" + "github.com/nspcc-dev/neo-go/pkg/neorpc/result" + "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" + "math/big" + "unicode/utf8" +) + +// Hash contains contract hash. +var Hash = util.Uint160{0x33, 0x22, 0x11, 0x0, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, 0x0} + +// LedgerBlock is a contract-specific ledger.Block type used by its methods. +type LedgerBlock struct { + Hash util.Uint256 + Version *big.Int + PrevHash util.Uint256 + MerkleRoot util.Uint256 + Timestamp *big.Int + Nonce *big.Int + Index *big.Int + NextConsensus util.Uint160 + TransactionsLength *big.Int +} + +// LedgerBlockSR is a contract-specific ledger.BlockSR type used by its methods. +type LedgerBlockSR struct { + Hash util.Uint256 + Version *big.Int + PrevHash util.Uint256 + MerkleRoot util.Uint256 + Timestamp *big.Int + Nonce *big.Int + Index *big.Int + NextConsensus util.Uint160 + TransactionsLength *big.Int + PrevStateRoot util.Uint256 +} + +// LedgerTransaction is a contract-specific ledger.Transaction type used by its methods. +type LedgerTransaction struct { + Hash util.Uint256 + Version *big.Int + Nonce *big.Int + Sender util.Uint160 + SysFee *big.Int + NetFee *big.Int + ValidUntilBlock *big.Int + Script []byte +} + +// LedgerTransactionSigner is a contract-specific ledger.TransactionSigner type used by its methods. +type LedgerTransactionSigner struct { + Account util.Uint160 + Scopes *big.Int + AllowedContracts []util.Uint160 + AllowedGroups keys.PublicKeys + Rules []*LedgerWitnessRule +} + +// LedgerWitnessCondition is a contract-specific ledger.WitnessCondition type used by its methods. +type LedgerWitnessCondition struct { + Type *big.Int + Value any +} + +// LedgerWitnessRule is a contract-specific ledger.WitnessRule type used by its methods. +type LedgerWitnessRule struct { + Action *big.Int + Condition *LedgerWitnessCondition +} + +// ComplicatedNameEvent represents "! complicated name %$#" event emitted by the contract. +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 +} + +// SomeArrayEvent represents "SomeArray" event emitted by the contract. +type SomeArrayEvent struct { + A []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) + MakeRun(script []byte) (*transaction.Transaction, error) + MakeUnsignedCall(contract util.Uint160, method string, attrs []transaction.Attribute, params ...any) (*transaction.Transaction, error) + MakeUnsignedRun(script []byte, attrs []transaction.Attribute) (*transaction.Transaction, error) + SendCall(contract util.Uint160, method string, params ...any) (util.Uint256, uint32, error) + SendRun(script []byte) (util.Uint256, uint32, error) +} + +// Contract implements all contract methods. +type Contract struct { + actor Actor +} + +// New creates an instance of Contract using Hash and the given Actor. +func New(actor Actor) *Contract { + return &Contract{actor} +} + +// Array creates a transaction invoking `array` 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) Array() (util.Uint256, uint32, error) { + return c.actor.SendCall(Hash, "array") +} + +// ArrayTransaction creates a transaction invoking `array` method of the contract. +// This transaction is signed, but not sent to the network, instead it's +// returned to the caller. +func (c *Contract) ArrayTransaction() (*transaction.Transaction, error) { + return c.actor.MakeCall(Hash, "array") +} + +// ArrayUnsigned creates a transaction invoking `array` 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) ArrayUnsigned() (*transaction.Transaction, error) { + return c.actor.MakeUnsignedCall(Hash, "array", nil) +} + +// 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. +func (c *Contract) Main() (util.Uint256, uint32, error) { + return c.actor.SendCall(Hash, "main") +} + +// MainTransaction creates a transaction invoking `main` method of the contract. +// This transaction is signed, but not sent to the network, instead it's +// returned to the caller. +func (c *Contract) MainTransaction() (*transaction.Transaction, error) { + return c.actor.MakeCall(Hash, "main") +} + +// MainUnsigned creates a transaction invoking `main` 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) 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 { + return nil, err + } + var res = new(LedgerBlock) + err = res.FromStackItem(item) + return res, err +} + +// FromStackItem retrieves fields of LedgerBlock from the given +// [stackitem.Item] or returns an error if it's not possible to do to so. +func (res *LedgerBlock) FromStackItem(item stackitem.Item) error { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return errors.New("not an array") + } + if len(arr) != 9 { + return errors.New("wrong number of structure elements") + } + + var ( + index = -1 + err error + ) + index++ + res.Hash, err = func (item stackitem.Item) (util.Uint256, error) { + b, err := item.TryBytes() + if err != nil { + return util.Uint256{}, err + } + u, err := util.Uint256DecodeBytesBE(b) + if err != nil { + return util.Uint256{}, err + } + return u, nil + } (arr[index]) + if err != nil { + return fmt.Errorf("field Hash: %w", err) + } + + index++ + res.Version, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field Version: %w", err) + } + + index++ + res.PrevHash, err = func (item stackitem.Item) (util.Uint256, error) { + b, err := item.TryBytes() + if err != nil { + return util.Uint256{}, err + } + u, err := util.Uint256DecodeBytesBE(b) + if err != nil { + return util.Uint256{}, err + } + return u, nil + } (arr[index]) + if err != nil { + return fmt.Errorf("field PrevHash: %w", err) + } + + index++ + res.MerkleRoot, err = func (item stackitem.Item) (util.Uint256, error) { + b, err := item.TryBytes() + if err != nil { + return util.Uint256{}, err + } + u, err := util.Uint256DecodeBytesBE(b) + if err != nil { + return util.Uint256{}, err + } + return u, nil + } (arr[index]) + if err != nil { + return fmt.Errorf("field MerkleRoot: %w", err) + } + + index++ + res.Timestamp, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field Timestamp: %w", err) + } + + index++ + res.Nonce, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field Nonce: %w", err) + } + + index++ + res.Index, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field Index: %w", err) + } + + index++ + res.NextConsensus, err = func (item stackitem.Item) (util.Uint160, error) { + b, err := item.TryBytes() + if err != nil { + return util.Uint160{}, err + } + u, err := util.Uint160DecodeBytesBE(b) + if err != nil { + return util.Uint160{}, err + } + return u, nil + } (arr[index]) + if err != nil { + return fmt.Errorf("field NextConsensus: %w", err) + } + + index++ + res.TransactionsLength, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field TransactionsLength: %w", err) + } + + return nil +} + +// itemToLedgerBlockSR converts stack item into *LedgerBlockSR. +func itemToLedgerBlockSR(item stackitem.Item, err error) (*LedgerBlockSR, error) { + if err != nil { + return nil, err + } + var res = new(LedgerBlockSR) + err = res.FromStackItem(item) + return res, err +} + +// FromStackItem retrieves fields of LedgerBlockSR from the given +// [stackitem.Item] or returns an error if it's not possible to do to so. +func (res *LedgerBlockSR) FromStackItem(item stackitem.Item) error { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return errors.New("not an array") + } + if len(arr) != 10 { + return errors.New("wrong number of structure elements") + } + + var ( + index = -1 + err error + ) + index++ + res.Hash, err = func (item stackitem.Item) (util.Uint256, error) { + b, err := item.TryBytes() + if err != nil { + return util.Uint256{}, err + } + u, err := util.Uint256DecodeBytesBE(b) + if err != nil { + return util.Uint256{}, err + } + return u, nil + } (arr[index]) + if err != nil { + return fmt.Errorf("field Hash: %w", err) + } + + index++ + res.Version, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field Version: %w", err) + } + + index++ + res.PrevHash, err = func (item stackitem.Item) (util.Uint256, error) { + b, err := item.TryBytes() + if err != nil { + return util.Uint256{}, err + } + u, err := util.Uint256DecodeBytesBE(b) + if err != nil { + return util.Uint256{}, err + } + return u, nil + } (arr[index]) + if err != nil { + return fmt.Errorf("field PrevHash: %w", err) + } + + index++ + res.MerkleRoot, err = func (item stackitem.Item) (util.Uint256, error) { + b, err := item.TryBytes() + if err != nil { + return util.Uint256{}, err + } + u, err := util.Uint256DecodeBytesBE(b) + if err != nil { + return util.Uint256{}, err + } + return u, nil + } (arr[index]) + if err != nil { + return fmt.Errorf("field MerkleRoot: %w", err) + } + + index++ + res.Timestamp, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field Timestamp: %w", err) + } + + index++ + res.Nonce, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field Nonce: %w", err) + } + + index++ + res.Index, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field Index: %w", err) + } + + index++ + res.NextConsensus, err = func (item stackitem.Item) (util.Uint160, error) { + b, err := item.TryBytes() + if err != nil { + return util.Uint160{}, err + } + u, err := util.Uint160DecodeBytesBE(b) + if err != nil { + return util.Uint160{}, err + } + return u, nil + } (arr[index]) + if err != nil { + return fmt.Errorf("field NextConsensus: %w", err) + } + + index++ + res.TransactionsLength, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field TransactionsLength: %w", err) + } + + index++ + res.PrevStateRoot, err = func (item stackitem.Item) (util.Uint256, error) { + b, err := item.TryBytes() + if err != nil { + return util.Uint256{}, err + } + u, err := util.Uint256DecodeBytesBE(b) + if err != nil { + return util.Uint256{}, err + } + return u, nil + } (arr[index]) + if err != nil { + return fmt.Errorf("field PrevStateRoot: %w", err) + } + + return nil +} + +// itemToLedgerTransaction converts stack item into *LedgerTransaction. +func itemToLedgerTransaction(item stackitem.Item, err error) (*LedgerTransaction, error) { + if err != nil { + return nil, err + } + var res = new(LedgerTransaction) + err = res.FromStackItem(item) + return res, err +} + +// FromStackItem retrieves fields of LedgerTransaction from the given +// [stackitem.Item] or returns an error if it's not possible to do to so. +func (res *LedgerTransaction) FromStackItem(item stackitem.Item) error { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return errors.New("not an array") + } + if len(arr) != 8 { + return errors.New("wrong number of structure elements") + } + + var ( + index = -1 + err error + ) + index++ + res.Hash, err = func (item stackitem.Item) (util.Uint256, error) { + b, err := item.TryBytes() + if err != nil { + return util.Uint256{}, err + } + u, err := util.Uint256DecodeBytesBE(b) + if err != nil { + return util.Uint256{}, err + } + return u, nil + } (arr[index]) + if err != nil { + return fmt.Errorf("field Hash: %w", err) + } + + index++ + res.Version, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field Version: %w", err) + } + + index++ + res.Nonce, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field Nonce: %w", err) + } + + index++ + res.Sender, err = func (item stackitem.Item) (util.Uint160, error) { + b, err := item.TryBytes() + if err != nil { + return util.Uint160{}, err + } + u, err := util.Uint160DecodeBytesBE(b) + if err != nil { + return util.Uint160{}, err + } + return u, nil + } (arr[index]) + if err != nil { + return fmt.Errorf("field Sender: %w", err) + } + + index++ + res.SysFee, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field SysFee: %w", err) + } + + index++ + res.NetFee, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field NetFee: %w", err) + } + + index++ + res.ValidUntilBlock, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field ValidUntilBlock: %w", err) + } + + index++ + res.Script, err = arr[index].TryBytes() + if err != nil { + return fmt.Errorf("field Script: %w", err) + } + + return nil +} + +// itemToLedgerTransactionSigner converts stack item into *LedgerTransactionSigner. +func itemToLedgerTransactionSigner(item stackitem.Item, err error) (*LedgerTransactionSigner, error) { + if err != nil { + return nil, err + } + var res = new(LedgerTransactionSigner) + err = res.FromStackItem(item) + return res, err +} + +// FromStackItem retrieves fields of LedgerTransactionSigner from the given +// [stackitem.Item] or returns an error if it's not possible to do to so. +func (res *LedgerTransactionSigner) FromStackItem(item stackitem.Item) error { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return errors.New("not an array") + } + if len(arr) != 5 { + return errors.New("wrong number of structure elements") + } + + var ( + index = -1 + err error + ) + index++ + res.Account, err = func (item stackitem.Item) (util.Uint160, error) { + b, err := item.TryBytes() + if err != nil { + return util.Uint160{}, err + } + u, err := util.Uint160DecodeBytesBE(b) + if err != nil { + return util.Uint160{}, err + } + return u, nil + } (arr[index]) + if err != nil { + return fmt.Errorf("field Account: %w", err) + } + + index++ + res.Scopes, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field Scopes: %w", err) + } + + index++ + res.AllowedContracts, err = func (item stackitem.Item) ([]util.Uint160, error) { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return nil, errors.New("not an array") + } + res := make([]util.Uint160, len(arr)) + for i := range res { + res[i], err = func (item stackitem.Item) (util.Uint160, error) { + b, err := item.TryBytes() + if err != nil { + return util.Uint160{}, err + } + u, err := util.Uint160DecodeBytesBE(b) + if err != nil { + return util.Uint160{}, err + } + return u, nil + } (arr[i]) + if err != nil { + return nil, fmt.Errorf("item %d: %w", i, err) + } + } + return res, nil + } (arr[index]) + if err != nil { + return fmt.Errorf("field AllowedContracts: %w", err) + } + + index++ + res.AllowedGroups, err = func (item stackitem.Item) (keys.PublicKeys, error) { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return nil, errors.New("not an array") + } + res := make(keys.PublicKeys, len(arr)) + for i := range res { + res[i], err = func (item stackitem.Item) (*keys.PublicKey, error) { + b, err := item.TryBytes() + if err != nil { + return nil, err + } + k, err := keys.NewPublicKeyFromBytes(b, elliptic.P256()) + if err != nil { + return nil, err + } + return k, nil + } (arr[i]) + if err != nil { + return nil, fmt.Errorf("item %d: %w", i, err) + } + } + return res, nil + } (arr[index]) + if err != nil { + return fmt.Errorf("field AllowedGroups: %w", err) + } + + index++ + res.Rules, err = func (item stackitem.Item) ([]*LedgerWitnessRule, error) { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return nil, errors.New("not an array") + } + res := make([]*LedgerWitnessRule, len(arr)) + for i := range res { + res[i], err = itemToLedgerWitnessRule(arr[i], 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 Rules: %w", err) + } + + return nil +} + +// itemToLedgerWitnessCondition converts stack item into *LedgerWitnessCondition. +func itemToLedgerWitnessCondition(item stackitem.Item, err error) (*LedgerWitnessCondition, error) { + if err != nil { + return nil, err + } + var res = new(LedgerWitnessCondition) + err = res.FromStackItem(item) + return res, err +} + +// FromStackItem retrieves fields of LedgerWitnessCondition from the given +// [stackitem.Item] or returns an error if it's not possible to do to so. +func (res *LedgerWitnessCondition) FromStackItem(item stackitem.Item) error { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return errors.New("not an array") + } + if len(arr) != 2 { + return errors.New("wrong number of structure elements") + } + + var ( + index = -1 + err error + ) + index++ + res.Type, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field Type: %w", err) + } + + index++ + res.Value, err = arr[index].Value(), error(nil) + if err != nil { + return fmt.Errorf("field Value: %w", err) + } + + return nil +} + +// itemToLedgerWitnessRule converts stack item into *LedgerWitnessRule. +func itemToLedgerWitnessRule(item stackitem.Item, err error) (*LedgerWitnessRule, error) { + if err != nil { + return nil, err + } + var res = new(LedgerWitnessRule) + err = res.FromStackItem(item) + return res, err +} + +// FromStackItem retrieves fields of LedgerWitnessRule from the given +// [stackitem.Item] or returns an error if it's not possible to do to so. +func (res *LedgerWitnessRule) FromStackItem(item stackitem.Item) error { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return errors.New("not an array") + } + if len(arr) != 2 { + return errors.New("wrong number of structure elements") + } + + var ( + index = -1 + err error + ) + index++ + res.Action, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field Action: %w", err) + } + + index++ + res.Condition, err = itemToLedgerWitnessCondition(arr[index], nil) + if err != nil { + return fmt.Errorf("field Condition: %w", err) + } + + return nil +} + +// ComplicatedNameEventsFromApplicationLog retrieves a set of all emitted events +// with "! complicated name %$#" name from the provided [result.ApplicationLog]. +func ComplicatedNameEventsFromApplicationLog(log *result.ApplicationLog) ([]*ComplicatedNameEvent, error) { + if log == nil { + return nil, errors.New("nil application log") + } + + var res []*ComplicatedNameEvent + for i, ex := range log.Executions { + for j, e := range ex.Events { + if e.Name != "! complicated name %$#" { + continue + } + event := new(ComplicatedNameEvent) + err := event.FromStackItem(e.Item) + if err != nil { + return nil, fmt.Errorf("failed to deserialize ComplicatedNameEvent from stackitem (execution #%d, event #%d): %w", i, j, err) + } + res = append(res, event) + } + } + + return res, nil +} + +// FromStackItem converts provided [stackitem.Array] to ComplicatedNameEvent or +// returns an error if it's not possible to do to so. +func (e *ComplicatedNameEvent) 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.ComplicatedParam, err = func (item stackitem.Item) (string, error) { + b, err := item.TryBytes() + if err != nil { + return "", err + } + if !utf8.Valid(b) { + return "", errors.New("not a UTF-8 string") + } + return string(b), nil + } (arr[index]) + if err != nil { + return fmt.Errorf("field ComplicatedParam: %w", err) + } + + return nil +} + +// SomeMapEventsFromApplicationLog retrieves a set of all emitted events +// with "SomeMap" name from the provided [result.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 or +// returns an error if it's not possible to do to 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 [result.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 or +// returns an error if it's not possible to do to 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 +} + +// SomeArrayEventsFromApplicationLog retrieves a set of all emitted events +// with "SomeArray" name from the provided [result.ApplicationLog]. +func SomeArrayEventsFromApplicationLog(log *result.ApplicationLog) ([]*SomeArrayEvent, error) { + if log == nil { + return nil, errors.New("nil application log") + } + + var res []*SomeArrayEvent + for i, ex := range log.Executions { + for j, e := range ex.Events { + if e.Name != "SomeArray" { + continue + } + event := new(SomeArrayEvent) + err := event.FromStackItem(e.Item) + if err != nil { + return nil, fmt.Errorf("failed to deserialize SomeArrayEvent from stackitem (execution #%d, event #%d): %w", i, j, err) + } + res = append(res, event) + } + } + + return res, nil +} + +// FromStackItem converts provided [stackitem.Array] to SomeArrayEvent or +// returns an error if it's not possible to do to so. +func (e *SomeArrayEvent) 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.A, 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 A: %w", err) + } + + return nil +} diff --git a/cli/smartcontract/testdata/notifications/rpcbindings_extended.out b/cli/smartcontract/testdata/notifications/rpcbindings_extended.out new file mode 100755 index 000000000..818c91993 --- /dev/null +++ b/cli/smartcontract/testdata/notifications/rpcbindings_extended.out @@ -0,0 +1,1126 @@ +// Package structs contains RPC wrappers for Notifications contract. +package structs + +import ( + "crypto/elliptic" + "errors" + "fmt" + "github.com/nspcc-dev/neo-go/pkg/core/transaction" + "github.com/nspcc-dev/neo-go/pkg/crypto/keys" + "github.com/nspcc-dev/neo-go/pkg/neorpc/result" + "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" + "math/big" + "unicode/utf8" +) + +// Hash contains contract hash. +var Hash = util.Uint160{0x33, 0x22, 0x11, 0x0, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, 0x0} + +// CrazyStruct is a contract-specific crazyStruct type used by its methods. +type CrazyStruct struct { + I *big.Int + B bool +} + +// LedgerBlock is a contract-specific ledger.Block type used by its methods. +type LedgerBlock struct { + Hash util.Uint256 + Version *big.Int + PrevHash util.Uint256 + MerkleRoot util.Uint256 + Timestamp *big.Int + Nonce *big.Int + Index *big.Int + NextConsensus util.Uint160 + TransactionsLength *big.Int +} + +// LedgerBlockSR is a contract-specific ledger.BlockSR type used by its methods. +type LedgerBlockSR struct { + Hash util.Uint256 + Version *big.Int + PrevHash util.Uint256 + MerkleRoot util.Uint256 + Timestamp *big.Int + Nonce *big.Int + Index *big.Int + NextConsensus util.Uint160 + TransactionsLength *big.Int + PrevStateRoot util.Uint256 +} + +// LedgerTransaction is a contract-specific ledger.Transaction type used by its methods. +type LedgerTransaction struct { + Hash util.Uint256 + Version *big.Int + Nonce *big.Int + Sender util.Uint160 + SysFee *big.Int + NetFee *big.Int + ValidUntilBlock *big.Int + Script []byte +} + +// LedgerTransactionSigner is a contract-specific ledger.TransactionSigner type used by its methods. +type LedgerTransactionSigner struct { + Account util.Uint160 + Scopes *big.Int + AllowedContracts []util.Uint160 + AllowedGroups keys.PublicKeys + Rules []*LedgerWitnessRule +} + +// LedgerWitnessCondition is a contract-specific ledger.WitnessCondition type used by its methods. +type LedgerWitnessCondition struct { + Type *big.Int + Value any +} + +// LedgerWitnessRule is a contract-specific ledger.WitnessRule type used by its methods. +type LedgerWitnessRule struct { + Action *big.Int + Condition *LedgerWitnessCondition +} + +// ComplicatedNameEvent represents "! complicated name %$#" event emitted by the contract. +type ComplicatedNameEvent struct { + ComplicatedParam string +} + +// SomeMapEvent represents "SomeMap" event emitted by the contract. +type SomeMapEvent struct { + M map[*big.Int]map[string][]util.Uint160 +} + +// SomeStructEvent represents "SomeStruct" event emitted by the contract. +type SomeStructEvent struct { + S *CrazyStruct +} + +// SomeArrayEvent represents "SomeArray" event emitted by the contract. +type SomeArrayEvent struct { + A [][]*big.Int +} + +// Actor is used by Contract to call state-changing methods. +type Actor interface { + MakeCall(contract util.Uint160, method string, params ...any) (*transaction.Transaction, error) + MakeRun(script []byte) (*transaction.Transaction, error) + MakeUnsignedCall(contract util.Uint160, method string, attrs []transaction.Attribute, params ...any) (*transaction.Transaction, error) + MakeUnsignedRun(script []byte, attrs []transaction.Attribute) (*transaction.Transaction, error) + SendCall(contract util.Uint160, method string, params ...any) (util.Uint256, uint32, error) + SendRun(script []byte) (util.Uint256, uint32, error) +} + +// Contract implements all contract methods. +type Contract struct { + actor Actor +} + +// New creates an instance of Contract using Hash and the given Actor. +func New(actor Actor) *Contract { + return &Contract{actor} +} + +// Array creates a transaction invoking `array` 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) Array() (util.Uint256, uint32, error) { + return c.actor.SendCall(Hash, "array") +} + +// ArrayTransaction creates a transaction invoking `array` method of the contract. +// This transaction is signed, but not sent to the network, instead it's +// returned to the caller. +func (c *Contract) ArrayTransaction() (*transaction.Transaction, error) { + return c.actor.MakeCall(Hash, "array") +} + +// ArrayUnsigned creates a transaction invoking `array` 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) ArrayUnsigned() (*transaction.Transaction, error) { + return c.actor.MakeUnsignedCall(Hash, "array", nil) +} + +// 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. +func (c *Contract) Main() (util.Uint256, uint32, error) { + return c.actor.SendCall(Hash, "main") +} + +// MainTransaction creates a transaction invoking `main` method of the contract. +// This transaction is signed, but not sent to the network, instead it's +// returned to the caller. +func (c *Contract) MainTransaction() (*transaction.Transaction, error) { + return c.actor.MakeCall(Hash, "main") +} + +// MainUnsigned creates a transaction invoking `main` 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) 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) +} + +// itemToCrazyStruct converts stack item into *CrazyStruct. +func itemToCrazyStruct(item stackitem.Item, err error) (*CrazyStruct, error) { + if err != nil { + return nil, err + } + var res = new(CrazyStruct) + err = res.FromStackItem(item) + return res, err +} + +// FromStackItem retrieves fields of CrazyStruct from the given +// [stackitem.Item] or returns an error if it's not possible to do to so. +func (res *CrazyStruct) FromStackItem(item stackitem.Item) error { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return errors.New("not an array") + } + if len(arr) != 2 { + return errors.New("wrong number of structure elements") + } + + var ( + index = -1 + err error + ) + index++ + res.I, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field I: %w", err) + } + + index++ + res.B, err = arr[index].TryBool() + if err != nil { + return fmt.Errorf("field B: %w", err) + } + + return nil +} + +// itemToLedgerBlock converts stack item into *LedgerBlock. +func itemToLedgerBlock(item stackitem.Item, err error) (*LedgerBlock, error) { + if err != nil { + return nil, err + } + var res = new(LedgerBlock) + err = res.FromStackItem(item) + return res, err +} + +// FromStackItem retrieves fields of LedgerBlock from the given +// [stackitem.Item] or returns an error if it's not possible to do to so. +func (res *LedgerBlock) FromStackItem(item stackitem.Item) error { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return errors.New("not an array") + } + if len(arr) != 9 { + return errors.New("wrong number of structure elements") + } + + var ( + index = -1 + err error + ) + index++ + res.Hash, err = func (item stackitem.Item) (util.Uint256, error) { + b, err := item.TryBytes() + if err != nil { + return util.Uint256{}, err + } + u, err := util.Uint256DecodeBytesBE(b) + if err != nil { + return util.Uint256{}, err + } + return u, nil + } (arr[index]) + if err != nil { + return fmt.Errorf("field Hash: %w", err) + } + + index++ + res.Version, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field Version: %w", err) + } + + index++ + res.PrevHash, err = func (item stackitem.Item) (util.Uint256, error) { + b, err := item.TryBytes() + if err != nil { + return util.Uint256{}, err + } + u, err := util.Uint256DecodeBytesBE(b) + if err != nil { + return util.Uint256{}, err + } + return u, nil + } (arr[index]) + if err != nil { + return fmt.Errorf("field PrevHash: %w", err) + } + + index++ + res.MerkleRoot, err = func (item stackitem.Item) (util.Uint256, error) { + b, err := item.TryBytes() + if err != nil { + return util.Uint256{}, err + } + u, err := util.Uint256DecodeBytesBE(b) + if err != nil { + return util.Uint256{}, err + } + return u, nil + } (arr[index]) + if err != nil { + return fmt.Errorf("field MerkleRoot: %w", err) + } + + index++ + res.Timestamp, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field Timestamp: %w", err) + } + + index++ + res.Nonce, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field Nonce: %w", err) + } + + index++ + res.Index, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field Index: %w", err) + } + + index++ + res.NextConsensus, err = func (item stackitem.Item) (util.Uint160, error) { + b, err := item.TryBytes() + if err != nil { + return util.Uint160{}, err + } + u, err := util.Uint160DecodeBytesBE(b) + if err != nil { + return util.Uint160{}, err + } + return u, nil + } (arr[index]) + if err != nil { + return fmt.Errorf("field NextConsensus: %w", err) + } + + index++ + res.TransactionsLength, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field TransactionsLength: %w", err) + } + + return nil +} + +// itemToLedgerBlockSR converts stack item into *LedgerBlockSR. +func itemToLedgerBlockSR(item stackitem.Item, err error) (*LedgerBlockSR, error) { + if err != nil { + return nil, err + } + var res = new(LedgerBlockSR) + err = res.FromStackItem(item) + return res, err +} + +// FromStackItem retrieves fields of LedgerBlockSR from the given +// [stackitem.Item] or returns an error if it's not possible to do to so. +func (res *LedgerBlockSR) FromStackItem(item stackitem.Item) error { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return errors.New("not an array") + } + if len(arr) != 10 { + return errors.New("wrong number of structure elements") + } + + var ( + index = -1 + err error + ) + index++ + res.Hash, err = func (item stackitem.Item) (util.Uint256, error) { + b, err := item.TryBytes() + if err != nil { + return util.Uint256{}, err + } + u, err := util.Uint256DecodeBytesBE(b) + if err != nil { + return util.Uint256{}, err + } + return u, nil + } (arr[index]) + if err != nil { + return fmt.Errorf("field Hash: %w", err) + } + + index++ + res.Version, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field Version: %w", err) + } + + index++ + res.PrevHash, err = func (item stackitem.Item) (util.Uint256, error) { + b, err := item.TryBytes() + if err != nil { + return util.Uint256{}, err + } + u, err := util.Uint256DecodeBytesBE(b) + if err != nil { + return util.Uint256{}, err + } + return u, nil + } (arr[index]) + if err != nil { + return fmt.Errorf("field PrevHash: %w", err) + } + + index++ + res.MerkleRoot, err = func (item stackitem.Item) (util.Uint256, error) { + b, err := item.TryBytes() + if err != nil { + return util.Uint256{}, err + } + u, err := util.Uint256DecodeBytesBE(b) + if err != nil { + return util.Uint256{}, err + } + return u, nil + } (arr[index]) + if err != nil { + return fmt.Errorf("field MerkleRoot: %w", err) + } + + index++ + res.Timestamp, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field Timestamp: %w", err) + } + + index++ + res.Nonce, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field Nonce: %w", err) + } + + index++ + res.Index, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field Index: %w", err) + } + + index++ + res.NextConsensus, err = func (item stackitem.Item) (util.Uint160, error) { + b, err := item.TryBytes() + if err != nil { + return util.Uint160{}, err + } + u, err := util.Uint160DecodeBytesBE(b) + if err != nil { + return util.Uint160{}, err + } + return u, nil + } (arr[index]) + if err != nil { + return fmt.Errorf("field NextConsensus: %w", err) + } + + index++ + res.TransactionsLength, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field TransactionsLength: %w", err) + } + + index++ + res.PrevStateRoot, err = func (item stackitem.Item) (util.Uint256, error) { + b, err := item.TryBytes() + if err != nil { + return util.Uint256{}, err + } + u, err := util.Uint256DecodeBytesBE(b) + if err != nil { + return util.Uint256{}, err + } + return u, nil + } (arr[index]) + if err != nil { + return fmt.Errorf("field PrevStateRoot: %w", err) + } + + return nil +} + +// itemToLedgerTransaction converts stack item into *LedgerTransaction. +func itemToLedgerTransaction(item stackitem.Item, err error) (*LedgerTransaction, error) { + if err != nil { + return nil, err + } + var res = new(LedgerTransaction) + err = res.FromStackItem(item) + return res, err +} + +// FromStackItem retrieves fields of LedgerTransaction from the given +// [stackitem.Item] or returns an error if it's not possible to do to so. +func (res *LedgerTransaction) FromStackItem(item stackitem.Item) error { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return errors.New("not an array") + } + if len(arr) != 8 { + return errors.New("wrong number of structure elements") + } + + var ( + index = -1 + err error + ) + index++ + res.Hash, err = func (item stackitem.Item) (util.Uint256, error) { + b, err := item.TryBytes() + if err != nil { + return util.Uint256{}, err + } + u, err := util.Uint256DecodeBytesBE(b) + if err != nil { + return util.Uint256{}, err + } + return u, nil + } (arr[index]) + if err != nil { + return fmt.Errorf("field Hash: %w", err) + } + + index++ + res.Version, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field Version: %w", err) + } + + index++ + res.Nonce, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field Nonce: %w", err) + } + + index++ + res.Sender, err = func (item stackitem.Item) (util.Uint160, error) { + b, err := item.TryBytes() + if err != nil { + return util.Uint160{}, err + } + u, err := util.Uint160DecodeBytesBE(b) + if err != nil { + return util.Uint160{}, err + } + return u, nil + } (arr[index]) + if err != nil { + return fmt.Errorf("field Sender: %w", err) + } + + index++ + res.SysFee, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field SysFee: %w", err) + } + + index++ + res.NetFee, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field NetFee: %w", err) + } + + index++ + res.ValidUntilBlock, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field ValidUntilBlock: %w", err) + } + + index++ + res.Script, err = arr[index].TryBytes() + if err != nil { + return fmt.Errorf("field Script: %w", err) + } + + return nil +} + +// itemToLedgerTransactionSigner converts stack item into *LedgerTransactionSigner. +func itemToLedgerTransactionSigner(item stackitem.Item, err error) (*LedgerTransactionSigner, error) { + if err != nil { + return nil, err + } + var res = new(LedgerTransactionSigner) + err = res.FromStackItem(item) + return res, err +} + +// FromStackItem retrieves fields of LedgerTransactionSigner from the given +// [stackitem.Item] or returns an error if it's not possible to do to so. +func (res *LedgerTransactionSigner) FromStackItem(item stackitem.Item) error { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return errors.New("not an array") + } + if len(arr) != 5 { + return errors.New("wrong number of structure elements") + } + + var ( + index = -1 + err error + ) + index++ + res.Account, err = func (item stackitem.Item) (util.Uint160, error) { + b, err := item.TryBytes() + if err != nil { + return util.Uint160{}, err + } + u, err := util.Uint160DecodeBytesBE(b) + if err != nil { + return util.Uint160{}, err + } + return u, nil + } (arr[index]) + if err != nil { + return fmt.Errorf("field Account: %w", err) + } + + index++ + res.Scopes, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field Scopes: %w", err) + } + + index++ + res.AllowedContracts, err = func (item stackitem.Item) ([]util.Uint160, error) { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return nil, errors.New("not an array") + } + res := make([]util.Uint160, len(arr)) + for i := range res { + res[i], err = func (item stackitem.Item) (util.Uint160, error) { + b, err := item.TryBytes() + if err != nil { + return util.Uint160{}, err + } + u, err := util.Uint160DecodeBytesBE(b) + if err != nil { + return util.Uint160{}, err + } + return u, nil + } (arr[i]) + if err != nil { + return nil, fmt.Errorf("item %d: %w", i, err) + } + } + return res, nil + } (arr[index]) + if err != nil { + return fmt.Errorf("field AllowedContracts: %w", err) + } + + index++ + res.AllowedGroups, err = func (item stackitem.Item) (keys.PublicKeys, error) { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return nil, errors.New("not an array") + } + res := make(keys.PublicKeys, len(arr)) + for i := range res { + res[i], err = func (item stackitem.Item) (*keys.PublicKey, error) { + b, err := item.TryBytes() + if err != nil { + return nil, err + } + k, err := keys.NewPublicKeyFromBytes(b, elliptic.P256()) + if err != nil { + return nil, err + } + return k, nil + } (arr[i]) + if err != nil { + return nil, fmt.Errorf("item %d: %w", i, err) + } + } + return res, nil + } (arr[index]) + if err != nil { + return fmt.Errorf("field AllowedGroups: %w", err) + } + + index++ + res.Rules, err = func (item stackitem.Item) ([]*LedgerWitnessRule, error) { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return nil, errors.New("not an array") + } + res := make([]*LedgerWitnessRule, len(arr)) + for i := range res { + res[i], err = itemToLedgerWitnessRule(arr[i], 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 Rules: %w", err) + } + + return nil +} + +// itemToLedgerWitnessCondition converts stack item into *LedgerWitnessCondition. +func itemToLedgerWitnessCondition(item stackitem.Item, err error) (*LedgerWitnessCondition, error) { + if err != nil { + return nil, err + } + var res = new(LedgerWitnessCondition) + err = res.FromStackItem(item) + return res, err +} + +// FromStackItem retrieves fields of LedgerWitnessCondition from the given +// [stackitem.Item] or returns an error if it's not possible to do to so. +func (res *LedgerWitnessCondition) FromStackItem(item stackitem.Item) error { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return errors.New("not an array") + } + if len(arr) != 2 { + return errors.New("wrong number of structure elements") + } + + var ( + index = -1 + err error + ) + index++ + res.Type, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field Type: %w", err) + } + + index++ + res.Value, err = arr[index].Value(), error(nil) + if err != nil { + return fmt.Errorf("field Value: %w", err) + } + + return nil +} + +// itemToLedgerWitnessRule converts stack item into *LedgerWitnessRule. +func itemToLedgerWitnessRule(item stackitem.Item, err error) (*LedgerWitnessRule, error) { + if err != nil { + return nil, err + } + var res = new(LedgerWitnessRule) + err = res.FromStackItem(item) + return res, err +} + +// FromStackItem retrieves fields of LedgerWitnessRule from the given +// [stackitem.Item] or returns an error if it's not possible to do to so. +func (res *LedgerWitnessRule) FromStackItem(item stackitem.Item) error { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return errors.New("not an array") + } + if len(arr) != 2 { + return errors.New("wrong number of structure elements") + } + + var ( + index = -1 + err error + ) + index++ + res.Action, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field Action: %w", err) + } + + index++ + res.Condition, err = itemToLedgerWitnessCondition(arr[index], nil) + if err != nil { + return fmt.Errorf("field Condition: %w", err) + } + + return nil +} + +// ComplicatedNameEventsFromApplicationLog retrieves a set of all emitted events +// with "! complicated name %$#" name from the provided [result.ApplicationLog]. +func ComplicatedNameEventsFromApplicationLog(log *result.ApplicationLog) ([]*ComplicatedNameEvent, error) { + if log == nil { + return nil, errors.New("nil application log") + } + + var res []*ComplicatedNameEvent + for i, ex := range log.Executions { + for j, e := range ex.Events { + if e.Name != "! complicated name %$#" { + continue + } + event := new(ComplicatedNameEvent) + err := event.FromStackItem(e.Item) + if err != nil { + return nil, fmt.Errorf("failed to deserialize ComplicatedNameEvent from stackitem (execution #%d, event #%d): %w", i, j, err) + } + res = append(res, event) + } + } + + return res, nil +} + +// FromStackItem converts provided [stackitem.Array] to ComplicatedNameEvent or +// returns an error if it's not possible to do to so. +func (e *ComplicatedNameEvent) 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.ComplicatedParam, err = func (item stackitem.Item) (string, error) { + b, err := item.TryBytes() + if err != nil { + return "", err + } + if !utf8.Valid(b) { + return "", errors.New("not a UTF-8 string") + } + return string(b), nil + } (arr[index]) + if err != nil { + return fmt.Errorf("field ComplicatedParam: %w", err) + } + + return nil +} + +// SomeMapEventsFromApplicationLog retrieves a set of all emitted events +// with "SomeMap" name from the provided [result.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 or +// returns an error if it's not possible to do to 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[*big.Int]map[string][]util.Uint160, 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]map[string][]util.Uint160) + for i := range m { + k, err := m[i].Key.TryInteger() + if err != nil { + return nil, fmt.Errorf("key %d: %w", i, err) + } + v, err := func (item stackitem.Item) (map[string][]util.Uint160, error) { + m, ok := item.Value().([]stackitem.MapElement) + if !ok { + return nil, fmt.Errorf("%s is not a map", item.Type().String()) + } + res := make(map[string][]util.Uint160) + for i := range m { + k, err := func (item stackitem.Item) (string, error) { + b, err := item.TryBytes() + if err != nil { + return "", err + } + if !utf8.Valid(b) { + return "", errors.New("not a UTF-8 string") + } + return string(b), nil + } (m[i].Key) + if err != nil { + return nil, fmt.Errorf("key %d: %w", i, err) + } + v, err := func (item stackitem.Item) ([]util.Uint160, error) { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return nil, errors.New("not an array") + } + res := make([]util.Uint160, len(arr)) + for i := range res { + res[i], err = func (item stackitem.Item) (util.Uint160, error) { + b, err := item.TryBytes() + if err != nil { + return util.Uint160{}, err + } + u, err := util.Uint160DecodeBytesBE(b) + if err != nil { + return util.Uint160{}, err + } + return u, nil + } (arr[i]) + if err != nil { + return nil, fmt.Errorf("item %d: %w", i, err) + } + } + return res, nil + } (m[i].Value) + if err != nil { + return nil, fmt.Errorf("value %d: %w", i, err) + } + res[k] = v + } + return res, nil + } (m[i].Value) + 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 [result.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 or +// returns an error if it's not possible to do to 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 = itemToCrazyStruct(arr[index], nil) + if err != nil { + return fmt.Errorf("field S: %w", err) + } + + return nil +} + +// SomeArrayEventsFromApplicationLog retrieves a set of all emitted events +// with "SomeArray" name from the provided [result.ApplicationLog]. +func SomeArrayEventsFromApplicationLog(log *result.ApplicationLog) ([]*SomeArrayEvent, error) { + if log == nil { + return nil, errors.New("nil application log") + } + + var res []*SomeArrayEvent + for i, ex := range log.Executions { + for j, e := range ex.Events { + if e.Name != "SomeArray" { + continue + } + event := new(SomeArrayEvent) + err := event.FromStackItem(e.Item) + if err != nil { + return nil, fmt.Errorf("failed to deserialize SomeArrayEvent from stackitem (execution #%d, event #%d): %w", i, j, err) + } + res = append(res, event) + } + } + + return res, nil +} + +// FromStackItem converts provided [stackitem.Array] to SomeArrayEvent or +// returns an error if it's not possible to do to so. +func (e *SomeArrayEvent) 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.A, err = func (item stackitem.Item) ([][]*big.Int, error) { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return nil, errors.New("not an array") + } + res := make([][]*big.Int, len(arr)) + for i := range res { + res[i], err = func (item stackitem.Item) ([]*big.Int, error) { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return nil, errors.New("not an array") + } + res := make([]*big.Int, len(arr)) + for i := range res { + res[i], err = arr[i].TryInteger() + if err != nil { + return nil, fmt.Errorf("item %d: %w", i, err) + } + } + return res, nil + } (arr[i]) + if err != nil { + return nil, fmt.Errorf("item %d: %w", i, err) + } + } + return res, nil + } (arr[index]) + if err != nil { + return fmt.Errorf("field A: %w", err) + } + + return nil +} diff --git a/cli/smartcontract/testdata/notifications/rpcbindings_guessed.out b/cli/smartcontract/testdata/notifications/rpcbindings_guessed.out new file mode 100755 index 000000000..883283d09 --- /dev/null +++ b/cli/smartcontract/testdata/notifications/rpcbindings_guessed.out @@ -0,0 +1,1139 @@ +// Package structs contains RPC wrappers for Notifications contract. +package structs + +import ( + "crypto/elliptic" + "errors" + "fmt" + "github.com/nspcc-dev/neo-go/pkg/core/transaction" + "github.com/nspcc-dev/neo-go/pkg/crypto/keys" + "github.com/nspcc-dev/neo-go/pkg/neorpc/result" + "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" + "math/big" + "unicode/utf8" +) + +// Hash contains contract hash. +var Hash = util.Uint160{0x33, 0x22, 0x11, 0x0, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, 0x0} + +// LedgerBlock is a contract-specific ledger.Block type used by its methods. +type LedgerBlock struct { + Hash util.Uint256 + Version *big.Int + PrevHash util.Uint256 + MerkleRoot util.Uint256 + Timestamp *big.Int + Nonce *big.Int + Index *big.Int + NextConsensus util.Uint160 + TransactionsLength *big.Int +} + +// LedgerBlockSR is a contract-specific ledger.BlockSR type used by its methods. +type LedgerBlockSR struct { + Hash util.Uint256 + Version *big.Int + PrevHash util.Uint256 + MerkleRoot util.Uint256 + Timestamp *big.Int + Nonce *big.Int + Index *big.Int + NextConsensus util.Uint160 + TransactionsLength *big.Int + PrevStateRoot util.Uint256 +} + +// LedgerTransaction is a contract-specific ledger.Transaction type used by its methods. +type LedgerTransaction struct { + Hash util.Uint256 + Version *big.Int + Nonce *big.Int + Sender util.Uint160 + SysFee *big.Int + NetFee *big.Int + ValidUntilBlock *big.Int + Script []byte +} + +// LedgerTransactionSigner is a contract-specific ledger.TransactionSigner type used by its methods. +type LedgerTransactionSigner struct { + Account util.Uint160 + Scopes *big.Int + AllowedContracts []util.Uint160 + AllowedGroups keys.PublicKeys + Rules []*LedgerWitnessRule +} + +// LedgerWitnessCondition is a contract-specific ledger.WitnessCondition type used by its methods. +type LedgerWitnessCondition struct { + Type *big.Int + Value any +} + +// LedgerWitnessRule is a contract-specific ledger.WitnessRule type used by its methods. +type LedgerWitnessRule struct { + Action *big.Int + Condition *LedgerWitnessCondition +} + +// Unnamed is a contract-specific unnamed type used by its methods. +type Unnamed struct { + I *big.Int + B bool +} + +// ComplicatedNameEvent represents "! complicated name %$#" event emitted by the contract. +type ComplicatedNameEvent struct { + ComplicatedParam string +} + +// SomeMapEvent represents "SomeMap" event emitted by the contract. +type SomeMapEvent struct { + M map[*big.Int][]map[string][]util.Uint160 +} + +// SomeStructEvent represents "SomeStruct" event emitted by the contract. +type SomeStructEvent struct { + S *Unnamed +} + +// SomeArrayEvent represents "SomeArray" event emitted by the contract. +type SomeArrayEvent struct { + A [][]*big.Int +} + +// Actor is used by Contract to call state-changing methods. +type Actor interface { + MakeCall(contract util.Uint160, method string, params ...any) (*transaction.Transaction, error) + MakeRun(script []byte) (*transaction.Transaction, error) + MakeUnsignedCall(contract util.Uint160, method string, attrs []transaction.Attribute, params ...any) (*transaction.Transaction, error) + MakeUnsignedRun(script []byte, attrs []transaction.Attribute) (*transaction.Transaction, error) + SendCall(contract util.Uint160, method string, params ...any) (util.Uint256, uint32, error) + SendRun(script []byte) (util.Uint256, uint32, error) +} + +// Contract implements all contract methods. +type Contract struct { + actor Actor +} + +// New creates an instance of Contract using Hash and the given Actor. +func New(actor Actor) *Contract { + return &Contract{actor} +} + +// Array creates a transaction invoking `array` 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) Array() (util.Uint256, uint32, error) { + return c.actor.SendCall(Hash, "array") +} + +// ArrayTransaction creates a transaction invoking `array` method of the contract. +// This transaction is signed, but not sent to the network, instead it's +// returned to the caller. +func (c *Contract) ArrayTransaction() (*transaction.Transaction, error) { + return c.actor.MakeCall(Hash, "array") +} + +// ArrayUnsigned creates a transaction invoking `array` 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) ArrayUnsigned() (*transaction.Transaction, error) { + return c.actor.MakeUnsignedCall(Hash, "array", nil) +} + +// 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. +func (c *Contract) Main() (util.Uint256, uint32, error) { + return c.actor.SendCall(Hash, "main") +} + +// MainTransaction creates a transaction invoking `main` method of the contract. +// This transaction is signed, but not sent to the network, instead it's +// returned to the caller. +func (c *Contract) MainTransaction() (*transaction.Transaction, error) { + return c.actor.MakeCall(Hash, "main") +} + +// MainUnsigned creates a transaction invoking `main` 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) 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 { + return nil, err + } + var res = new(LedgerBlock) + err = res.FromStackItem(item) + return res, err +} + +// FromStackItem retrieves fields of LedgerBlock from the given +// [stackitem.Item] or returns an error if it's not possible to do to so. +func (res *LedgerBlock) FromStackItem(item stackitem.Item) error { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return errors.New("not an array") + } + if len(arr) != 9 { + return errors.New("wrong number of structure elements") + } + + var ( + index = -1 + err error + ) + index++ + res.Hash, err = func (item stackitem.Item) (util.Uint256, error) { + b, err := item.TryBytes() + if err != nil { + return util.Uint256{}, err + } + u, err := util.Uint256DecodeBytesBE(b) + if err != nil { + return util.Uint256{}, err + } + return u, nil + } (arr[index]) + if err != nil { + return fmt.Errorf("field Hash: %w", err) + } + + index++ + res.Version, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field Version: %w", err) + } + + index++ + res.PrevHash, err = func (item stackitem.Item) (util.Uint256, error) { + b, err := item.TryBytes() + if err != nil { + return util.Uint256{}, err + } + u, err := util.Uint256DecodeBytesBE(b) + if err != nil { + return util.Uint256{}, err + } + return u, nil + } (arr[index]) + if err != nil { + return fmt.Errorf("field PrevHash: %w", err) + } + + index++ + res.MerkleRoot, err = func (item stackitem.Item) (util.Uint256, error) { + b, err := item.TryBytes() + if err != nil { + return util.Uint256{}, err + } + u, err := util.Uint256DecodeBytesBE(b) + if err != nil { + return util.Uint256{}, err + } + return u, nil + } (arr[index]) + if err != nil { + return fmt.Errorf("field MerkleRoot: %w", err) + } + + index++ + res.Timestamp, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field Timestamp: %w", err) + } + + index++ + res.Nonce, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field Nonce: %w", err) + } + + index++ + res.Index, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field Index: %w", err) + } + + index++ + res.NextConsensus, err = func (item stackitem.Item) (util.Uint160, error) { + b, err := item.TryBytes() + if err != nil { + return util.Uint160{}, err + } + u, err := util.Uint160DecodeBytesBE(b) + if err != nil { + return util.Uint160{}, err + } + return u, nil + } (arr[index]) + if err != nil { + return fmt.Errorf("field NextConsensus: %w", err) + } + + index++ + res.TransactionsLength, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field TransactionsLength: %w", err) + } + + return nil +} + +// itemToLedgerBlockSR converts stack item into *LedgerBlockSR. +func itemToLedgerBlockSR(item stackitem.Item, err error) (*LedgerBlockSR, error) { + if err != nil { + return nil, err + } + var res = new(LedgerBlockSR) + err = res.FromStackItem(item) + return res, err +} + +// FromStackItem retrieves fields of LedgerBlockSR from the given +// [stackitem.Item] or returns an error if it's not possible to do to so. +func (res *LedgerBlockSR) FromStackItem(item stackitem.Item) error { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return errors.New("not an array") + } + if len(arr) != 10 { + return errors.New("wrong number of structure elements") + } + + var ( + index = -1 + err error + ) + index++ + res.Hash, err = func (item stackitem.Item) (util.Uint256, error) { + b, err := item.TryBytes() + if err != nil { + return util.Uint256{}, err + } + u, err := util.Uint256DecodeBytesBE(b) + if err != nil { + return util.Uint256{}, err + } + return u, nil + } (arr[index]) + if err != nil { + return fmt.Errorf("field Hash: %w", err) + } + + index++ + res.Version, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field Version: %w", err) + } + + index++ + res.PrevHash, err = func (item stackitem.Item) (util.Uint256, error) { + b, err := item.TryBytes() + if err != nil { + return util.Uint256{}, err + } + u, err := util.Uint256DecodeBytesBE(b) + if err != nil { + return util.Uint256{}, err + } + return u, nil + } (arr[index]) + if err != nil { + return fmt.Errorf("field PrevHash: %w", err) + } + + index++ + res.MerkleRoot, err = func (item stackitem.Item) (util.Uint256, error) { + b, err := item.TryBytes() + if err != nil { + return util.Uint256{}, err + } + u, err := util.Uint256DecodeBytesBE(b) + if err != nil { + return util.Uint256{}, err + } + return u, nil + } (arr[index]) + if err != nil { + return fmt.Errorf("field MerkleRoot: %w", err) + } + + index++ + res.Timestamp, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field Timestamp: %w", err) + } + + index++ + res.Nonce, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field Nonce: %w", err) + } + + index++ + res.Index, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field Index: %w", err) + } + + index++ + res.NextConsensus, err = func (item stackitem.Item) (util.Uint160, error) { + b, err := item.TryBytes() + if err != nil { + return util.Uint160{}, err + } + u, err := util.Uint160DecodeBytesBE(b) + if err != nil { + return util.Uint160{}, err + } + return u, nil + } (arr[index]) + if err != nil { + return fmt.Errorf("field NextConsensus: %w", err) + } + + index++ + res.TransactionsLength, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field TransactionsLength: %w", err) + } + + index++ + res.PrevStateRoot, err = func (item stackitem.Item) (util.Uint256, error) { + b, err := item.TryBytes() + if err != nil { + return util.Uint256{}, err + } + u, err := util.Uint256DecodeBytesBE(b) + if err != nil { + return util.Uint256{}, err + } + return u, nil + } (arr[index]) + if err != nil { + return fmt.Errorf("field PrevStateRoot: %w", err) + } + + return nil +} + +// itemToLedgerTransaction converts stack item into *LedgerTransaction. +func itemToLedgerTransaction(item stackitem.Item, err error) (*LedgerTransaction, error) { + if err != nil { + return nil, err + } + var res = new(LedgerTransaction) + err = res.FromStackItem(item) + return res, err +} + +// FromStackItem retrieves fields of LedgerTransaction from the given +// [stackitem.Item] or returns an error if it's not possible to do to so. +func (res *LedgerTransaction) FromStackItem(item stackitem.Item) error { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return errors.New("not an array") + } + if len(arr) != 8 { + return errors.New("wrong number of structure elements") + } + + var ( + index = -1 + err error + ) + index++ + res.Hash, err = func (item stackitem.Item) (util.Uint256, error) { + b, err := item.TryBytes() + if err != nil { + return util.Uint256{}, err + } + u, err := util.Uint256DecodeBytesBE(b) + if err != nil { + return util.Uint256{}, err + } + return u, nil + } (arr[index]) + if err != nil { + return fmt.Errorf("field Hash: %w", err) + } + + index++ + res.Version, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field Version: %w", err) + } + + index++ + res.Nonce, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field Nonce: %w", err) + } + + index++ + res.Sender, err = func (item stackitem.Item) (util.Uint160, error) { + b, err := item.TryBytes() + if err != nil { + return util.Uint160{}, err + } + u, err := util.Uint160DecodeBytesBE(b) + if err != nil { + return util.Uint160{}, err + } + return u, nil + } (arr[index]) + if err != nil { + return fmt.Errorf("field Sender: %w", err) + } + + index++ + res.SysFee, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field SysFee: %w", err) + } + + index++ + res.NetFee, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field NetFee: %w", err) + } + + index++ + res.ValidUntilBlock, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field ValidUntilBlock: %w", err) + } + + index++ + res.Script, err = arr[index].TryBytes() + if err != nil { + return fmt.Errorf("field Script: %w", err) + } + + return nil +} + +// itemToLedgerTransactionSigner converts stack item into *LedgerTransactionSigner. +func itemToLedgerTransactionSigner(item stackitem.Item, err error) (*LedgerTransactionSigner, error) { + if err != nil { + return nil, err + } + var res = new(LedgerTransactionSigner) + err = res.FromStackItem(item) + return res, err +} + +// FromStackItem retrieves fields of LedgerTransactionSigner from the given +// [stackitem.Item] or returns an error if it's not possible to do to so. +func (res *LedgerTransactionSigner) FromStackItem(item stackitem.Item) error { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return errors.New("not an array") + } + if len(arr) != 5 { + return errors.New("wrong number of structure elements") + } + + var ( + index = -1 + err error + ) + index++ + res.Account, err = func (item stackitem.Item) (util.Uint160, error) { + b, err := item.TryBytes() + if err != nil { + return util.Uint160{}, err + } + u, err := util.Uint160DecodeBytesBE(b) + if err != nil { + return util.Uint160{}, err + } + return u, nil + } (arr[index]) + if err != nil { + return fmt.Errorf("field Account: %w", err) + } + + index++ + res.Scopes, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field Scopes: %w", err) + } + + index++ + res.AllowedContracts, err = func (item stackitem.Item) ([]util.Uint160, error) { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return nil, errors.New("not an array") + } + res := make([]util.Uint160, len(arr)) + for i := range res { + res[i], err = func (item stackitem.Item) (util.Uint160, error) { + b, err := item.TryBytes() + if err != nil { + return util.Uint160{}, err + } + u, err := util.Uint160DecodeBytesBE(b) + if err != nil { + return util.Uint160{}, err + } + return u, nil + } (arr[i]) + if err != nil { + return nil, fmt.Errorf("item %d: %w", i, err) + } + } + return res, nil + } (arr[index]) + if err != nil { + return fmt.Errorf("field AllowedContracts: %w", err) + } + + index++ + res.AllowedGroups, err = func (item stackitem.Item) (keys.PublicKeys, error) { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return nil, errors.New("not an array") + } + res := make(keys.PublicKeys, len(arr)) + for i := range res { + res[i], err = func (item stackitem.Item) (*keys.PublicKey, error) { + b, err := item.TryBytes() + if err != nil { + return nil, err + } + k, err := keys.NewPublicKeyFromBytes(b, elliptic.P256()) + if err != nil { + return nil, err + } + return k, nil + } (arr[i]) + if err != nil { + return nil, fmt.Errorf("item %d: %w", i, err) + } + } + return res, nil + } (arr[index]) + if err != nil { + return fmt.Errorf("field AllowedGroups: %w", err) + } + + index++ + res.Rules, err = func (item stackitem.Item) ([]*LedgerWitnessRule, error) { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return nil, errors.New("not an array") + } + res := make([]*LedgerWitnessRule, len(arr)) + for i := range res { + res[i], err = itemToLedgerWitnessRule(arr[i], 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 Rules: %w", err) + } + + return nil +} + +// itemToLedgerWitnessCondition converts stack item into *LedgerWitnessCondition. +func itemToLedgerWitnessCondition(item stackitem.Item, err error) (*LedgerWitnessCondition, error) { + if err != nil { + return nil, err + } + var res = new(LedgerWitnessCondition) + err = res.FromStackItem(item) + return res, err +} + +// FromStackItem retrieves fields of LedgerWitnessCondition from the given +// [stackitem.Item] or returns an error if it's not possible to do to so. +func (res *LedgerWitnessCondition) FromStackItem(item stackitem.Item) error { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return errors.New("not an array") + } + if len(arr) != 2 { + return errors.New("wrong number of structure elements") + } + + var ( + index = -1 + err error + ) + index++ + res.Type, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field Type: %w", err) + } + + index++ + res.Value, err = arr[index].Value(), error(nil) + if err != nil { + return fmt.Errorf("field Value: %w", err) + } + + return nil +} + +// itemToLedgerWitnessRule converts stack item into *LedgerWitnessRule. +func itemToLedgerWitnessRule(item stackitem.Item, err error) (*LedgerWitnessRule, error) { + if err != nil { + return nil, err + } + var res = new(LedgerWitnessRule) + err = res.FromStackItem(item) + return res, err +} + +// FromStackItem retrieves fields of LedgerWitnessRule from the given +// [stackitem.Item] or returns an error if it's not possible to do to so. +func (res *LedgerWitnessRule) FromStackItem(item stackitem.Item) error { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return errors.New("not an array") + } + if len(arr) != 2 { + return errors.New("wrong number of structure elements") + } + + var ( + index = -1 + err error + ) + index++ + res.Action, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field Action: %w", err) + } + + index++ + res.Condition, err = itemToLedgerWitnessCondition(arr[index], nil) + if err != nil { + return fmt.Errorf("field Condition: %w", err) + } + + return nil +} + +// itemToUnnamed converts stack item into *Unnamed. +func itemToUnnamed(item stackitem.Item, err error) (*Unnamed, error) { + if err != nil { + return nil, err + } + var res = new(Unnamed) + err = res.FromStackItem(item) + return res, err +} + +// FromStackItem retrieves fields of Unnamed from the given +// [stackitem.Item] or returns an error if it's not possible to do to so. +func (res *Unnamed) FromStackItem(item stackitem.Item) error { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return errors.New("not an array") + } + if len(arr) != 2 { + return errors.New("wrong number of structure elements") + } + + var ( + index = -1 + err error + ) + index++ + res.I, err = arr[index].TryInteger() + if err != nil { + return fmt.Errorf("field I: %w", err) + } + + index++ + res.B, err = arr[index].TryBool() + if err != nil { + return fmt.Errorf("field B: %w", err) + } + + return nil +} + +// ComplicatedNameEventsFromApplicationLog retrieves a set of all emitted events +// with "! complicated name %$#" name from the provided [result.ApplicationLog]. +func ComplicatedNameEventsFromApplicationLog(log *result.ApplicationLog) ([]*ComplicatedNameEvent, error) { + if log == nil { + return nil, errors.New("nil application log") + } + + var res []*ComplicatedNameEvent + for i, ex := range log.Executions { + for j, e := range ex.Events { + if e.Name != "! complicated name %$#" { + continue + } + event := new(ComplicatedNameEvent) + err := event.FromStackItem(e.Item) + if err != nil { + return nil, fmt.Errorf("failed to deserialize ComplicatedNameEvent from stackitem (execution #%d, event #%d): %w", i, j, err) + } + res = append(res, event) + } + } + + return res, nil +} + +// FromStackItem converts provided [stackitem.Array] to ComplicatedNameEvent or +// returns an error if it's not possible to do to so. +func (e *ComplicatedNameEvent) 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.ComplicatedParam, err = func (item stackitem.Item) (string, error) { + b, err := item.TryBytes() + if err != nil { + return "", err + } + if !utf8.Valid(b) { + return "", errors.New("not a UTF-8 string") + } + return string(b), nil + } (arr[index]) + if err != nil { + return fmt.Errorf("field ComplicatedParam: %w", err) + } + + return nil +} + +// SomeMapEventsFromApplicationLog retrieves a set of all emitted events +// with "SomeMap" name from the provided [result.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 or +// returns an error if it's not possible to do to 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[*big.Int][]map[string][]util.Uint160, 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][]map[string][]util.Uint160) + for i := range m { + k, err := m[i].Key.TryInteger() + if err != nil { + return nil, fmt.Errorf("key %d: %w", i, err) + } + v, err := func (item stackitem.Item) ([]map[string][]util.Uint160, error) { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return nil, errors.New("not an array") + } + res := make([]map[string][]util.Uint160, len(arr)) + for i := range res { + res[i], err = func (item stackitem.Item) (map[string][]util.Uint160, error) { + m, ok := item.Value().([]stackitem.MapElement) + if !ok { + return nil, fmt.Errorf("%s is not a map", item.Type().String()) + } + res := make(map[string][]util.Uint160) + for i := range m { + k, err := func (item stackitem.Item) (string, error) { + b, err := item.TryBytes() + if err != nil { + return "", err + } + if !utf8.Valid(b) { + return "", errors.New("not a UTF-8 string") + } + return string(b), nil + } (m[i].Key) + if err != nil { + return nil, fmt.Errorf("key %d: %w", i, err) + } + v, err := func (item stackitem.Item) ([]util.Uint160, error) { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return nil, errors.New("not an array") + } + res := make([]util.Uint160, len(arr)) + for i := range res { + res[i], err = func (item stackitem.Item) (util.Uint160, error) { + b, err := item.TryBytes() + if err != nil { + return util.Uint160{}, err + } + u, err := util.Uint160DecodeBytesBE(b) + if err != nil { + return util.Uint160{}, err + } + return u, nil + } (arr[i]) + if err != nil { + return nil, fmt.Errorf("item %d: %w", i, err) + } + } + return res, nil + } (m[i].Value) + if err != nil { + return nil, fmt.Errorf("value %d: %w", i, err) + } + res[k] = v + } + return res, nil + } (arr[i]) + if err != nil { + return nil, fmt.Errorf("item %d: %w", i, err) + } + } + return res, nil + } (m[i].Value) + 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 [result.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 or +// returns an error if it's not possible to do to 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 = itemToUnnamed(arr[index], nil) + if err != nil { + return fmt.Errorf("field S: %w", err) + } + + return nil +} + +// SomeArrayEventsFromApplicationLog retrieves a set of all emitted events +// with "SomeArray" name from the provided [result.ApplicationLog]. +func SomeArrayEventsFromApplicationLog(log *result.ApplicationLog) ([]*SomeArrayEvent, error) { + if log == nil { + return nil, errors.New("nil application log") + } + + var res []*SomeArrayEvent + for i, ex := range log.Executions { + for j, e := range ex.Events { + if e.Name != "SomeArray" { + continue + } + event := new(SomeArrayEvent) + err := event.FromStackItem(e.Item) + if err != nil { + return nil, fmt.Errorf("failed to deserialize SomeArrayEvent from stackitem (execution #%d, event #%d): %w", i, j, err) + } + res = append(res, event) + } + } + + return res, nil +} + +// FromStackItem converts provided [stackitem.Array] to SomeArrayEvent or +// returns an error if it's not possible to do to so. +func (e *SomeArrayEvent) 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.A, err = func (item stackitem.Item) ([][]*big.Int, error) { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return nil, errors.New("not an array") + } + res := make([][]*big.Int, len(arr)) + for i := range res { + res[i], err = func (item stackitem.Item) ([]*big.Int, error) { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return nil, errors.New("not an array") + } + res := make([]*big.Int, len(arr)) + for i := range res { + res[i], err = arr[i].TryInteger() + if err != nil { + return nil, fmt.Errorf("item %d: %w", i, err) + } + } + return res, nil + } (arr[i]) + if err != nil { + return nil, fmt.Errorf("item %d: %w", i, err) + } + } + return res, nil + } (arr[index]) + if err != nil { + return fmt.Errorf("field A: %w", err) + } + + return nil +} diff --git a/cli/smartcontract/testdata/structs/rpcbindings.out b/cli/smartcontract/testdata/structs/rpcbindings.out index cbe2a6e67..ba503be59 100644 --- a/cli/smartcontract/testdata/structs/rpcbindings.out +++ b/cli/smartcontract/testdata/structs/rpcbindings.out @@ -17,7 +17,6 @@ import ( // Hash contains contract hash. var Hash = util.Uint160{0x33, 0x22, 0x11, 0x0, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, 0x0} - // LedgerBlock is a contract-specific ledger.Block type used by its methods. type LedgerBlock struct { Hash util.Uint256 @@ -154,6 +153,7 @@ type StructsInternal struct { Map map[*big.Int]keys.PublicKeys Struct *StructsInternal } + // Invoker is used by ContractReader to call various safe methods. type Invoker interface { Call(contract util.Uint160, operation string, params ...any) (*result.Invoke, error) @@ -169,7 +169,6 @@ func NewReader(invoker Invoker) *ContractReader { return &ContractReader{invoker} } - // Block invokes `block` method of contract. func (c *ContractReader) Block(b *LedgerBlock) (*LedgerBlock, error) { return itemToLedgerBlock(unwrap.Item(c.invoker.Call(Hash, "block", b))) @@ -195,16 +194,26 @@ func itemToLedgerBlock(item stackitem.Item, err error) (*LedgerBlock, error) { if err != nil { return nil, err } + var res = new(LedgerBlock) + err = res.FromStackItem(item) + return res, err +} + +// FromStackItem retrieves fields of LedgerBlock from the given +// [stackitem.Item] or returns an error if it's not possible to do to so. +func (res *LedgerBlock) FromStackItem(item stackitem.Item) error { arr, ok := item.Value().([]stackitem.Item) if !ok { - return nil, errors.New("not an array") + return errors.New("not an array") } if len(arr) != 9 { - return nil, errors.New("wrong number of structure elements") + return errors.New("wrong number of structure elements") } - var res = new(LedgerBlock) - var index = -1 + var ( + index = -1 + err error + ) index++ res.Hash, err = func (item stackitem.Item) (util.Uint256, error) { b, err := item.TryBytes() @@ -218,13 +227,13 @@ func itemToLedgerBlock(item stackitem.Item, err error) (*LedgerBlock, error) { return u, nil } (arr[index]) if err != nil { - return nil, fmt.Errorf("field Hash: %w", err) + return fmt.Errorf("field Hash: %w", err) } index++ res.Version, err = arr[index].TryInteger() if err != nil { - return nil, fmt.Errorf("field Version: %w", err) + return fmt.Errorf("field Version: %w", err) } index++ @@ -240,7 +249,7 @@ func itemToLedgerBlock(item stackitem.Item, err error) (*LedgerBlock, error) { return u, nil } (arr[index]) if err != nil { - return nil, fmt.Errorf("field PrevHash: %w", err) + return fmt.Errorf("field PrevHash: %w", err) } index++ @@ -256,25 +265,25 @@ func itemToLedgerBlock(item stackitem.Item, err error) (*LedgerBlock, error) { return u, nil } (arr[index]) if err != nil { - return nil, fmt.Errorf("field MerkleRoot: %w", err) + return fmt.Errorf("field MerkleRoot: %w", err) } index++ res.Timestamp, err = arr[index].TryInteger() if err != nil { - return nil, fmt.Errorf("field Timestamp: %w", err) + return fmt.Errorf("field Timestamp: %w", err) } index++ res.Nonce, err = arr[index].TryInteger() if err != nil { - return nil, fmt.Errorf("field Nonce: %w", err) + return fmt.Errorf("field Nonce: %w", err) } index++ res.Index, err = arr[index].TryInteger() if err != nil { - return nil, fmt.Errorf("field Index: %w", err) + return fmt.Errorf("field Index: %w", err) } index++ @@ -290,17 +299,16 @@ func itemToLedgerBlock(item stackitem.Item, err error) (*LedgerBlock, error) { return u, nil } (arr[index]) if err != nil { - return nil, fmt.Errorf("field NextConsensus: %w", err) + return fmt.Errorf("field NextConsensus: %w", err) } index++ res.TransactionsLength, err = arr[index].TryInteger() if err != nil { - return nil, fmt.Errorf("field TransactionsLength: %w", err) + return fmt.Errorf("field TransactionsLength: %w", err) } - - return res, err + return nil } // itemToLedgerBlockSR converts stack item into *LedgerBlockSR. @@ -308,16 +316,26 @@ func itemToLedgerBlockSR(item stackitem.Item, err error) (*LedgerBlockSR, error) if err != nil { return nil, err } + var res = new(LedgerBlockSR) + err = res.FromStackItem(item) + return res, err +} + +// FromStackItem retrieves fields of LedgerBlockSR from the given +// [stackitem.Item] or returns an error if it's not possible to do to so. +func (res *LedgerBlockSR) FromStackItem(item stackitem.Item) error { arr, ok := item.Value().([]stackitem.Item) if !ok { - return nil, errors.New("not an array") + return errors.New("not an array") } if len(arr) != 10 { - return nil, errors.New("wrong number of structure elements") + return errors.New("wrong number of structure elements") } - var res = new(LedgerBlockSR) - var index = -1 + var ( + index = -1 + err error + ) index++ res.Hash, err = func (item stackitem.Item) (util.Uint256, error) { b, err := item.TryBytes() @@ -331,13 +349,13 @@ func itemToLedgerBlockSR(item stackitem.Item, err error) (*LedgerBlockSR, error) return u, nil } (arr[index]) if err != nil { - return nil, fmt.Errorf("field Hash: %w", err) + return fmt.Errorf("field Hash: %w", err) } index++ res.Version, err = arr[index].TryInteger() if err != nil { - return nil, fmt.Errorf("field Version: %w", err) + return fmt.Errorf("field Version: %w", err) } index++ @@ -353,7 +371,7 @@ func itemToLedgerBlockSR(item stackitem.Item, err error) (*LedgerBlockSR, error) return u, nil } (arr[index]) if err != nil { - return nil, fmt.Errorf("field PrevHash: %w", err) + return fmt.Errorf("field PrevHash: %w", err) } index++ @@ -369,25 +387,25 @@ func itemToLedgerBlockSR(item stackitem.Item, err error) (*LedgerBlockSR, error) return u, nil } (arr[index]) if err != nil { - return nil, fmt.Errorf("field MerkleRoot: %w", err) + return fmt.Errorf("field MerkleRoot: %w", err) } index++ res.Timestamp, err = arr[index].TryInteger() if err != nil { - return nil, fmt.Errorf("field Timestamp: %w", err) + return fmt.Errorf("field Timestamp: %w", err) } index++ res.Nonce, err = arr[index].TryInteger() if err != nil { - return nil, fmt.Errorf("field Nonce: %w", err) + return fmt.Errorf("field Nonce: %w", err) } index++ res.Index, err = arr[index].TryInteger() if err != nil { - return nil, fmt.Errorf("field Index: %w", err) + return fmt.Errorf("field Index: %w", err) } index++ @@ -403,13 +421,13 @@ func itemToLedgerBlockSR(item stackitem.Item, err error) (*LedgerBlockSR, error) return u, nil } (arr[index]) if err != nil { - return nil, fmt.Errorf("field NextConsensus: %w", err) + return fmt.Errorf("field NextConsensus: %w", err) } index++ res.TransactionsLength, err = arr[index].TryInteger() if err != nil { - return nil, fmt.Errorf("field TransactionsLength: %w", err) + return fmt.Errorf("field TransactionsLength: %w", err) } index++ @@ -425,11 +443,10 @@ func itemToLedgerBlockSR(item stackitem.Item, err error) (*LedgerBlockSR, error) return u, nil } (arr[index]) if err != nil { - return nil, fmt.Errorf("field PrevStateRoot: %w", err) + return fmt.Errorf("field PrevStateRoot: %w", err) } - - return res, err + return nil } // itemToLedgerTransaction converts stack item into *LedgerTransaction. @@ -437,16 +454,26 @@ func itemToLedgerTransaction(item stackitem.Item, err error) (*LedgerTransaction if err != nil { return nil, err } + var res = new(LedgerTransaction) + err = res.FromStackItem(item) + return res, err +} + +// FromStackItem retrieves fields of LedgerTransaction from the given +// [stackitem.Item] or returns an error if it's not possible to do to so. +func (res *LedgerTransaction) FromStackItem(item stackitem.Item) error { arr, ok := item.Value().([]stackitem.Item) if !ok { - return nil, errors.New("not an array") + return errors.New("not an array") } if len(arr) != 8 { - return nil, errors.New("wrong number of structure elements") + return errors.New("wrong number of structure elements") } - var res = new(LedgerTransaction) - var index = -1 + var ( + index = -1 + err error + ) index++ res.Hash, err = func (item stackitem.Item) (util.Uint256, error) { b, err := item.TryBytes() @@ -460,19 +487,19 @@ func itemToLedgerTransaction(item stackitem.Item, err error) (*LedgerTransaction return u, nil } (arr[index]) if err != nil { - return nil, fmt.Errorf("field Hash: %w", err) + return fmt.Errorf("field Hash: %w", err) } index++ res.Version, err = arr[index].TryInteger() if err != nil { - return nil, fmt.Errorf("field Version: %w", err) + return fmt.Errorf("field Version: %w", err) } index++ res.Nonce, err = arr[index].TryInteger() if err != nil { - return nil, fmt.Errorf("field Nonce: %w", err) + return fmt.Errorf("field Nonce: %w", err) } index++ @@ -488,35 +515,34 @@ func itemToLedgerTransaction(item stackitem.Item, err error) (*LedgerTransaction return u, nil } (arr[index]) if err != nil { - return nil, fmt.Errorf("field Sender: %w", err) + return fmt.Errorf("field Sender: %w", err) } index++ res.SysFee, err = arr[index].TryInteger() if err != nil { - return nil, fmt.Errorf("field SysFee: %w", err) + return fmt.Errorf("field SysFee: %w", err) } index++ res.NetFee, err = arr[index].TryInteger() if err != nil { - return nil, fmt.Errorf("field NetFee: %w", err) + return fmt.Errorf("field NetFee: %w", err) } index++ res.ValidUntilBlock, err = arr[index].TryInteger() if err != nil { - return nil, fmt.Errorf("field ValidUntilBlock: %w", err) + return fmt.Errorf("field ValidUntilBlock: %w", err) } index++ res.Script, err = arr[index].TryBytes() if err != nil { - return nil, fmt.Errorf("field Script: %w", err) + return fmt.Errorf("field Script: %w", err) } - - return res, err + return nil } // itemToLedgerTransactionSigner converts stack item into *LedgerTransactionSigner. @@ -524,16 +550,26 @@ func itemToLedgerTransactionSigner(item stackitem.Item, err error) (*LedgerTrans if err != nil { return nil, err } + var res = new(LedgerTransactionSigner) + err = res.FromStackItem(item) + return res, err +} + +// FromStackItem retrieves fields of LedgerTransactionSigner from the given +// [stackitem.Item] or returns an error if it's not possible to do to so. +func (res *LedgerTransactionSigner) FromStackItem(item stackitem.Item) error { arr, ok := item.Value().([]stackitem.Item) if !ok { - return nil, errors.New("not an array") + return errors.New("not an array") } if len(arr) != 5 { - return nil, errors.New("wrong number of structure elements") + return errors.New("wrong number of structure elements") } - var res = new(LedgerTransactionSigner) - var index = -1 + var ( + index = -1 + err error + ) index++ res.Account, err = func (item stackitem.Item) (util.Uint160, error) { b, err := item.TryBytes() @@ -547,13 +583,13 @@ func itemToLedgerTransactionSigner(item stackitem.Item, err error) (*LedgerTrans return u, nil } (arr[index]) if err != nil { - return nil, fmt.Errorf("field Account: %w", err) + return fmt.Errorf("field Account: %w", err) } index++ res.Scopes, err = arr[index].TryInteger() if err != nil { - return nil, fmt.Errorf("field Scopes: %w", err) + return fmt.Errorf("field Scopes: %w", err) } index++ @@ -582,7 +618,7 @@ func itemToLedgerTransactionSigner(item stackitem.Item, err error) (*LedgerTrans return res, nil } (arr[index]) if err != nil { - return nil, fmt.Errorf("field AllowedContracts: %w", err) + return fmt.Errorf("field AllowedContracts: %w", err) } index++ @@ -611,7 +647,7 @@ func itemToLedgerTransactionSigner(item stackitem.Item, err error) (*LedgerTrans return res, nil } (arr[index]) if err != nil { - return nil, fmt.Errorf("field AllowedGroups: %w", err) + return fmt.Errorf("field AllowedGroups: %w", err) } index++ @@ -630,11 +666,10 @@ func itemToLedgerTransactionSigner(item stackitem.Item, err error) (*LedgerTrans return res, nil } (arr[index]) if err != nil { - return nil, fmt.Errorf("field Rules: %w", err) + return fmt.Errorf("field Rules: %w", err) } - - return res, err + return nil } // itemToLedgerWitnessCondition converts stack item into *LedgerWitnessCondition. @@ -642,30 +677,39 @@ func itemToLedgerWitnessCondition(item stackitem.Item, err error) (*LedgerWitnes if err != nil { return nil, err } + var res = new(LedgerWitnessCondition) + err = res.FromStackItem(item) + return res, err +} + +// FromStackItem retrieves fields of LedgerWitnessCondition from the given +// [stackitem.Item] or returns an error if it's not possible to do to so. +func (res *LedgerWitnessCondition) FromStackItem(item stackitem.Item) error { arr, ok := item.Value().([]stackitem.Item) if !ok { - return nil, errors.New("not an array") + return errors.New("not an array") } if len(arr) != 2 { - return nil, errors.New("wrong number of structure elements") + return errors.New("wrong number of structure elements") } - var res = new(LedgerWitnessCondition) - var index = -1 + var ( + index = -1 + err error + ) index++ res.Type, err = arr[index].TryInteger() if err != nil { - return nil, fmt.Errorf("field Type: %w", err) + return fmt.Errorf("field Type: %w", err) } index++ - res.Value, err = arr[index].Value(), nil + res.Value, err = arr[index].Value(), error(nil) if err != nil { - return nil, fmt.Errorf("field Value: %w", err) + return fmt.Errorf("field Value: %w", err) } - - return res, err + return nil } // itemToLedgerWitnessRule converts stack item into *LedgerWitnessRule. @@ -673,30 +717,39 @@ func itemToLedgerWitnessRule(item stackitem.Item, err error) (*LedgerWitnessRule if err != nil { return nil, err } + var res = new(LedgerWitnessRule) + err = res.FromStackItem(item) + return res, err +} + +// FromStackItem retrieves fields of LedgerWitnessRule from the given +// [stackitem.Item] or returns an error if it's not possible to do to so. +func (res *LedgerWitnessRule) FromStackItem(item stackitem.Item) error { arr, ok := item.Value().([]stackitem.Item) if !ok { - return nil, errors.New("not an array") + return errors.New("not an array") } if len(arr) != 2 { - return nil, errors.New("wrong number of structure elements") + return errors.New("wrong number of structure elements") } - var res = new(LedgerWitnessRule) - var index = -1 + var ( + index = -1 + err error + ) index++ res.Action, err = arr[index].TryInteger() if err != nil { - return nil, fmt.Errorf("field Action: %w", err) + return fmt.Errorf("field Action: %w", err) } index++ res.Condition, err = itemToLedgerWitnessCondition(arr[index], nil) if err != nil { - return nil, fmt.Errorf("field Condition: %w", err) + return fmt.Errorf("field Condition: %w", err) } - - return res, err + return nil } // itemToManagementABI converts stack item into *ManagementABI. @@ -704,16 +757,26 @@ func itemToManagementABI(item stackitem.Item, err error) (*ManagementABI, error) if err != nil { return nil, err } + var res = new(ManagementABI) + err = res.FromStackItem(item) + return res, err +} + +// FromStackItem retrieves fields of ManagementABI from the given +// [stackitem.Item] or returns an error if it's not possible to do to so. +func (res *ManagementABI) FromStackItem(item stackitem.Item) error { arr, ok := item.Value().([]stackitem.Item) if !ok { - return nil, errors.New("not an array") + return errors.New("not an array") } if len(arr) != 2 { - return nil, errors.New("wrong number of structure elements") + return errors.New("wrong number of structure elements") } - var res = new(ManagementABI) - var index = -1 + var ( + index = -1 + err error + ) index++ res.Methods, err = func (item stackitem.Item) ([]*ManagementMethod, error) { arr, ok := item.Value().([]stackitem.Item) @@ -730,7 +793,7 @@ func itemToManagementABI(item stackitem.Item, err error) (*ManagementABI, error) return res, nil } (arr[index]) if err != nil { - return nil, fmt.Errorf("field Methods: %w", err) + return fmt.Errorf("field Methods: %w", err) } index++ @@ -749,11 +812,10 @@ func itemToManagementABI(item stackitem.Item, err error) (*ManagementABI, error) return res, nil } (arr[index]) if err != nil { - return nil, fmt.Errorf("field Events: %w", err) + return fmt.Errorf("field Events: %w", err) } - - return res, err + return nil } // itemToManagementContract converts stack item into *ManagementContract. @@ -761,26 +823,36 @@ func itemToManagementContract(item stackitem.Item, err error) (*ManagementContra if err != nil { return nil, err } + var res = new(ManagementContract) + err = res.FromStackItem(item) + return res, err +} + +// FromStackItem retrieves fields of ManagementContract from the given +// [stackitem.Item] or returns an error if it's not possible to do to so. +func (res *ManagementContract) FromStackItem(item stackitem.Item) error { arr, ok := item.Value().([]stackitem.Item) if !ok { - return nil, errors.New("not an array") + return errors.New("not an array") } if len(arr) != 5 { - return nil, errors.New("wrong number of structure elements") + return errors.New("wrong number of structure elements") } - var res = new(ManagementContract) - var index = -1 + var ( + index = -1 + err error + ) index++ res.ID, err = arr[index].TryInteger() if err != nil { - return nil, fmt.Errorf("field ID: %w", err) + return fmt.Errorf("field ID: %w", err) } index++ res.UpdateCounter, err = arr[index].TryInteger() if err != nil { - return nil, fmt.Errorf("field UpdateCounter: %w", err) + return fmt.Errorf("field UpdateCounter: %w", err) } index++ @@ -796,23 +868,22 @@ func itemToManagementContract(item stackitem.Item, err error) (*ManagementContra return u, nil } (arr[index]) if err != nil { - return nil, fmt.Errorf("field Hash: %w", err) + return fmt.Errorf("field Hash: %w", err) } index++ res.NEF, err = arr[index].TryBytes() if err != nil { - return nil, fmt.Errorf("field NEF: %w", err) + return fmt.Errorf("field NEF: %w", err) } index++ res.Manifest, err = itemToManagementManifest(arr[index], nil) if err != nil { - return nil, fmt.Errorf("field Manifest: %w", err) + return fmt.Errorf("field Manifest: %w", err) } - - return res, err + return nil } // itemToManagementEvent converts stack item into *ManagementEvent. @@ -820,16 +891,26 @@ func itemToManagementEvent(item stackitem.Item, err error) (*ManagementEvent, er if err != nil { return nil, err } + var res = new(ManagementEvent) + err = res.FromStackItem(item) + return res, err +} + +// FromStackItem retrieves fields of ManagementEvent from the given +// [stackitem.Item] or returns an error if it's not possible to do to so. +func (res *ManagementEvent) FromStackItem(item stackitem.Item) error { arr, ok := item.Value().([]stackitem.Item) if !ok { - return nil, errors.New("not an array") + return errors.New("not an array") } if len(arr) != 2 { - return nil, errors.New("wrong number of structure elements") + return errors.New("wrong number of structure elements") } - var res = new(ManagementEvent) - var index = -1 + var ( + index = -1 + err error + ) index++ res.Name, err = func (item stackitem.Item) (string, error) { b, err := item.TryBytes() @@ -842,7 +923,7 @@ func itemToManagementEvent(item stackitem.Item, err error) (*ManagementEvent, er return string(b), nil } (arr[index]) if err != nil { - return nil, fmt.Errorf("field Name: %w", err) + return fmt.Errorf("field Name: %w", err) } index++ @@ -861,11 +942,10 @@ func itemToManagementEvent(item stackitem.Item, err error) (*ManagementEvent, er return res, nil } (arr[index]) if err != nil { - return nil, fmt.Errorf("field Params: %w", err) + return fmt.Errorf("field Params: %w", err) } - - return res, err + return nil } // itemToManagementGroup converts stack item into *ManagementGroup. @@ -873,16 +953,26 @@ func itemToManagementGroup(item stackitem.Item, err error) (*ManagementGroup, er if err != nil { return nil, err } + var res = new(ManagementGroup) + err = res.FromStackItem(item) + return res, err +} + +// FromStackItem retrieves fields of ManagementGroup from the given +// [stackitem.Item] or returns an error if it's not possible to do to so. +func (res *ManagementGroup) FromStackItem(item stackitem.Item) error { arr, ok := item.Value().([]stackitem.Item) if !ok { - return nil, errors.New("not an array") + return errors.New("not an array") } if len(arr) != 2 { - return nil, errors.New("wrong number of structure elements") + return errors.New("wrong number of structure elements") } - var res = new(ManagementGroup) - var index = -1 + var ( + index = -1 + err error + ) index++ res.PublicKey, err = func (item stackitem.Item) (*keys.PublicKey, error) { b, err := item.TryBytes() @@ -896,17 +986,16 @@ func itemToManagementGroup(item stackitem.Item, err error) (*ManagementGroup, er return k, nil } (arr[index]) if err != nil { - return nil, fmt.Errorf("field PublicKey: %w", err) + return fmt.Errorf("field PublicKey: %w", err) } index++ res.Signature, err = arr[index].TryBytes() if err != nil { - return nil, fmt.Errorf("field Signature: %w", err) + return fmt.Errorf("field Signature: %w", err) } - - return res, err + return nil } // itemToManagementManifest converts stack item into *ManagementManifest. @@ -914,16 +1003,26 @@ func itemToManagementManifest(item stackitem.Item, err error) (*ManagementManife if err != nil { return nil, err } + var res = new(ManagementManifest) + err = res.FromStackItem(item) + return res, err +} + +// FromStackItem retrieves fields of ManagementManifest from the given +// [stackitem.Item] or returns an error if it's not possible to do to so. +func (res *ManagementManifest) FromStackItem(item stackitem.Item) error { arr, ok := item.Value().([]stackitem.Item) if !ok { - return nil, errors.New("not an array") + return errors.New("not an array") } if len(arr) != 8 { - return nil, errors.New("wrong number of structure elements") + return errors.New("wrong number of structure elements") } - var res = new(ManagementManifest) - var index = -1 + var ( + index = -1 + err error + ) index++ res.Name, err = func (item stackitem.Item) (string, error) { b, err := item.TryBytes() @@ -936,7 +1035,7 @@ func itemToManagementManifest(item stackitem.Item, err error) (*ManagementManife return string(b), nil } (arr[index]) if err != nil { - return nil, fmt.Errorf("field Name: %w", err) + return fmt.Errorf("field Name: %w", err) } index++ @@ -955,7 +1054,7 @@ func itemToManagementManifest(item stackitem.Item, err error) (*ManagementManife return res, nil } (arr[index]) if err != nil { - return nil, fmt.Errorf("field Groups: %w", err) + return fmt.Errorf("field Groups: %w", err) } index++ @@ -997,7 +1096,7 @@ func itemToManagementManifest(item stackitem.Item, err error) (*ManagementManife return res, nil } (arr[index]) if err != nil { - return nil, fmt.Errorf("field Features: %w", err) + return fmt.Errorf("field Features: %w", err) } index++ @@ -1025,13 +1124,13 @@ func itemToManagementManifest(item stackitem.Item, err error) (*ManagementManife return res, nil } (arr[index]) if err != nil { - return nil, fmt.Errorf("field SupportedStandards: %w", err) + return fmt.Errorf("field SupportedStandards: %w", err) } index++ res.ABI, err = itemToManagementABI(arr[index], nil) if err != nil { - return nil, fmt.Errorf("field ABI: %w", err) + return fmt.Errorf("field ABI: %w", err) } index++ @@ -1050,7 +1149,7 @@ func itemToManagementManifest(item stackitem.Item, err error) (*ManagementManife return res, nil } (arr[index]) if err != nil { - return nil, fmt.Errorf("field Permissions: %w", err) + return fmt.Errorf("field Permissions: %w", err) } index++ @@ -1079,17 +1178,16 @@ func itemToManagementManifest(item stackitem.Item, err error) (*ManagementManife return res, nil } (arr[index]) if err != nil { - return nil, fmt.Errorf("field Trusts: %w", err) + return fmt.Errorf("field Trusts: %w", err) } index++ - res.Extra, err = arr[index].Value(), nil + res.Extra, err = arr[index].Value(), error(nil) if err != nil { - return nil, fmt.Errorf("field Extra: %w", err) + return fmt.Errorf("field Extra: %w", err) } - - return res, err + return nil } // itemToManagementMethod converts stack item into *ManagementMethod. @@ -1097,16 +1195,26 @@ func itemToManagementMethod(item stackitem.Item, err error) (*ManagementMethod, if err != nil { return nil, err } + var res = new(ManagementMethod) + err = res.FromStackItem(item) + return res, err +} + +// FromStackItem retrieves fields of ManagementMethod from the given +// [stackitem.Item] or returns an error if it's not possible to do to so. +func (res *ManagementMethod) FromStackItem(item stackitem.Item) error { arr, ok := item.Value().([]stackitem.Item) if !ok { - return nil, errors.New("not an array") + return errors.New("not an array") } if len(arr) != 5 { - return nil, errors.New("wrong number of structure elements") + return errors.New("wrong number of structure elements") } - var res = new(ManagementMethod) - var index = -1 + var ( + index = -1 + err error + ) index++ res.Name, err = func (item stackitem.Item) (string, error) { b, err := item.TryBytes() @@ -1119,7 +1227,7 @@ func itemToManagementMethod(item stackitem.Item, err error) (*ManagementMethod, return string(b), nil } (arr[index]) if err != nil { - return nil, fmt.Errorf("field Name: %w", err) + return fmt.Errorf("field Name: %w", err) } index++ @@ -1138,29 +1246,28 @@ func itemToManagementMethod(item stackitem.Item, err error) (*ManagementMethod, return res, nil } (arr[index]) if err != nil { - return nil, fmt.Errorf("field Params: %w", err) + return fmt.Errorf("field Params: %w", err) } index++ res.ReturnType, err = arr[index].TryInteger() if err != nil { - return nil, fmt.Errorf("field ReturnType: %w", err) + return fmt.Errorf("field ReturnType: %w", err) } index++ res.Offset, err = arr[index].TryInteger() if err != nil { - return nil, fmt.Errorf("field Offset: %w", err) + return fmt.Errorf("field Offset: %w", err) } index++ res.Safe, err = arr[index].TryBool() if err != nil { - return nil, fmt.Errorf("field Safe: %w", err) + return fmt.Errorf("field Safe: %w", err) } - - return res, err + return nil } // itemToManagementParameter converts stack item into *ManagementParameter. @@ -1168,16 +1275,26 @@ func itemToManagementParameter(item stackitem.Item, err error) (*ManagementParam if err != nil { return nil, err } + var res = new(ManagementParameter) + err = res.FromStackItem(item) + return res, err +} + +// FromStackItem retrieves fields of ManagementParameter from the given +// [stackitem.Item] or returns an error if it's not possible to do to so. +func (res *ManagementParameter) FromStackItem(item stackitem.Item) error { arr, ok := item.Value().([]stackitem.Item) if !ok { - return nil, errors.New("not an array") + return errors.New("not an array") } if len(arr) != 2 { - return nil, errors.New("wrong number of structure elements") + return errors.New("wrong number of structure elements") } - var res = new(ManagementParameter) - var index = -1 + var ( + index = -1 + err error + ) index++ res.Name, err = func (item stackitem.Item) (string, error) { b, err := item.TryBytes() @@ -1190,17 +1307,16 @@ func itemToManagementParameter(item stackitem.Item, err error) (*ManagementParam return string(b), nil } (arr[index]) if err != nil { - return nil, fmt.Errorf("field Name: %w", err) + return fmt.Errorf("field Name: %w", err) } index++ res.Type, err = arr[index].TryInteger() if err != nil { - return nil, fmt.Errorf("field Type: %w", err) + return fmt.Errorf("field Type: %w", err) } - - return res, err + return nil } // itemToManagementPermission converts stack item into *ManagementPermission. @@ -1208,16 +1324,26 @@ func itemToManagementPermission(item stackitem.Item, err error) (*ManagementPerm if err != nil { return nil, err } + var res = new(ManagementPermission) + err = res.FromStackItem(item) + return res, err +} + +// FromStackItem retrieves fields of ManagementPermission from the given +// [stackitem.Item] or returns an error if it's not possible to do to so. +func (res *ManagementPermission) FromStackItem(item stackitem.Item) error { arr, ok := item.Value().([]stackitem.Item) if !ok { - return nil, errors.New("not an array") + return errors.New("not an array") } if len(arr) != 2 { - return nil, errors.New("wrong number of structure elements") + return errors.New("wrong number of structure elements") } - var res = new(ManagementPermission) - var index = -1 + var ( + index = -1 + err error + ) index++ res.Contract, err = func (item stackitem.Item) (util.Uint160, error) { b, err := item.TryBytes() @@ -1231,7 +1357,7 @@ func itemToManagementPermission(item stackitem.Item, err error) (*ManagementPerm return u, nil } (arr[index]) if err != nil { - return nil, fmt.Errorf("field Contract: %w", err) + return fmt.Errorf("field Contract: %w", err) } index++ @@ -1259,11 +1385,10 @@ func itemToManagementPermission(item stackitem.Item, err error) (*ManagementPerm return res, nil } (arr[index]) if err != nil { - return nil, fmt.Errorf("field Methods: %w", err) + return fmt.Errorf("field Methods: %w", err) } - - return res, err + return nil } // itemToStructsInternal converts stack item into *StructsInternal. @@ -1271,32 +1396,42 @@ func itemToStructsInternal(item stackitem.Item, err error) (*StructsInternal, er if err != nil { return nil, err } + var res = new(StructsInternal) + err = res.FromStackItem(item) + return res, err +} + +// FromStackItem retrieves fields of StructsInternal from the given +// [stackitem.Item] or returns an error if it's not possible to do to so. +func (res *StructsInternal) FromStackItem(item stackitem.Item) error { arr, ok := item.Value().([]stackitem.Item) if !ok { - return nil, errors.New("not an array") + return errors.New("not an array") } if len(arr) != 13 { - return nil, errors.New("wrong number of structure elements") + return errors.New("wrong number of structure elements") } - var res = new(StructsInternal) - var index = -1 + var ( + index = -1 + err error + ) index++ res.Bool, err = arr[index].TryBool() if err != nil { - return nil, fmt.Errorf("field Bool: %w", err) + return fmt.Errorf("field Bool: %w", err) } index++ res.Int, err = arr[index].TryInteger() if err != nil { - return nil, fmt.Errorf("field Int: %w", err) + return fmt.Errorf("field Int: %w", err) } index++ res.Bytes, err = arr[index].TryBytes() if err != nil { - return nil, fmt.Errorf("field Bytes: %w", err) + return fmt.Errorf("field Bytes: %w", err) } index++ @@ -1311,7 +1446,7 @@ func itemToStructsInternal(item stackitem.Item, err error) (*StructsInternal, er return string(b), nil } (arr[index]) if err != nil { - return nil, fmt.Errorf("field String: %w", err) + return fmt.Errorf("field String: %w", err) } index++ @@ -1327,7 +1462,7 @@ func itemToStructsInternal(item stackitem.Item, err error) (*StructsInternal, er return u, nil } (arr[index]) if err != nil { - return nil, fmt.Errorf("field H160: %w", err) + return fmt.Errorf("field H160: %w", err) } index++ @@ -1343,7 +1478,7 @@ func itemToStructsInternal(item stackitem.Item, err error) (*StructsInternal, er return u, nil } (arr[index]) if err != nil { - return nil, fmt.Errorf("field H256: %w", err) + return fmt.Errorf("field H256: %w", err) } index++ @@ -1359,7 +1494,7 @@ func itemToStructsInternal(item stackitem.Item, err error) (*StructsInternal, er return k, nil } (arr[index]) if err != nil { - return nil, fmt.Errorf("field PK: %w", err) + return fmt.Errorf("field PK: %w", err) } index++ @@ -1375,13 +1510,13 @@ func itemToStructsInternal(item stackitem.Item, err error) (*StructsInternal, er return k, nil } (arr[index]) if err != nil { - return nil, fmt.Errorf("field PubKey: %w", err) + return fmt.Errorf("field PubKey: %w", err) } index++ res.Sign, err = arr[index].TryBytes() if err != nil { - return nil, fmt.Errorf("field Sign: %w", err) + return fmt.Errorf("field Sign: %w", err) } index++ @@ -1400,7 +1535,7 @@ func itemToStructsInternal(item stackitem.Item, err error) (*StructsInternal, er return res, nil } (arr[index]) if err != nil { - return nil, fmt.Errorf("field ArrOfBytes: %w", err) + return fmt.Errorf("field ArrOfBytes: %w", err) } index++ @@ -1429,7 +1564,7 @@ func itemToStructsInternal(item stackitem.Item, err error) (*StructsInternal, er return res, nil } (arr[index]) if err != nil { - return nil, fmt.Errorf("field ArrOfH160: %w", err) + return fmt.Errorf("field ArrOfH160: %w", err) } index++ @@ -1476,15 +1611,14 @@ func itemToStructsInternal(item stackitem.Item, err error) (*StructsInternal, er return res, nil } (arr[index]) if err != nil { - return nil, fmt.Errorf("field Map: %w", err) + return fmt.Errorf("field Map: %w", err) } index++ res.Struct, err = itemToStructsInternal(arr[index], nil) if err != nil { - return nil, fmt.Errorf("field Struct: %w", err) + return fmt.Errorf("field Struct: %w", err) } - - return res, 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 e6a14ecea..49f6ac7e5 100644 --- a/cli/smartcontract/testdata/types/rpcbindings.out +++ b/cli/smartcontract/testdata/types/rpcbindings.out @@ -31,7 +31,6 @@ func NewReader(invoker Invoker) *ContractReader { return &ContractReader{invoker} } - // AAAStrings invokes `aAAStrings` method of contract. func (c *ContractReader) AAAStrings(s [][][]string) ([][][]string, error) { return func (item stackitem.Item, err error) ([][][]string, error) { @@ -96,10 +95,38 @@ func (c *ContractReader) Any(a any) (any, error) { if err != nil { return nil, err } - return item.Value(), nil + return item.Value(), error(nil) } (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/cli/smartcontract/testdata/verifyrpc/verify.go b/cli/smartcontract/testdata/verifyrpc/verify.go index 4a9af0156..3a01b990c 100644 --- a/cli/smartcontract/testdata/verifyrpc/verify.go +++ b/cli/smartcontract/testdata/verifyrpc/verify.go @@ -2,14 +2,23 @@ package verify import ( + "errors" + "fmt" "github.com/nspcc-dev/neo-go/pkg/core/transaction" + "github.com/nspcc-dev/neo-go/pkg/neorpc/result" "github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" ) // Hash contains contract hash. var Hash = util.Uint160{0x33, 0x22, 0x11, 0x0, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, 0x0} +// HelloWorldEvent represents "Hello world!" event emitted by the contract. +type HelloWorldEvent struct { + Args []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) @@ -30,7 +39,6 @@ func New(actor Actor) *Contract { return &Contract{actor} } - func scriptForVerify() ([]byte, error) { return smartcontract.CreateCallWithAssertScript(Hash, "verify") } @@ -68,3 +76,68 @@ func (c *Contract) VerifyUnsigned() (*transaction.Transaction, error) { } return c.actor.MakeUnsignedRun(script, nil) } + +// HelloWorldEventsFromApplicationLog retrieves a set of all emitted events +// with "Hello world!" name from the provided [result.ApplicationLog]. +func HelloWorldEventsFromApplicationLog(log *result.ApplicationLog) ([]*HelloWorldEvent, error) { + if log == nil { + return nil, errors.New("nil application log") + } + + var res []*HelloWorldEvent + for i, ex := range log.Executions { + for j, e := range ex.Events { + if e.Name != "Hello world!" { + continue + } + event := new(HelloWorldEvent) + err := event.FromStackItem(e.Item) + if err != nil { + return nil, fmt.Errorf("failed to deserialize HelloWorldEvent from stackitem (execution #%d, event #%d): %w", i, j, err) + } + res = append(res, event) + } + } + + return res, nil +} + +// FromStackItem converts provided [stackitem.Array] to HelloWorldEvent or +// returns an error if it's not possible to do to so. +func (e *HelloWorldEvent) 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.Args, 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 Args: %w", err) + } + + return nil +} diff --git a/docs/compiler.md b/docs/compiler.md index 0279d2196..118b94d92 100644 --- a/docs/compiler.md +++ b/docs/compiler.md @@ -472,10 +472,113 @@ and structures. Notice that structured types returned by methods can't be Null at the moment (see #2795). ``` -$ ./bin/neo-go contract compile -i contract.go --config contract.yml -o contract.nef --manifest manifest.json --bindings contract.bindings.yml +$ ./bin/neo-go contract compile -i contract.go --config contract.yml -o contract.nef --manifest manifest.json --bindings contract.bindings.yml --guess-eventtypes $ ./bin/neo-go contract generate-rpcwrapper --manifest manifest.json --config contract.bindings.yml --out rpcwrapper.go --hash 0x1b4357bff5a01bdf2a6581247cf9ed1e24629176 ``` +Contract-specific RPC-bindings generated by "generate-rpcwrapper" command include +structure wrappers for each event declared in the contract manifest as far as the +set of helpers that allow to retrieve emitted event from the application log or +from stackitem. By default, event wrappers builder use event structure that was +described in the manifest. Since the type data available in the manifest is +limited, in some cases the resulting generated event structure may use generic +go types. Go contracts can make use of additional type data from bindings +configuration file generated during compilation. Like for any other contract +types, this can cover arrays, maps and structures. To reach the maximum +resemblance between the emitted events and the generated event wrappers, we +recommend either to fill in the extended events type information in the contract +configuration file before the compilation or to use `--guess-eventtypes` +compilation option. + +If using `--guess-eventtypes` compilation option, event parameter types will be +guessed from the arguments of `runtime.Notify` calls for each emitted event. If +multiple calls of `runtime.Notify` are found, then argument types will be checked +for matching (guessed types must be the same across the particular event usages). +After that, the extended types binding configuration will be generated according +to the emitted events parameter types. `--guess-eventtypes` compilation option +is able to recognize those events that has a constant name known at a compilation +time and do not include variadic arguments usage. Thus, use this option if your +contract suites these requirements. Otherwise, we recommend to manually specify +extended event parameter types information in the contract configuration file. + +Extended event parameter type information can be provided manually via contract +configuration file under the `events` section. Each event parameter specified in +this section may be supplied with additional parameter type information specified +under `extendedtype` subsection. The extended type information (`ExtendedType`) +has the following structure: + +| Field | Type | Required | Meaning | +|-------------|---------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------| +| `base` | Any valid [NEP-14 parameter type](https://github.com/neo-project/proposals/blob/master/nep-14.mediawiki#parametertype) except `Void`. | Always required. | The base type of a parameter, e.g. `Array` for go structures and any nested arrays, `Map` for nested maps, `Hash160` for 160-bits integers, etc. | +| `name` | `string` | Required for structures, omitted for arrays, interfaces and maps. | Name of a structure that will be used in the resulting RPC binding. | +| `interface` | `string` | Required for `InteropInterface`-based types, currently `iterator` only is supported. | Underlying value of the `InteropInterface`. | +| `key` | Any simple [NEP-14 parameter type](https://github.com/neo-project/proposals/blob/master/nep-14.mediawiki#parametertype). | Required for `Map`-based types. | Key type for maps. | +| `value` | `ExtendedType`. | Required for iterators, arrays and maps. | Value type of iterators, arrays and maps. | +| `fields` | Array of `FieldExtendedType`. | Required for structures. | Ordered type data for structure fields. | + +The structure's field extended information (`FieldExtendedType`) has the following structure: + +| Field | Type | Required | Meaning | +|------------------------|----------------|------------------|-----------------------------------------------------------------------------| +| `field` | `string` | Always required. | Name of the structure field that will be used in the resulting RPC binding. | +| Inlined `ExtendedType` | `ExtendedType` | Always required. | The extended type information about structure field. | + + +Any named structures used in the `ExtendedType` description must be manually +specified in the contract configuration file under top-level `namedtypes` section +in the form of `map[string]ExtendedType`, where the map key is a name of the +described named structure that matches the one provided in the `name` field of +the event parameter's extended type. + +Here's the example of manually-created contract configuration file that uses +extended types for event parameters description: + +``` +name: "HelloWorld contract" +supportedstandards: [] +events: + - name: Some simple notification + parameters: + - name: intP + type: Integer + - name: boolP + type: Boolean + - name: stringP + type: String + - name: Structure notification + parameters: + - name: structure parameter + type: Array + extendedtype: + base: Array + name: transferData + - name: Map of structures notification + parameters: + - name: map parameter + type: Map + extendedtype: + base: Map + key: Integer + value: + base: Array + name: transferData + - name: Iterator notification + parameters: + - name: data + type: InteropInterface + extendedtype: + base: InteropInterface + interface: iterator +namedtypes: + transferData: + base: Array + fields: + - field: IntField + base: Integer + - field: BoolField + base: Boolean +``` + ## Smart contract examples Some examples are provided in the [examples directory](../examples). For more 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/internal/testchain/transaction.go b/internal/testchain/transaction.go index 630902ac8..cce8dd49c 100644 --- a/internal/testchain/transaction.go +++ b/internal/testchain/transaction.go @@ -76,6 +76,7 @@ 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 14793c8ad..4ee702626 100644 --- a/pkg/compiler/codegen.go +++ b/pkg/compiler/codegen.go @@ -110,7 +110,7 @@ type codegen struct { docIndex map[string]int // emittedEvents contains all events emitted by the contract. - emittedEvents map[string][][]string + emittedEvents map[string][]EmittedEventInfo // invokedContracts contains invoked methods of other contracts. invokedContracts map[util.Uint160][]string @@ -2269,7 +2269,7 @@ func newCodegen(info *buildInfo, pkg *packages.Package) *codegen { initEndOffset: -1, deployEndOffset: -1, - emittedEvents: make(map[string][][]string), + emittedEvents: make(map[string][]EmittedEventInfo), invokedContracts: make(map[util.Uint160][]string), sequencePoints: make(map[string][]DebugSeqPoint), } diff --git a/pkg/compiler/compiler.go b/pkg/compiler/compiler.go index 60a45525c..a7826a552 100644 --- a/pkg/compiler/compiler.go +++ b/pkg/compiler/compiler.go @@ -18,6 +18,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest/standard" "github.com/nspcc-dev/neo-go/pkg/smartcontract/nef" + "github.com/nspcc-dev/neo-go/pkg/smartcontract/rpcbinding" "github.com/nspcc-dev/neo-go/pkg/util" "golang.org/x/tools/go/packages" "gopkg.in/yaml.v3" @@ -52,14 +53,26 @@ 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 + // 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 + // occurrence of event call. + GuessEventTypes bool + // Name is a contract's name to be written to manifest. Name string // SourceURL is a contract's source URL to be written to manifest. SourceURL string - // Runtime notifications. - ContractEvents []manifest.Event + // 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 // The list of standards supported by the contract. ContractSupportedStandards []string @@ -78,6 +91,23 @@ 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 @@ -309,6 +339,78 @@ func CompileAndSave(src string, o *Options) ([]byte, error) { 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 { + pStructName := rpcbinding.ToParameterBindingName(p.Name) + if p.ExtendedType != nil { + pName := eStructName + "." + pStructName + cfg.Types[pName] = *p.ExtendedType + } + } + } + if o.GuessEventTypes { + if len(di.EmittedEvents) > 0 { + for eventName, eventUsages := range di.EmittedEvents { + var manifestEvent HybridEvent + for _, e := range o.ContractEvents { + if e.Name == eventName { + manifestEvent = e + break + } + } + if len(manifestEvent.Name) == 0 { + return nil, fmt.Errorf("inconsistent usages of event `%s`: not declared in the contract config", eventName) + } + exampleUsage := eventUsages[0] + for _, usage := range eventUsages { + if len(usage.Params) != len(manifestEvent.Parameters) { + return nil, fmt.Errorf("inconsistent usages of event `%s` against config: number of params mismatch: %d vs %d", eventName, len(exampleUsage.Params), len(manifestEvent.Parameters)) + } + for i, actual := range usage.Params { + mParam := manifestEvent.Parameters[i] + // TODO: see the TestCompile_GuessEventTypes, "SC parameter type mismatch" section, + // do we want to compare with actual.RealType? The conversion code is emitted by the + // compiler for it, so we expect the parameter to be of the proper type. + if !(mParam.Type == smartcontract.AnyType || actual.TypeSC == mParam.Type) { + return nil, fmt.Errorf("inconsistent usages of event `%s` against config: SC type of param #%d mismatch: %s vs %s", eventName, i, actual.TypeSC, mParam.Type) + } + expected := exampleUsage.Params[i] + if !actual.ExtendedType.Equals(expected.ExtendedType) { + return nil, fmt.Errorf("inconsistent usages of event `%s`: extended type of param #%d mismatch", eventName, i) + } + } + } + eBindingName := rpcbinding.ToEventBindingName(eventName) + for typeName, extType := range exampleUsage.ExtTypes { + if _, ok := cfg.NamedTypes[typeName]; !ok { + cfg.NamedTypes[typeName] = extType + } + } + + for _, p := range exampleUsage.Params { + pBindingName := rpcbinding.ToParameterBindingName(p.Name) + pname := eBindingName + "." + pBindingName + if p.RealType.TypeName != "" { + if _, ok := cfg.Overrides[pname]; !ok { + cfg.Overrides[pname] = p.RealType + } + } + if p.ExtendedType != nil { + if _, ok := cfg.Types[pname]; !ok { + cfg.Types[pname] = *p.ExtendedType + } + } + } + } + } + } data, err := yaml.Marshal(&cfg) if err != nil { return nil, fmt.Errorf("can't marshal bindings configuration: %w", err) @@ -366,24 +468,23 @@ func CreateManifest(di *DebugInfo, o *Options) (*manifest.Manifest, error) { } if !o.NoEventsCheck { for name := range di.EmittedEvents { - ev := m.ABI.GetEvent(name) - if ev == nil { + expected := m.ABI.GetEvent(name) + if expected == nil { return nil, fmt.Errorf("event '%s' is emitted but not specified in manifest", name) } - argsList := di.EmittedEvents[name] - for i := range argsList { - if len(argsList[i]) != len(ev.Parameters) { + for _, emitted := range di.EmittedEvents[name] { + if len(emitted.Params) != len(expected.Parameters) { return nil, fmt.Errorf("event '%s' should have %d parameters but has %d", - name, len(ev.Parameters), len(argsList[i])) + name, len(expected.Parameters), len(emitted.Params)) } - for j := range ev.Parameters { - if ev.Parameters[j].Type == smartcontract.AnyType { + for j := range expected.Parameters { + if expected.Parameters[j].Type == smartcontract.AnyType { continue } - expected := ev.Parameters[j].Type.String() - if argsList[i][j] != expected { + expectedT := expected.Parameters[j].Type + if emitted.Params[j].TypeSC != expectedT { return nil, fmt.Errorf("event '%s' should have '%s' as type of %d parameter, "+ - "got: %s", name, expected, j+1, argsList[i][j]) + "got: %s", name, expectedT, j+1, emitted.Params[j].TypeSC) } } } diff --git a/pkg/compiler/compiler_test.go b/pkg/compiler/compiler_test.go index 8a9ab207a..288aaf3c7 100644 --- a/pkg/compiler/compiler_test.go +++ b/pkg/compiler/compiler_test.go @@ -159,16 +159,16 @@ func TestEventWarnings(t *testing.T) { }) t.Run("wrong parameter number", func(t *testing.T) { _, err = compiler.CreateManifest(di, &compiler.Options{ - ContractEvents: []manifest.Event{{Name: "Event"}}, + ContractEvents: []compiler.HybridEvent{{Name: "Event"}}, Name: "payable", }) require.Error(t, err) }) t.Run("wrong parameter type", func(t *testing.T) { _, err = compiler.CreateManifest(di, &compiler.Options{ - ContractEvents: []manifest.Event{{ + ContractEvents: []compiler.HybridEvent{{ Name: "Event", - Parameters: []manifest.Parameter{manifest.NewParameter("number", smartcontract.StringType)}, + Parameters: []compiler.HybridParameter{{Parameter: manifest.NewParameter("number", smartcontract.StringType)}}, }}, Name: "payable", }) @@ -176,9 +176,9 @@ func TestEventWarnings(t *testing.T) { }) t.Run("any parameter type", func(t *testing.T) { _, err = compiler.CreateManifest(di, &compiler.Options{ - ContractEvents: []manifest.Event{{ + ContractEvents: []compiler.HybridEvent{{ Name: "Event", - Parameters: []manifest.Parameter{manifest.NewParameter("number", smartcontract.AnyType)}, + Parameters: []compiler.HybridParameter{{Parameter: manifest.NewParameter("number", smartcontract.AnyType)}}, }}, Name: "payable", }) @@ -186,9 +186,9 @@ func TestEventWarnings(t *testing.T) { }) t.Run("good", func(t *testing.T) { _, err = compiler.CreateManifest(di, &compiler.Options{ - ContractEvents: []manifest.Event{{ + ContractEvents: []compiler.HybridEvent{{ Name: "Event", - Parameters: []manifest.Parameter{manifest.NewParameter("number", smartcontract.IntegerType)}, + Parameters: []compiler.HybridParameter{{Parameter: manifest.NewParameter("number", smartcontract.IntegerType)}}, }}, Name: "payable", }) @@ -224,7 +224,7 @@ func TestEventWarnings(t *testing.T) { require.Error(t, err) _, err = compiler.CreateManifest(di, &compiler.Options{ - ContractEvents: []manifest.Event{{Name: "Event"}}, + ContractEvents: []compiler.HybridEvent{{Name: "Event"}}, Name: "eventTest", }) require.NoError(t, err) @@ -242,9 +242,9 @@ func TestEventWarnings(t *testing.T) { _, err = compiler.CreateManifest(di, &compiler.Options{ Name: "eventTest", - ContractEvents: []manifest.Event{{ + ContractEvents: []compiler.HybridEvent{{ Name: "Event", - Parameters: []manifest.Parameter{manifest.NewParameter("number", smartcontract.IntegerType)}, + Parameters: []compiler.HybridParameter{{Parameter: manifest.NewParameter("number", smartcontract.IntegerType)}}, }}, }) require.NoError(t, err) @@ -260,7 +260,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: []manifest.Event{{Name: "Event"}}}) + &compiler.Options{ContractEvents: []compiler.HybridEvent{{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 82e2e49f7..a583fc3bd 100644 --- a/pkg/compiler/debug.go +++ b/pkg/compiler/debug.go @@ -29,9 +29,14 @@ type DebugInfo struct { // 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:"-"` - Events []EventDebugInfo `json:"events"` - // EmittedEvents contains events occurring in code. - EmittedEvents map[string][][]string `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"` + // EmittedEvents contains events occurring in code, i.e. events emitted + // via runtime.Notify(...) call in the contract code if they have constant + // names and doesn't have ellipsis arguments. EmittedEvents are not related + // to the debug info and are aimed to serve bindings generation. + EmittedEvents map[string][]EmittedEventInfo `json:"-"` // InvokedContracts contains foreign contract invocations. InvokedContracts map[util.Uint160][]string `json:"-"` // StaticVariables contains a list of static variable names and types. @@ -103,7 +108,7 @@ type DebugRange struct { End uint16 } -// DebugParam represents the variables's name and type. +// DebugParam represents the variable's name and type. type DebugParam struct { Name string `json:"name"` Type string `json:"type"` @@ -112,6 +117,14 @@ type DebugParam struct { TypeSC smartcontract.ParamType `json:"-"` } +// EmittedEventInfo describes information about single emitted event got from +// 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 + Params []DebugParam +} + func (c *codegen) saveSequencePoint(n ast.Node) { name := "init" if c.scope != nil { @@ -373,10 +386,12 @@ func (c *codegen) scAndVMTypeFromType(t types.Type, exts map[string]binding.Exte over.TypeName = "map[" + t.Key().String() + "]" + over.TypeName return smartcontract.MapType, stackitem.MapT, over, et case *types.Struct: + var extName string if isNamed { over.Package = named.Obj().Pkg().Path() over.TypeName = named.Obj().Pkg().Name() + "." + named.Obj().Name() _ = c.genStructExtended(t, over.TypeName, exts) + extName = over.TypeName } else { name := "unnamed" if exts != nil { @@ -385,11 +400,14 @@ func (c *codegen) scAndVMTypeFromType(t types.Type, exts map[string]binding.Exte } _ = c.genStructExtended(t, name, exts) } + // For bindings configurator this structure becomes named in fact. Its name + // is "unnamed[X...X]". + extName = name } return smartcontract.ArrayType, stackitem.StructT, over, &binding.ExtendedType{ // Value-less, refer to exts. Base: smartcontract.ArrayType, - Name: over.TypeName, + Name: extName, } case *types.Slice: @@ -580,9 +598,20 @@ func (di *DebugInfo) ConvertToManifest(o *Options) (*manifest.Manifest, error) { if o.ContractSupportedStandards != nil { result.SupportedStandards = o.ContractSupportedStandards } + 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 + } + events[i] = manifest.Event{ + Name: o.ContractEvents[i].Name, + Parameters: params, + } + } result.ABI = manifest.ABI{ Methods: methods, - Events: o.ContractEvents, + Events: events, } if result.ABI.Events == nil { result.ABI.Events = make([]manifest.Event, 0) diff --git a/pkg/compiler/inline.go b/pkg/compiler/inline.go index 1653d8638..b6cd03d3b 100644 --- a/pkg/compiler/inline.go +++ b/pkg/compiler/inline.go @@ -7,6 +7,7 @@ import ( "go/types" "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/util" "github.com/nspcc-dev/neo-go/pkg/vm/emit" @@ -172,11 +173,21 @@ func (c *codegen) processNotify(f *funcScope, args []ast.Expr, hasEllipsis bool) return nil } - params := make([]string, 0, len(args[1:])) + params := make([]DebugParam, 0, len(args[1:])) vParams := make([]*stackitem.Type, 0, len(args[1:])) + // 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) for _, p := range args[1:] { - st, vt, _, _ := c.scAndVMTypeFromExpr(p, nil) - params = append(params, st.String()) + st, vt, over, extT := c.scAndVMTypeFromExpr(p, extMap) + params = append(params, DebugParam{ + Name: "", // Parameter name will be filled in several lines below if the corresponding event exists in the buildinfo.options. + Type: vt.String(), + RealType: over, + ExtendedType: extT, + TypeSC: st, + }) vParams = append(vParams, &vt) } @@ -187,36 +198,43 @@ func (c *codegen) processNotify(f *funcScope, args []ast.Expr, hasEllipsis bool) return nil } var eventFound bool - if c.buildInfo.options != nil && c.buildInfo.options.ContractEvents != nil && !c.buildInfo.options.NoEventsCheck { + if c.buildInfo.options != nil && c.buildInfo.options.ContractEvents != nil { for _, e := range c.buildInfo.options.ContractEvents { if e.Name == name && len(e.Parameters) == len(vParams) { eventFound = true for i, scParam := range e.Parameters { - expectedType := scParam.Type.ConvertToStackitemType() - // No need to cast if the desired type is unknown. - if expectedType == stackitem.AnyT || - // Do not cast if desired type is Interop, the actual type is likely to be Any, leave the resolving to runtime.Notify. - expectedType == stackitem.InteropT || - // No need to cast if actual parameter type matches the desired one. - *vParams[i] == expectedType || - // expectedType doesn't contain Buffer anyway, but if actual variable type is Buffer, - // then runtime.Notify will convert it to ByteArray automatically, thus no need to emit conversion code. - (*vParams[i] == stackitem.BufferT && expectedType == stackitem.ByteArrayT) { - vParams[i] = nil - } else { - // For other cases the conversion code will be emitted using vParams... - vParams[i] = &expectedType - // ...thus, update emitted notification info in advance. - params[i] = scParam.Type.String() + params[i].Name = scParam.Name + if !c.buildInfo.options.NoEventsCheck { + expectedType := scParam.Type.ConvertToStackitemType() + // No need to cast if the desired type is unknown. + if expectedType == stackitem.AnyT || + // Do not cast if desired type is Interop, the actual type is likely to be Any, leave the resolving to runtime.Notify. + expectedType == stackitem.InteropT || + // No need to cast if actual parameter type matches the desired one. + *vParams[i] == expectedType || + // expectedType doesn't contain Buffer anyway, but if actual variable type is Buffer, + // then runtime.Notify will convert it to ByteArray automatically, thus no need to emit conversion code. + (*vParams[i] == stackitem.BufferT && expectedType == stackitem.ByteArrayT) { + vParams[i] = nil + } else { + // For other cases the conversion code will be emitted using vParams... + vParams[i] = &expectedType + // ...thus, update emitted notification info in advance. + params[i].Type = scParam.Type.String() + params[i].TypeSC = scParam.Type + } } } } } } - c.emittedEvents[name] = append(c.emittedEvents[name], params) + c.emittedEvents[name] = append(c.emittedEvents[name], EmittedEventInfo{ + ExtTypes: extMap, + Params: params, + }) // Do not enforce perfect expected/actual events match on this step, the final // check wil be performed after compilation if --no-events option is off. - if eventFound { + if eventFound && !c.buildInfo.options.NoEventsCheck { return vParams } return nil diff --git a/pkg/compiler/interop_test.go b/pkg/compiler/interop_test.go index 1cd1c88f3..c8f77ce24 100644 --- a/pkg/compiler/interop_test.go +++ b/pkg/compiler/interop_test.go @@ -542,13 +542,13 @@ func TestForcedNotifyArgumentsConversion(t *testing.T) { if count != len(expectedVMParamTypes) { t.Fatalf("parameters count mismatch: %d vs %d", count, len(expectedVMParamTypes)) } - scParams := make([]manifest.Parameter, len(targetSCParamTypes)) + scParams := make([]compiler.HybridParameter, len(targetSCParamTypes)) vmParams := make([]stackitem.Item, len(expectedVMParamTypes)) for i := range scParams { - scParams[i] = manifest.Parameter{ + scParams[i] = compiler.HybridParameter{Parameter: manifest.Parameter{ Name: strconv.Itoa(i), Type: targetSCParamTypes[i], - } + }} defaultValue := stackitem.NewBigInteger(big.NewInt(int64(i))) var ( val stackitem.Item @@ -564,7 +564,7 @@ func TestForcedNotifyArgumentsConversion(t *testing.T) { } ctr := neotest.CompileSource(t, e.CommitteeHash, strings.NewReader(src), &compiler.Options{ Name: "Helper", - ContractEvents: []manifest.Event{ + ContractEvents: []compiler.HybridEvent{ { Name: methodWithoutEllipsis, Parameters: scParams, diff --git a/pkg/neotest/compile.go b/pkg/neotest/compile.go index fee77c17e..70645c11c 100644 --- a/pkg/neotest/compile.go +++ b/pkg/neotest/compile.go @@ -60,6 +60,7 @@ 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/rpcclient/nep11/base.go b/pkg/rpcclient/nep11/base.go index ed30df2af..db906cb3d 100644 --- a/pkg/rpcclient/nep11/base.go +++ b/pkg/rpcclient/nep11/base.go @@ -10,6 +10,7 @@ purposes, otherwise more specific types are recommended. package nep11 import ( + "errors" "fmt" "math/big" "unicode/utf8" @@ -246,3 +247,71 @@ func UnwrapKnownProperties(m *stackitem.Map, err error) (map[string]string, erro } return res, nil } + +// TransferEventsFromApplicationLog retrieves all emitted TransferEvents from the +// provided [result.ApplicationLog]. +func TransferEventsFromApplicationLog(log *result.ApplicationLog) ([]*TransferEvent, error) { + if log == nil { + return nil, errors.New("nil application log") + } + var res []*TransferEvent + for i, ex := range log.Executions { + for j, e := range ex.Events { + if e.Name != "Transfer" { + continue + } + event := new(TransferEvent) + err := event.FromStackItem(e.Item) + if err != nil { + return nil, fmt.Errorf("failed to decode event from stackitem (event #%d, execution #%d): %w", j, i, err) + } + res = append(res, event) + } + } + return res, nil +} + +// FromStackItem converts provided [stackitem.Array] to TransferEvent or returns an +// error if it's not possible to do to so. +func (e *TransferEvent) 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) != 4 { + return errors.New("wrong number of event parameters") + } + + b, err := arr[0].TryBytes() + if err != nil { + return fmt.Errorf("invalid From: %w", err) + } + e.From, err = util.Uint160DecodeBytesBE(b) + if err != nil { + return fmt.Errorf("failed to decode From: %w", err) + } + + b, err = arr[1].TryBytes() + if err != nil { + return fmt.Errorf("invalid To: %w", err) + } + e.To, err = util.Uint160DecodeBytesBE(b) + if err != nil { + return fmt.Errorf("failed to decode To: %w", err) + } + + e.Amount, err = arr[2].TryInteger() + if err != nil { + return fmt.Errorf("field to decode Avount: %w", err) + } + + e.ID, err = arr[3].TryBytes() + if err != nil { + return fmt.Errorf("failed to decode ID: %w", err) + } + + return nil +} diff --git a/pkg/rpcclient/nep17/nep17.go b/pkg/rpcclient/nep17/nep17.go index f91488e82..dec570a22 100644 --- a/pkg/rpcclient/nep17/nep17.go +++ b/pkg/rpcclient/nep17/nep17.go @@ -8,12 +8,15 @@ package nep17 import ( "errors" + "fmt" "math/big" "github.com/nspcc-dev/neo-go/pkg/core/transaction" + "github.com/nspcc-dev/neo-go/pkg/neorpc/result" "github.com/nspcc-dev/neo-go/pkg/rpcclient/neptoken" "github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" ) // Invoker is used by TokenReader to call various safe methods. @@ -147,3 +150,66 @@ func (t *TokenWriter) MultiTransferUnsigned(params []TransferParameters) (*trans } return t.actor.MakeUnsignedRun(script, nil) } + +// TransferEventsFromApplicationLog retrieves all emitted TransferEvents from the +// provided [result.ApplicationLog]. +func TransferEventsFromApplicationLog(log *result.ApplicationLog) ([]*TransferEvent, error) { + if log == nil { + return nil, errors.New("nil application log") + } + var res []*TransferEvent + for i, ex := range log.Executions { + for j, e := range ex.Events { + if e.Name != "Transfer" { + continue + } + event := new(TransferEvent) + err := event.FromStackItem(e.Item) + if err != nil { + return nil, fmt.Errorf("failed to decode event from stackitem (event #%d, execution #%d): %w", j, i, err) + } + res = append(res, event) + } + } + return res, nil +} + +// FromStackItem converts provided [stackitem.Array] to TransferEvent or returns an +// error if it's not possible to do to so. +func (e *TransferEvent) 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) != 3 { + return errors.New("wrong number of event parameters") + } + + b, err := arr[0].TryBytes() + if err != nil { + return fmt.Errorf("invalid From: %w", err) + } + e.From, err = util.Uint160DecodeBytesBE(b) + if err != nil { + return fmt.Errorf("failed to decode From: %w", err) + } + + b, err = arr[1].TryBytes() + if err != nil { + return fmt.Errorf("invalid To: %w", err) + } + e.To, err = util.Uint160DecodeBytesBE(b) + if err != nil { + return fmt.Errorf("failed to decode To: %w", err) + } + + e.Amount, err = arr[2].TryInteger() + if err != nil { + return fmt.Errorf("field to decode Avount: %w", err) + } + + return nil +} diff --git a/pkg/smartcontract/binding/generate.go b/pkg/smartcontract/binding/generate.go index c02bbd49a..bd7d42cbc 100644 --- a/pkg/smartcontract/binding/generate.go +++ b/pkg/smartcontract/binding/generate.go @@ -48,14 +48,23 @@ const Hash = "{{ .Hash }}" type ( // Config contains parameter for the generated binding. Config struct { - Package string `yaml:"package,omitempty"` - Manifest *manifest.Manifest `yaml:"-"` - Hash util.Uint160 `yaml:"hash,omitempty"` - Overrides map[string]Override `yaml:"overrides,omitempty"` - CallFlags map[string]callflag.CallFlag `yaml:"callflags,omitempty"` - NamedTypes map[string]ExtendedType `yaml:"namedtypes,omitempty"` - Types map[string]ExtendedType `yaml:"types,omitempty"` - Output io.Writer `yaml:"-"` + Package string `yaml:"package,omitempty"` + Manifest *manifest.Manifest `yaml:"-"` + Hash util.Uint160 `yaml:"hash,omitempty"` + Overrides map[string]Override `yaml:"overrides,omitempty"` + CallFlags map[string]callflag.CallFlag `yaml:"callflags,omitempty"` + // NamedTypes contains exported structured types that have some name (even + // 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"` + // 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 { @@ -63,7 +72,7 @@ type ( 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 and arrays. + Value *ExtendedType `yaml:"value,omitempty"` // Value type for iterators, arrays and maps. Fields []FieldExtendedType `yaml:"fields,omitempty"` // Ordered type data for structure fields. } @@ -256,3 +265,34 @@ 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].ExtendedType.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 new file mode 100644 index 000000000..64522d28c --- /dev/null +++ b/pkg/smartcontract/binding/generate_test.go @@ -0,0 +1,229 @@ +package binding + +import ( + "testing" + + "github.com/nspcc-dev/neo-go/pkg/smartcontract" + "github.com/stretchr/testify/assert" +) + +func TestExtendedType_Equals(t *testing.T) { + crazyT := ExtendedType{ + Base: smartcontract.StringType, + Name: "qwertyu", + Interface: "qwerty", + Key: smartcontract.BoolType, + Value: &ExtendedType{ + Base: smartcontract.IntegerType, + }, + Fields: []FieldExtendedType{ + { + Field: "qwe", + ExtendedType: ExtendedType{ + Base: smartcontract.IntegerType, + Name: "qwer", + Interface: "qw", + Key: smartcontract.ArrayType, + Fields: []FieldExtendedType{ + { + Field: "as", + }, + }, + }, + }, + { + Field: "asf", + ExtendedType: ExtendedType{ + Base: smartcontract.BoolType, + }, + }, + { + Field: "sffg", + ExtendedType: ExtendedType{ + Base: smartcontract.AnyType, + }, + }, + }, + } + tcs := map[string]struct { + a *ExtendedType + b *ExtendedType + expectedRes bool + }{ + "both nil": { + a: nil, + b: nil, + expectedRes: true, + }, + "a is nil": { + a: nil, + b: &ExtendedType{}, + expectedRes: false, + }, + "b is nil": { + a: &ExtendedType{}, + b: nil, + expectedRes: false, + }, + "base mismatch": { + a: &ExtendedType{ + Base: smartcontract.StringType, + }, + b: &ExtendedType{ + Base: smartcontract.IntegerType, + }, + expectedRes: false, + }, + "name mismatch": { + a: &ExtendedType{ + Base: smartcontract.ArrayType, + Name: "q", + }, + b: &ExtendedType{ + Base: smartcontract.ArrayType, + Name: "w", + }, + expectedRes: false, + }, + "number of fields mismatch": { + a: &ExtendedType{ + Base: smartcontract.ArrayType, + Name: "q", + Fields: []FieldExtendedType{ + { + Field: "IntField", + ExtendedType: ExtendedType{Base: smartcontract.IntegerType}, + }, + }, + }, + b: &ExtendedType{ + Base: smartcontract.ArrayType, + Name: "w", + Fields: []FieldExtendedType{ + { + Field: "IntField", + ExtendedType: ExtendedType{Base: smartcontract.IntegerType}, + }, + { + Field: "BoolField", + ExtendedType: ExtendedType{Base: smartcontract.BoolType}, + }, + }, + }, + expectedRes: false, + }, + "field names mismatch": { + a: &ExtendedType{ + Base: smartcontract.ArrayType, + Fields: []FieldExtendedType{ + { + Field: "IntField", + ExtendedType: ExtendedType{Base: smartcontract.IntegerType}, + }, + }, + }, + b: &ExtendedType{ + Base: smartcontract.ArrayType, + Fields: []FieldExtendedType{ + { + Field: "BoolField", + ExtendedType: ExtendedType{Base: smartcontract.BoolType}, + }, + }, + }, + expectedRes: false, + }, + "field types mismatch": { + a: &ExtendedType{ + Base: smartcontract.ArrayType, + Fields: []FieldExtendedType{ + { + Field: "Field", + ExtendedType: ExtendedType{Base: smartcontract.IntegerType}, + }, + }, + }, + b: &ExtendedType{ + Base: smartcontract.ArrayType, + Fields: []FieldExtendedType{ + { + Field: "Field", + ExtendedType: ExtendedType{Base: smartcontract.BoolType}, + }, + }, + }, + expectedRes: false, + }, + "interface mismatch": { + a: &ExtendedType{Interface: "iterator"}, + b: &ExtendedType{Interface: "unknown"}, + expectedRes: false, + }, + "value is nil": { + a: &ExtendedType{ + Base: smartcontract.StringType, + }, + b: &ExtendedType{ + Base: smartcontract.StringType, + }, + expectedRes: true, + }, + "a value is not nil": { + a: &ExtendedType{ + Base: smartcontract.ArrayType, + Value: &ExtendedType{}, + }, + b: &ExtendedType{ + Base: smartcontract.ArrayType, + }, + expectedRes: false, + }, + "b value is not nil": { + a: &ExtendedType{ + Base: smartcontract.ArrayType, + }, + b: &ExtendedType{ + Base: smartcontract.ArrayType, + Value: &ExtendedType{}, + }, + expectedRes: false, + }, + "byte array tolerance for a": { + a: &ExtendedType{ + Base: smartcontract.StringType, + }, + b: &ExtendedType{ + Base: smartcontract.ByteArrayType, + }, + expectedRes: true, + }, + "byte array tolerance for b": { + a: &ExtendedType{ + Base: smartcontract.ByteArrayType, + }, + b: &ExtendedType{ + Base: smartcontract.StringType, + }, + expectedRes: true, + }, + "key mismatch": { + a: &ExtendedType{ + Key: smartcontract.StringType, + }, + b: &ExtendedType{ + Key: smartcontract.IntegerType, + }, + expectedRes: false, + }, + "good nested": { + a: &crazyT, + b: &crazyT, + expectedRes: true, + }, + } + for name, tc := range tcs { + t.Run(name, func(t *testing.T) { + assert.Equal(t, tc.expectedRes, tc.a.Equals(tc.b)) + }) + } +} diff --git a/pkg/smartcontract/rpcbinding/binding.go b/pkg/smartcontract/rpcbinding/binding.go index 3db076bd7..caf6f4397 100644 --- a/pkg/smartcontract/rpcbinding/binding.go +++ b/pkg/smartcontract/rpcbinding/binding.go @@ -5,6 +5,7 @@ import ( "sort" "strings" "text/template" + "unicode" "github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/smartcontract/binding" @@ -12,8 +13,21 @@ import ( "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest/standard" ) -const srcTmpl = ` -{{- define "SAFEMETHOD" -}} +// The set of constants containing parts of RPC binding template. Each block of code +// including template definition and var/type/method definitions contain new line at the +// start and ends with a new line. On adding new block of code to the template, please, +// ensure that this block has new line at the start and in the end of the block. +const ( + eventDefinition = `{{ define "EVENT" }} +// {{.Name}} represents "{{.ManifestName}}" event emitted by the contract. +type {{.Name}} struct { + {{- range $index, $arg := .Parameters}} + {{.Name}} {{.Type}} + {{- end}} +} +{{ end }}` + + safemethodDefinition = `{{ define "SAFEMETHOD" }} // {{.Name}} {{.Comment}} func (c *ContractReader) {{.Name}}({{range $index, $arg := .Arguments -}} {{- if ne $index 0}}, {{end}} @@ -32,8 +46,7 @@ func (c *ContractReader) {{.Name}}({{range $index, $arg := .Arguments -}} {{- range $arg := .Arguments -}}, {{.Name}}{{end}}) {{- end}} } -{{- if eq .Unwrapper "SessionIterator"}} - +{{ if eq .Unwrapper "SessionIterator" }} // {{.Name}}Expanded is similar to {{.Name}} (uses the same contract // method), but can be useful if the server used doesn't support sessions and // doesn't expand iterators. It creates a script that will get the specified @@ -42,17 +55,16 @@ func (c *ContractReader) {{.Name}}({{range $index, $arg := .Arguments -}} func (c *ContractReader) {{.Name}}Expanded({{range $index, $arg := .Arguments}}{{.Name}} {{.Type}}, {{end}}_numOfIteratorItems int) ([]stackitem.Item, error) { return unwrap.Array(c.invoker.CallAndExpandIterator(Hash, "{{.NameABI}}", _numOfIteratorItems{{range $arg := .Arguments}}, {{.Name}}{{end}})) } -{{- end -}} -{{- end -}} -{{- define "METHOD" -}} -{{- if eq .ReturnType "bool"}}func scriptFor{{.Name}}({{range $index, $arg := .Arguments -}} +{{ end }}{{ end }}` + methodDefinition = `{{ define "METHOD" }}{{ if eq .ReturnType "bool"}} +func scriptFor{{.Name}}({{range $index, $arg := .Arguments -}} {{- if ne $index 0}}, {{end}} {{- .Name}} {{.Type}} {{- end}}) ([]byte, error) { return smartcontract.CreateCallWithAssertScript(Hash, "{{ .NameABI }}"{{- range $index, $arg := .Arguments -}}, {{.Name}}{{end}}) } - -{{end}}// {{.Name}} {{.Comment}} +{{ end }} +// {{.Name}} {{.Comment}} // 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) {{.Name}}({{range $index, $arg := .Arguments -}} @@ -97,8 +109,9 @@ func (c *Contract) {{.Name}}Unsigned({{range $index, $arg := .Arguments -}} } return c.actor.MakeUnsignedRun(script, nil){{end}} } -{{- end -}} -// Package {{.PackageName}} contains RPC wrappers for {{.ContractName}} contract. +{{end}}` + + bindingDefinition = `// Package {{.PackageName}} contains RPC wrappers for {{.ContractName}} contract. package {{.PackageName}} import ( @@ -107,16 +120,17 @@ import ( // Hash contains contract hash. var Hash = {{ .Hash }} - -{{range $name, $typ := .NamedTypes}} +{{ range $name, $typ := .NamedTypes }} // {{toTypeName $name}} is a contract-specific {{$name}} type used by its methods. type {{toTypeName $name}} struct { {{- range $m := $typ.Fields}} {{.Field}} {{etTypeToStr .ExtendedType}} {{- end}} } -{{end -}} -{{if .HasReader}}// Invoker is used by ContractReader to call various safe methods. +{{end}} +{{- range $e := .CustomEvents }}{{template "EVENT" $e }}{{ end -}} +{{- if .HasReader}} +// Invoker is used by ContractReader to call various safe methods. type Invoker interface { {{if or .IsNep11D .IsNep11ND}} nep11.Invoker {{else -}} @@ -129,9 +143,9 @@ type Invoker interface { {{end -}} {{end -}} } - {{end -}} -{{if .HasWriter}}// Actor is used by Contract to call state-changing methods. +{{- if .HasWriter}} +// Actor is used by Contract to call state-changing methods. type Actor interface { {{- if .HasReader}} Invoker @@ -150,9 +164,9 @@ type Actor interface { SendRun(script []byte) (util.Uint256, uint32, error) {{end -}} } - {{end -}} -{{if .HasReader}}// ContractReader implements safe contract methods. +{{- if .HasReader}} +// ContractReader implements safe contract methods. type ContractReader struct { {{if .IsNep11D}}nep11.DivisibleReader {{end -}} @@ -162,9 +176,9 @@ type ContractReader struct { {{end -}} invoker Invoker } - {{end -}} -{{if .HasWriter}}// Contract implements all contract methods. +{{- if .HasWriter}} +// Contract implements all contract methods. type Contract struct { {{if .HasReader}}ContractReader {{end -}} @@ -176,9 +190,9 @@ type Contract struct { {{end -}} actor Actor } - {{end -}} -{{if .HasReader}}// NewReader creates an instance of ContractReader using Hash and the given Invoker. +{{- if .HasReader}} +// NewReader creates an instance of ContractReader using Hash and the given Invoker. func NewReader(invoker Invoker) *ContractReader { return &ContractReader{ {{- if .IsNep11D}}*nep11.NewDivisibleReader(invoker, Hash), {{end}} @@ -186,9 +200,9 @@ func NewReader(invoker Invoker) *ContractReader { {{- if .IsNep17}}*nep17.NewReader(invoker, Hash), {{end -}} invoker} } - {{end -}} -{{if .HasWriter}}// New creates an instance of Contract using Hash and the given Actor. +{{- if .HasWriter}} +// New creates an instance of Contract using Hash and the given Actor. func New(actor Actor) *Contract { {{if .IsNep11D}}var nep11dt = nep11.NewDivisible(actor, Hash) {{end -}} @@ -207,48 +221,115 @@ func New(actor Actor) *Contract { {{- if .IsNep17}}nep17t.TokenWriter, {{end -}} actor} } - {{end -}} -{{range $m := .SafeMethods}} -{{template "SAFEMETHOD" $m }} -{{end}} -{{- range $m := .Methods}} -{{template "METHOD" $m }} -{{end}} -{{- range $name, $typ := .NamedTypes}} +{{- range $m := .SafeMethods }}{{template "SAFEMETHOD" $m }}{{ end -}} +{{- range $m := .Methods -}}{{template "METHOD" $m }}{{ end -}} +{{- range $name, $typ := .NamedTypes }} // itemTo{{toTypeName $name}} converts stack item into *{{toTypeName $name}}. func itemTo{{toTypeName $name}}(item stackitem.Item, err error) (*{{toTypeName $name}}, error) { if err != nil { return nil, err } + var res = new({{toTypeName $name}}) + err = res.FromStackItem(item) + return res, err +} + +// FromStackItem retrieves fields of {{toTypeName $name}} from the given +// [stackitem.Item] or returns an error if it's not possible to do to so. +func (res *{{toTypeName $name}}) FromStackItem(item stackitem.Item) error { arr, ok := item.Value().([]stackitem.Item) if !ok { - return nil, errors.New("not an array") + return errors.New("not an array") } if len(arr) != {{len $typ.Fields}} { - return nil, errors.New("wrong number of structure elements") + return errors.New("wrong number of structure elements") } - - var res = new({{toTypeName $name}}) -{{if len .Fields}} var index = -1 +{{if len .Fields}} + var ( + index = -1 + err error + ) {{- range $m := $typ.Fields}} index++ res.{{.Field}}, err = {{etTypeConverter .ExtendedType "arr[index]"}} if err != nil { - return nil, fmt.Errorf("field {{.Field}}: %w", err) + return fmt.Errorf("field {{.Field}}: %w", err) } {{end}} -{{end}} - return res, err +{{- end}} + return nil } -{{end}}` +{{ end -}} +{{- range $e := .CustomEvents }} +// {{$e.Name}}sFromApplicationLog retrieves a set of all emitted events +// with "{{$e.ManifestName}}" name from the provided [result.ApplicationLog]. +func {{$e.Name}}sFromApplicationLog(log *result.ApplicationLog) ([]*{{$e.Name}}, error) { + if log == nil { + return nil, errors.New("nil application log") + } + + var res []*{{$e.Name}} + for i, ex := range log.Executions { + for j, e := range ex.Events { + if e.Name != "{{$e.ManifestName}}" { + continue + } + event := new({{$e.Name}}) + err := event.FromStackItem(e.Item) + if err != nil { + return nil, fmt.Errorf("failed to deserialize {{$e.Name}} from stackitem (execution #%d, event #%d): %w", i, j, err) + } + res = append(res, event) + } + } + + return res, nil +} + +// FromStackItem converts provided [stackitem.Array] to {{$e.Name}} or +// returns an error if it's not possible to do to so. +func (e *{{$e.Name}}) 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) != {{len $e.Parameters}} { + return errors.New("wrong number of structure elements") + } + + {{if len $e.Parameters}}var ( + index = -1 + err error + ) + {{- range $p := $e.Parameters}} + index++ + e.{{.Name}}, err = {{etTypeConverter .ExtType "arr[index]"}} + if err != nil { + return fmt.Errorf("field {{.Name}}: %w", err) + } +{{end}} +{{- end}} + return nil +} +{{end -}}` + + srcTmpl = bindingDefinition + + eventDefinition + + safemethodDefinition + + methodDefinition +) type ( ContractTmpl struct { binding.ContractTmpl - SafeMethods []SafeMethodTmpl - NamedTypes map[string]binding.ExtendedType + SafeMethods []SafeMethodTmpl + CustomEvents []CustomEventTemplate + NamedTypes map[string]binding.ExtendedType IsNep11D bool IsNep11ND bool @@ -265,6 +346,25 @@ type ( ItemTo string ExtendedReturn binding.ExtendedType } + + CustomEventTemplate struct { + // Name is the event's name that will be used as the event structure name in + // the resulting RPC binding. It is a valid go structure name and may differ + // from ManifestName. + Name string + // ManifestName is the event's name declared in the contract manifest. + // It may contain any UTF8 character. + ManifestName string + Parameters []EventParamTmpl + } + + EventParamTmpl struct { + binding.ParamTmpl + + // ExtType holds the event parameter's type information provided by Manifest, + // i.e. simple types only. + ExtType binding.ExtendedType + } ) // NewConfig initializes and returns a new config instance. @@ -296,12 +396,14 @@ func Generate(cfg binding.Config) error { mfst.ABI.Methods = dropStdMethods(mfst.ABI.Methods, standard.Nep11NonDivisible) ctr.IsNep11ND = true } + mfst.ABI.Events = dropStdEvents(mfst.ABI.Events, standard.Nep11Base) break // Can't be NEP-17 at the same time. } if std == manifest.NEP17StandardName && standard.ComplyABI(cfg.Manifest, standard.Nep17) == nil { mfst.ABI.Methods = dropStdMethods(mfst.ABI.Methods, standard.Nep17) imports["github.com/nspcc-dev/neo-go/pkg/rpcclient/nep17"] = struct{}{} ctr.IsNep17 = true + mfst.ABI.Events = dropStdEvents(mfst.ABI.Events, standard.Nep17) break // Can't be NEP-11 at the same time. } } @@ -315,7 +417,7 @@ func Generate(cfg binding.Config) error { } ctr.ContractTmpl = binding.TemplateFromManifest(cfg, scTypeToGo) - ctr = scTemplateToRPC(cfg, ctr, imports) + ctr = scTemplateToRPC(cfg, ctr, imports, scTypeToGo) ctr.NamedTypes = cfg.NamedTypes var srcTemplate = template.Must(template.New("generate").Funcs(template.FuncMap{ @@ -344,6 +446,18 @@ func dropManifestMethods(meths []manifest.Method, manifested []manifest.Method) return meths } +func dropManifestEvents(events []manifest.Event, manifested []manifest.Event) []manifest.Event { + for _, e := range manifested { + for i := 0; i < len(events); i++ { + if events[i].Name == e.Name && len(events[i].Parameters) == len(e.Parameters) { + events = append(events[:i], events[i+1:]...) + i-- + } + } + } + return events +} + func dropStdMethods(meths []manifest.Method, std *standard.Standard) []manifest.Method { meths = dropManifestMethods(meths, std.Manifest.ABI.Methods) if std.Optional != nil { @@ -355,6 +469,14 @@ func dropStdMethods(meths []manifest.Method, std *standard.Standard) []manifest. return meths } +func dropStdEvents(events []manifest.Event, std *standard.Standard) []manifest.Event { + events = dropManifestEvents(events, std.Manifest.ABI.Events) + if std.Base != nil { + return dropStdEvents(events, std.Base) + } + return events +} + func extendedTypeToGo(et binding.ExtendedType, named map[string]binding.ExtendedType) (string, string) { switch et.Base { case smartcontract.AnyType: @@ -389,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", "" @@ -402,7 +529,7 @@ func extendedTypeToGo(et binding.ExtendedType, named map[string]binding.Extended func etTypeConverter(et binding.ExtendedType, v string) string { switch et.Base { case smartcontract.AnyType: - return v + ".Value(), nil" + return v + ".Value(), error(nil)" case smartcontract.BoolType: return v + ".TryBool()" case smartcontract.IntegerType: @@ -484,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()) @@ -504,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: @@ -520,7 +656,7 @@ func scTypeToGo(name string, typ smartcontract.ParamType, cfg *binding.Config) ( return extendedTypeToGo(et, cfg.NamedTypes) } -func scTemplateToRPC(cfg binding.Config, ctr ContractTmpl, imports map[string]struct{}) ContractTmpl { +func scTemplateToRPC(cfg binding.Config, ctr ContractTmpl, imports map[string]struct{}, scTypeConverter func(string, smartcontract.ParamType, *binding.Config) (string, string)) ContractTmpl { for i := range ctr.Imports { imports[ctr.Imports[i]] = struct{}{} } @@ -551,6 +687,47 @@ func scTemplateToRPC(cfg binding.Config, ctr ContractTmpl, imports map[string]st if len(cfg.NamedTypes) > 0 { imports["errors"] = struct{}{} } + for _, abiEvent := range cfg.Manifest.ABI.Events { + eBindingName := ToEventBindingName(abiEvent.Name) + eTmp := CustomEventTemplate{ + Name: eBindingName, + ManifestName: abiEvent.Name, + } + for i := range abiEvent.Parameters { + pBindingName := ToParameterBindingName(abiEvent.Parameters[i].Name) + fullPName := eBindingName + "." + pBindingName + typeStr, pkg := scTypeConverter(fullPName, abiEvent.Parameters[i].Type, &cfg) + if pkg != "" { + imports[pkg] = struct{}{} + } + + var ( + extType binding.ExtendedType + ok bool + ) + if extType, ok = cfg.Types[fullPName]; !ok { + extType = binding.ExtendedType{ + Base: abiEvent.Parameters[i].Type, + } + addETImports(extType, ctr.NamedTypes, imports) + } + eTmp.Parameters = append(eTmp.Parameters, EventParamTmpl{ + ParamTmpl: binding.ParamTmpl{ + Name: pBindingName, + Type: typeStr, + }, + ExtType: extType, + }) + } + ctr.CustomEvents = append(ctr.CustomEvents, eTmp) + } + + if len(ctr.CustomEvents) > 0 { + imports["github.com/nspcc-dev/neo-go/pkg/neorpc/result"] = struct{}{} + imports["github.com/nspcc-dev/neo-go/pkg/vm/stackitem"] = struct{}{} + imports["fmt"] = struct{}{} + imports["errors"] = struct{}{} + } for i := range ctr.SafeMethods { switch ctr.SafeMethods[i].ReturnType { @@ -680,3 +857,44 @@ func toTypeName(s string) string { func addIndent(str string, ind string) string { return strings.ReplaceAll(str, "\n", "\n"+ind) } + +// ToEventBindingName converts event name specified in the contract manifest to +// a valid go exported event structure name. +func ToEventBindingName(eventName string) string { + return toPascalCase(eventName) + "Event" +} + +// ToParameterBindingName converts parameter name specified in the contract +// manifest to a valid go structure's exported field name. +func ToParameterBindingName(paramName string) string { + return toPascalCase(paramName) +} + +// toPascalCase removes all non-unicode characters from the provided string and +// converts it to pascal case using space as delimiter. +func toPascalCase(s string) string { + var res string + ss := strings.Split(s, " ") + for i := range ss { // TODO: use DecodeRuneInString instead. + var word string + for _, ch := range ss[i] { + var ok bool + if len(res) == 0 && len(word) == 0 { + ok = unicode.IsLetter(ch) + } else { + ok = unicode.IsLetter(ch) || unicode.IsDigit(ch) || ch == '_' + } + if ok { + word += string(ch) + } + } + if len(word) > 0 { + res += upperFirst(word) + } + } + return res +} + +func upperFirst(s string) string { + return strings.ToUpper(s[0:1]) + s[1:] +}