255 lines
6.4 KiB
Go
255 lines
6.4 KiB
Go
|
package binding
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"fmt"
|
||
|
"io"
|
||
|
"sort"
|
||
|
"strconv"
|
||
|
"strings"
|
||
|
"text/template"
|
||
|
"unicode"
|
||
|
|
||
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
||
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
|
||
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||
|
)
|
||
|
|
||
|
const srcTmpl = `
|
||
|
{{- define "METHOD" -}}
|
||
|
// {{.Name}} {{.Comment}}
|
||
|
func {{.Name}}({{range $index, $arg := .Arguments -}}
|
||
|
{{- if ne $index 0}}, {{end}}
|
||
|
{{- .Name}} {{.Type}}
|
||
|
{{- end}}) {{if .ReturnType }}{{ .ReturnType }} {
|
||
|
return neogointernal.CallWithToken(Hash, "{{ .NameABI }}", int(contract.{{ .CallFlag }})
|
||
|
{{- range $arg := .Arguments -}}, {{.Name}}{{end}}).({{ .ReturnType }})
|
||
|
{{- else -}} {
|
||
|
neogointernal.CallWithTokenNoRet(Hash, "{{ .NameABI }}", int(contract.{{ .CallFlag }})
|
||
|
{{- range $arg := .Arguments -}}, {{.Name}}{{end}})
|
||
|
{{- end}}
|
||
|
}
|
||
|
{{- end -}}
|
||
|
// Package {{.PackageName}} contains wrappers for {{.ContractName}} contract.
|
||
|
package {{.PackageName}}
|
||
|
|
||
|
import (
|
||
|
{{range $m := .Imports}} "{{ $m }}"
|
||
|
{{end}})
|
||
|
|
||
|
// Hash contains contract hash in big-endian form.
|
||
|
const Hash = "{{ .Hash }}"
|
||
|
{{range $m := .Methods}}
|
||
|
{{template "METHOD" $m }}
|
||
|
{{end}}`
|
||
|
|
||
|
type (
|
||
|
// Config contains parameter for the generated binding.
|
||
|
Config struct {
|
||
|
Package string `yaml:"package,omitempty"`
|
||
|
Manifest *manifest.Manifest `yaml:"-"`
|
||
|
Hash util.Uint160 `yaml:"hash,omitempty"`
|
||
|
Overrides map[string]Override `yaml:"overrides,omitempty"`
|
||
|
CallFlags map[string]callflag.CallFlag `yaml:"callflags,omitempty"`
|
||
|
Output io.Writer `yaml:"-"`
|
||
|
}
|
||
|
|
||
|
contractTmpl struct {
|
||
|
PackageName string
|
||
|
ContractName string
|
||
|
Imports []string
|
||
|
Hash string
|
||
|
Methods []methodTmpl
|
||
|
}
|
||
|
|
||
|
methodTmpl struct {
|
||
|
Name string
|
||
|
NameABI string
|
||
|
CallFlag string
|
||
|
Comment string
|
||
|
Arguments []paramTmpl
|
||
|
ReturnType string
|
||
|
}
|
||
|
|
||
|
paramTmpl struct {
|
||
|
Name string
|
||
|
Type string
|
||
|
}
|
||
|
)
|
||
|
|
||
|
// NewConfig initializes and returns new config instance.
|
||
|
func NewConfig() Config {
|
||
|
return Config{
|
||
|
Overrides: make(map[string]Override),
|
||
|
CallFlags: make(map[string]callflag.CallFlag),
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Generate writes Go file containing smartcontract bindings to the `cfg.Output`.
|
||
|
func Generate(cfg Config) error {
|
||
|
ctr, err := templateFromManifest(cfg)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
tmp, err := template.New("generate").Funcs(template.FuncMap{
|
||
|
"lowerFirst": lowerFirst,
|
||
|
"scTypeToGo": scTypeToGo,
|
||
|
}).Parse(srcTmpl)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
return tmp.Execute(cfg.Output, ctr)
|
||
|
}
|
||
|
|
||
|
func scTypeToGo(typ smartcontract.ParamType) string {
|
||
|
switch typ {
|
||
|
case smartcontract.AnyType:
|
||
|
return "interface{}"
|
||
|
case smartcontract.BoolType:
|
||
|
return "bool"
|
||
|
case smartcontract.IntegerType:
|
||
|
return "int"
|
||
|
case smartcontract.ByteArrayType:
|
||
|
return "[]byte"
|
||
|
case smartcontract.StringType:
|
||
|
return "string"
|
||
|
case smartcontract.Hash160Type:
|
||
|
return "interop.Hash160"
|
||
|
case smartcontract.Hash256Type:
|
||
|
return "interop.Hash256"
|
||
|
case smartcontract.PublicKeyType:
|
||
|
return "interop.PublicKey"
|
||
|
case smartcontract.SignatureType:
|
||
|
return "interop.Signature"
|
||
|
case smartcontract.ArrayType:
|
||
|
return "[]interface{}"
|
||
|
case smartcontract.MapType:
|
||
|
return "map[string]interface{}"
|
||
|
case smartcontract.InteropInterfaceType:
|
||
|
return "interface{}"
|
||
|
case smartcontract.VoidType:
|
||
|
return ""
|
||
|
default:
|
||
|
panic("unreachable")
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func templateFromManifest(cfg Config) (contractTmpl, error) {
|
||
|
hStr := ""
|
||
|
for _, b := range cfg.Hash.BytesBE() {
|
||
|
hStr += fmt.Sprintf("\\x%02x", b)
|
||
|
}
|
||
|
|
||
|
ctr := contractTmpl{
|
||
|
PackageName: cfg.Package,
|
||
|
ContractName: cfg.Manifest.Name,
|
||
|
Hash: hStr,
|
||
|
}
|
||
|
if ctr.PackageName == "" {
|
||
|
buf := bytes.NewBuffer(make([]byte, 0, len(cfg.Manifest.Name)))
|
||
|
for _, r := range cfg.Manifest.Name {
|
||
|
if unicode.IsLetter(r) {
|
||
|
buf.WriteRune(unicode.ToLower(r))
|
||
|
}
|
||
|
}
|
||
|
|
||
|
ctr.PackageName = buf.String()
|
||
|
}
|
||
|
|
||
|
imports := make(map[string]struct{})
|
||
|
seen := make(map[string]bool)
|
||
|
for _, m := range cfg.Manifest.ABI.Methods {
|
||
|
seen[m.Name] = false
|
||
|
}
|
||
|
for _, m := range cfg.Manifest.ABI.Methods {
|
||
|
if m.Name[0] == '_' {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
imports["github.com/nspcc-dev/neo-go/pkg/interop/contract"] = struct{}{}
|
||
|
imports["github.com/nspcc-dev/neo-go/pkg/interop/neogointernal"] = struct{}{}
|
||
|
|
||
|
// Consider `perform(a)` and `perform(a, b)` methods.
|
||
|
// First, try to export the second method with `Perform2` name.
|
||
|
// If `perform2` is already in the manifest, use `perform_2` with as many underscores
|
||
|
// as needed to eliminate name conflicts. It will produce long names in certain circumstances,
|
||
|
// but if the manifest contains lots of similar names with trailing underscores, delicate naming
|
||
|
// was probably not the goal.
|
||
|
name := m.Name
|
||
|
if v, ok := seen[name]; !ok || v {
|
||
|
suffix := strconv.Itoa(len(m.Parameters))
|
||
|
for ; seen[name]; name = m.Name + suffix {
|
||
|
suffix = "_" + suffix
|
||
|
}
|
||
|
}
|
||
|
seen[name] = true
|
||
|
|
||
|
mtd := methodTmpl{
|
||
|
Name: upperFirst(name),
|
||
|
NameABI: m.Name,
|
||
|
CallFlag: callflag.All.String(),
|
||
|
Comment: fmt.Sprintf("invokes `%s` method of contract.", m.Name),
|
||
|
}
|
||
|
if f, ok := cfg.CallFlags[m.Name]; ok {
|
||
|
mtd.CallFlag = f.String()
|
||
|
} else if m.Safe {
|
||
|
mtd.CallFlag = callflag.ReadOnly.String()
|
||
|
}
|
||
|
for i := range m.Parameters {
|
||
|
name := m.Parameters[i].Name
|
||
|
if name == "" {
|
||
|
name = fmt.Sprintf("arg%d", i)
|
||
|
}
|
||
|
|
||
|
var typeStr string
|
||
|
if over, ok := cfg.Overrides[m.Name+"."+name]; ok {
|
||
|
typeStr = over.TypeName
|
||
|
if over.Package != "" {
|
||
|
imports[over.Package] = struct{}{}
|
||
|
}
|
||
|
} else {
|
||
|
typeStr = scTypeToGo(m.Parameters[i].Type)
|
||
|
}
|
||
|
|
||
|
mtd.Arguments = append(mtd.Arguments, paramTmpl{
|
||
|
Name: name,
|
||
|
Type: typeStr,
|
||
|
})
|
||
|
}
|
||
|
|
||
|
if over, ok := cfg.Overrides[m.Name]; ok {
|
||
|
mtd.ReturnType = over.TypeName
|
||
|
if over.Package != "" {
|
||
|
imports[over.Package] = struct{}{}
|
||
|
}
|
||
|
} else {
|
||
|
mtd.ReturnType = scTypeToGo(m.ReturnType)
|
||
|
switch m.ReturnType {
|
||
|
case smartcontract.Hash160Type, smartcontract.Hash256Type, smartcontract.InteropInterfaceType,
|
||
|
smartcontract.SignatureType, smartcontract.PublicKeyType:
|
||
|
imports["github.com/nspcc-dev/neo-go/pkg/interop"] = struct{}{}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
ctr.Methods = append(ctr.Methods, mtd)
|
||
|
}
|
||
|
|
||
|
for imp := range imports {
|
||
|
ctr.Imports = append(ctr.Imports, imp)
|
||
|
}
|
||
|
sort.Strings(ctr.Imports)
|
||
|
|
||
|
return ctr, nil
|
||
|
}
|
||
|
|
||
|
func upperFirst(s string) string {
|
||
|
return strings.ToUpper(s[0:1]) + s[1:]
|
||
|
}
|
||
|
|
||
|
func lowerFirst(s string) string {
|
||
|
return strings.ToLower(s[0:1]) + s[1:]
|
||
|
}
|