smartconract: generate RPC binding wrappers for events
Signed-off-by: Anna Shaleva <shaleva.ann@nspcc.ru>
This commit is contained in:
parent
ae52b2c2fa
commit
044ae477ca
10 changed files with 896 additions and 44 deletions
96
cli/smartcontract/testdata/gas/gas.go
vendored
96
cli/smartcontract/testdata/gas/gas.go
vendored
|
@ -2,13 +2,25 @@
|
|||
package gastoken
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"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/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
// Hash contains contract hash.
|
||||
var Hash = util.Uint160{0xcf, 0x76, 0xe2, 0x8b, 0xd0, 0x6, 0x2c, 0x4a, 0x47, 0x8e, 0xe3, 0x55, 0x61, 0x1, 0x13, 0x19, 0xf3, 0xcf, 0xa4, 0xd2}
|
||||
|
||||
// TransferEvent represents "Transfer" event emitted by the contract.
|
||||
type TransferEvent struct {
|
||||
From util.Uint160
|
||||
To util.Uint160
|
||||
Amount *big.Int
|
||||
}
|
||||
|
||||
// Invoker is used by ContractReader to call various safe methods.
|
||||
type Invoker interface {
|
||||
nep17.Invoker
|
||||
|
@ -44,3 +56,87 @@ func New(actor Actor) *Contract {
|
|||
var nep17t = nep17.New(actor, Hash)
|
||||
return &Contract{ContractReader{nep17t.TokenReader, actor}, nep17t.TokenWriter, actor}
|
||||
}
|
||||
|
||||
// TransferEventsFromApplicationLog retrieves a set of all emitted events
|
||||
// with "Transfer" name from the provided 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 deserialize TransferEvent from stackitem (execution %d, event %d): %w", i, j, err)
|
||||
}
|
||||
res = append(res, event)
|
||||
}
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// FromStackItem converts provided stackitem.Array to TransferEvent and
|
||||
// returns an error if 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 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)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
280
cli/smartcontract/testdata/nameservice/nns.go
vendored
280
cli/smartcontract/testdata/nameservice/nns.go
vendored
|
@ -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"
|
||||
|
@ -16,6 +18,28 @@ import (
|
|||
// 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}
|
||||
|
||||
// TransferEvent represents "Transfer" event emitted by the contract.
|
||||
type TransferEvent struct {
|
||||
From util.Uint160
|
||||
To util.Uint160
|
||||
Amount *big.Int
|
||||
TokenId []byte
|
||||
}
|
||||
|
||||
// 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
|
||||
|
@ -320,3 +344,259 @@ 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)
|
||||
}
|
||||
|
||||
// TransferEventsFromApplicationLog retrieves a set of all emitted events
|
||||
// with "Transfer" name from the provided 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 deserialize TransferEvent from stackitem (execution %d, event %d): %w", i, j, err)
|
||||
}
|
||||
res = append(res, event)
|
||||
}
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// FromStackItem converts provided stackitem.Array to TransferEvent and
|
||||
// returns an error if 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 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.TokenId, err = arr[index].TryBytes()
|
||||
if err != nil {
|
||||
return fmt.Errorf("field TokenId: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetAdminEventsFromApplicationLog retrieves a set of all emitted events
|
||||
// with "SetAdmin" name from the provided 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 and
|
||||
// returns an error if 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 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 and
|
||||
// returns an error if 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
|
||||
}
|
||||
|
|
193
cli/smartcontract/testdata/nex/nex.go
vendored
193
cli/smartcontract/testdata/nex/nex.go
vendored
|
@ -2,17 +2,36 @@
|
|||
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}
|
||||
|
||||
// TransferEvent represents "Transfer" event emitted by the contract.
|
||||
type TransferEvent struct {
|
||||
From util.Uint160
|
||||
To util.Uint160
|
||||
Amount *big.Int
|
||||
}
|
||||
|
||||
// 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
|
||||
|
@ -229,3 +248,177 @@ 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)
|
||||
}
|
||||
|
||||
// TransferEventsFromApplicationLog retrieves a set of all emitted events
|
||||
// with "Transfer" name from the provided 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 deserialize TransferEvent from stackitem (execution %d, event %d): %w", i, j, err)
|
||||
}
|
||||
res = append(res, event)
|
||||
}
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// FromStackItem converts provided stackitem.Array to TransferEvent and
|
||||
// returns an error if 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 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)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// OnMintEventsFromApplicationLog retrieves a set of all emitted events
|
||||
// with "OnMint" name from the provided 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 and
|
||||
// returns an error if 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
|
||||
}
|
||||
|
|
74
cli/smartcontract/testdata/verifyrpc/verify.go
vendored
74
cli/smartcontract/testdata/verifyrpc/verify.go
vendored
|
@ -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)
|
||||
|
@ -67,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 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 and
|
||||
// returns an error if 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
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
}
|
||||
|
|
|
@ -309,6 +309,22 @@ func CompileAndSave(src string, o *Options) ([]byte, error) {
|
|||
if len(di.NamedTypes) > 0 {
|
||||
cfg.NamedTypes = di.NamedTypes
|
||||
}
|
||||
if len(di.EmittedEvents) > 0 {
|
||||
for eventName, eventUsages := range di.EmittedEvents {
|
||||
for typeName, extType := range eventUsages[0].ExtTypes {
|
||||
cfg.NamedTypes[typeName] = extType
|
||||
}
|
||||
for _, p := range eventUsages[0].Params {
|
||||
pname := eventName + "." + p.Name
|
||||
if p.RealType.TypeName != "" {
|
||||
cfg.Overrides[pname] = p.RealType
|
||||
}
|
||||
if p.ExtendedType != nil {
|
||||
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 +382,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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 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.
|
||||
EmittedEvents map[string][][]string `json:"-"`
|
||||
// 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.
|
||||
|
@ -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 {
|
||||
|
|
|
@ -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,11 +198,13 @@ 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 {
|
||||
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 ||
|
||||
|
@ -207,16 +220,21 @@ func (c *codegen) processNotify(f *funcScope, args []ast.Expr, hasEllipsis bool)
|
|||
// 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].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
|
||||
|
|
|
@ -62,6 +62,7 @@ type (
|
|||
// 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:"-"`
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
@ -17,6 +18,15 @@ import (
|
|||
// 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}}Event represents "{{.ManifestName}}" event emitted by the contract.
|
||||
type {{.Name}}Event struct {
|
||||
{{- range $index, $arg := .Parameters}}
|
||||
{{toPascalCase .Name}} {{.Type}}
|
||||
{{- end}}
|
||||
}
|
||||
{{ end }}`
|
||||
|
||||
safemethodDefinition = `{{ define "SAFEMETHOD" }}
|
||||
// {{.Name}} {{.Comment}}
|
||||
func (c *ContractReader) {{.Name}}({{range $index, $arg := .Arguments -}}
|
||||
|
@ -118,6 +128,7 @@ type {{toTypeName $name}} struct {
|
|||
{{- end}}
|
||||
}
|
||||
{{end}}
|
||||
{{- range $e := .CustomEvents }}{{template "EVENT" $e }}{{ end -}}
|
||||
{{- if .HasReader}}
|
||||
// Invoker is used by ContractReader to call various safe methods.
|
||||
type Invoker interface {
|
||||
|
@ -249,9 +260,65 @@ func (res *{{toTypeName $name}}) FromStackItem(item stackitem.Item) error {
|
|||
{{- end}}
|
||||
return nil
|
||||
}
|
||||
{{ end -}}
|
||||
{{- range $e := .CustomEvents }}
|
||||
// {{$e.Name}}EventsFromApplicationLog retrieves a set of all emitted events
|
||||
// with "{{$e.ManifestName}}" name from the provided ApplicationLog.
|
||||
func {{$e.Name}}EventsFromApplicationLog(log *result.ApplicationLog) ([]*{{$e.Name}}Event, error) {
|
||||
if log == nil {
|
||||
return nil, errors.New("nil application log")
|
||||
}
|
||||
|
||||
var res []*{{$e.Name}}Event
|
||||
for i, ex := range log.Executions {
|
||||
for j, e := range ex.Events {
|
||||
if e.Name != "{{$e.ManifestName}}" {
|
||||
continue
|
||||
}
|
||||
event := new({{$e.Name}}Event)
|
||||
err := event.FromStackItem(e.Item)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to deserialize {{$e.Name}}Event 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}}Event and
|
||||
// returns an error if so.
|
||||
func (e *{{$e.Name}}Event) 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.{{toPascalCase .Name}}, err = {{etTypeConverter .ExtType "arr[index]"}}
|
||||
if err != nil {
|
||||
return fmt.Errorf("field {{toPascalCase .Name}}: %w", err)
|
||||
}
|
||||
{{end}}
|
||||
{{- end}}
|
||||
return nil
|
||||
}
|
||||
{{end -}}`
|
||||
|
||||
srcTmpl = bindingDefinition +
|
||||
eventDefinition +
|
||||
safemethodDefinition +
|
||||
methodDefinition
|
||||
)
|
||||
|
@ -261,6 +328,7 @@ type (
|
|||
binding.ContractTmpl
|
||||
|
||||
SafeMethods []SafeMethodTmpl
|
||||
CustomEvents []CustomEventTemplate
|
||||
NamedTypes map[string]binding.ExtendedType
|
||||
|
||||
IsNep11D bool
|
||||
|
@ -278,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.
|
||||
|
@ -328,7 +415,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{
|
||||
|
@ -340,6 +427,7 @@ func Generate(cfg binding.Config) error {
|
|||
},
|
||||
"toTypeName": toTypeName,
|
||||
"cutPointer": cutPointer,
|
||||
"toPascalCase": toPascalCase,
|
||||
}).Parse(srcTmpl))
|
||||
|
||||
return srcTemplate.Execute(cfg.Output, ctr)
|
||||
|
@ -533,7 +621,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{}{}
|
||||
}
|
||||
|
@ -564,6 +652,51 @@ 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 {
|
||||
eTmp := CustomEventTemplate{
|
||||
// TODO: proper event name is better to be set right into config binding in normal form.
|
||||
Name: toPascalCase(abiEvent.Name),
|
||||
ManifestName: abiEvent.Name,
|
||||
}
|
||||
var varnames = make(map[string]bool)
|
||||
for i := range abiEvent.Parameters {
|
||||
name := abiEvent.Parameters[i].Name
|
||||
fullPName := abiEvent.Name + "." + name
|
||||
typeStr, pkg := scTypeConverter(fullPName, abiEvent.Parameters[i].Type, &cfg)
|
||||
if pkg != "" {
|
||||
imports[pkg] = struct{}{}
|
||||
}
|
||||
for varnames[name] {
|
||||
name = name + "_"
|
||||
}
|
||||
varnames[name] = true
|
||||
|
||||
var (
|
||||
extType binding.ExtendedType
|
||||
ok bool
|
||||
)
|
||||
if extType, ok = cfg.Types[fullPName]; !ok {
|
||||
extType = binding.ExtendedType{
|
||||
Base: abiEvent.Parameters[i].Type,
|
||||
}
|
||||
}
|
||||
eTmp.Parameters = append(eTmp.Parameters, EventParamTmpl{
|
||||
ParamTmpl: binding.ParamTmpl{
|
||||
Name: name,
|
||||
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 {
|
||||
|
@ -693,3 +826,32 @@ func toTypeName(s string) string {
|
|||
func addIndent(str string, ind string) string {
|
||||
return strings.ReplaceAll(str, "\n", "\n"+ind)
|
||||
}
|
||||
|
||||
// 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:]
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue