package manifest import ( "errors" "fmt" "sort" "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" ) const ( // MethodInit is a name for default initialization method. MethodInit = "_initialize" // MethodDeploy is a name for default method called during contract deployment. MethodDeploy = "_deploy" // MethodVerify is a name for default verification method. MethodVerify = "verify" // MethodOnNEP17Payment is the name of the method which is called when contract receives NEP-17 tokens. MethodOnNEP17Payment = "onNEP17Payment" // MethodOnNEP11Payment is the name of the method which is called when contract receives NEP-11 tokens. MethodOnNEP11Payment = "onNEP11Payment" ) // ABI represents a contract application binary interface. type ABI struct { Methods []Method `json:"methods"` Events []Event `json:"events"` } // GetMethod returns methods with the specified name. func (a *ABI) GetMethod(name string, paramCount int) *Method { for i := range a.Methods { if a.Methods[i].Name == name && (paramCount == -1 || len(a.Methods[i].Parameters) == paramCount) { return &a.Methods[i] } } return nil } // GetEvent returns the event with the specified name. func (a *ABI) GetEvent(name string) *Event { for i := range a.Events { if a.Events[i].Name == name { return &a.Events[i] } } return nil } // IsValid checks ABI consistency and correctness. func (a *ABI) IsValid() error { if len(a.Methods) == 0 { return errors.New("no methods") } for i := range a.Methods { err := a.Methods[i].IsValid() if err != nil { return fmt.Errorf("method %q/%d: %w", a.Methods[i].Name, len(a.Methods[i].Parameters), err) } } if len(a.Methods) > 1 { methods := make([]struct { name string params int }, len(a.Methods)) for i := range methods { methods[i].name = a.Methods[i].Name methods[i].params = len(a.Methods[i].Parameters) } sort.Slice(methods, func(i, j int) bool { if methods[i].name < methods[j].name { return true } if methods[i].name == methods[j].name { return methods[i].params < methods[j].params } return false }) for i := range methods { if i == 0 { continue } if methods[i].name == methods[i-1].name && methods[i].params == methods[i-1].params { return errors.New("duplicate method specifications") } } } for i := range a.Events { err := a.Events[i].IsValid() if err != nil { return fmt.Errorf("event %q/%d: %w", a.Events[i].Name, len(a.Events[i].Parameters), err) } } if len(a.Events) > 1 { names := make([]string, len(a.Events)) for i := range a.Events { names[i] = a.Events[i].Name } if stringsHaveDups(names) { return errors.New("duplicate event names") } } return nil } // ToStackItem converts ABI to stackitem.Item. func (a *ABI) ToStackItem() stackitem.Item { methods := make([]stackitem.Item, len(a.Methods)) for i := range a.Methods { methods[i] = a.Methods[i].ToStackItem() } events := make([]stackitem.Item, len(a.Events)) for i := range a.Events { events[i] = a.Events[i].ToStackItem() } return stackitem.NewStruct([]stackitem.Item{ stackitem.Make(methods), stackitem.Make(events), }) } // FromStackItem converts stackitem.Item to ABI. func (a *ABI) FromStackItem(item stackitem.Item) error { if item.Type() != stackitem.StructT { return errors.New("invalid ABI stackitem type") } str := item.Value().([]stackitem.Item) if len(str) != 2 { return errors.New("invalid ABI stackitem length") } if str[0].Type() != stackitem.ArrayT { return errors.New("invalid Methods stackitem type") } methods := str[0].Value().([]stackitem.Item) a.Methods = make([]Method, len(methods)) for i := range methods { m := new(Method) if err := m.FromStackItem(methods[i]); err != nil { return err } a.Methods[i] = *m } if str[1].Type() != stackitem.ArrayT { return errors.New("invalid Events stackitem type") } events := str[1].Value().([]stackitem.Item) a.Events = make([]Event, len(events)) for i := range events { e := new(Event) if err := e.FromStackItem(events[i]); err != nil { return err } a.Events[i] = *e } return nil }