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

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