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
|
@ -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 []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 {
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue