smartconract: generate RPC binding wrappers for events

Signed-off-by: Anna Shaleva <shaleva.ann@nspcc.ru>
This commit is contained in:
Anna Shaleva 2023-05-24 11:52:14 +03:00
parent ae52b2c2fa
commit 044ae477ca
10 changed files with 896 additions and 44 deletions

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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),
}

View file

@ -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)
}
}
}

View file

@ -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.
@ -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 {

View file

@ -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

View file

@ -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:"-"`
}

View file

@ -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
)
@ -260,8 +327,9 @@ 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
@ -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{
@ -338,8 +425,9 @@ func Generate(cfg binding.Config) error {
r, _ := extendedTypeToGo(et, ctr.NamedTypes)
return r
},
"toTypeName": toTypeName,
"cutPointer": cutPointer,
"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:]
}