abigen: adding support for configurable template file for generating bindings

This commit is contained in:
Pablo La Greca 2025-05-05 11:35:32 -03:00
parent b135da2eac
commit ec72537db5
No known key found for this signature in database
5 changed files with 45 additions and 11 deletions

View file

@ -294,7 +294,7 @@ func iterSorted[V any](inp map[string]V, onItem func(string, V) error) error {
// to be used as is in client code, but rather as an intermediate struct which // to be used as is in client code, but rather as an intermediate struct which
// enforces compile time type safety and naming convention as opposed to having to // enforces compile time type safety and naming convention as opposed to having to
// manually maintain hard coded strings that break on runtime. // manually maintain hard coded strings that break on runtime.
func BindV2(types []string, abis []string, bytecodes []string, pkg string, libs map[string]string, aliases map[string]string) (string, error) { func BindV2(types []string, abis []string, bytecodes []string, pkg string, libs map[string]string, aliases map[string]string, templateContent string) (string, error) {
b := binder{ b := binder{
contracts: make(map[string]*tmplContractV2), contracts: make(map[string]*tmplContractV2),
structs: make(map[string]*tmplStruct), structs: make(map[string]*tmplStruct),
@ -360,7 +360,7 @@ func BindV2(types []string, abis []string, bytecodes []string, pkg string, libs
"ispointertype": isPointerType, "ispointertype": isPointerType,
"underlyingbindtype": underlyingBindType, "underlyingbindtype": underlyingBindType,
} }
tmpl := template.Must(template.New("").Funcs(funcs).Parse(tmplSourceV2)) tmpl := template.Must(template.New("").Funcs(funcs).Parse(templateContent))
if err := tmpl.Execute(buffer, data); err != nil { if err := tmpl.Execute(buffer, data); err != nil {
return "", err return "", err
} }

View file

@ -58,7 +58,7 @@ func bindCombinedJSON(test *bindV2Test) (string, error) {
if test.aliases == nil { if test.aliases == nil {
test.aliases = make(map[string]string) test.aliases = make(map[string]string)
} }
code, err := BindV2(types, abis, bins, "bindtests", libs, test.aliases) code, err := BindV2(types, abis, bins, "bindtests", libs, test.aliases, TmplSourceV2)
if err != nil { if err != nil {
return "", fmt.Errorf("error creating bindings: %v", err) return "", fmt.Errorf("error creating bindings: %v", err)
} }

View file

@ -133,4 +133,4 @@ var tmplSource string
// for abigen v2 is based on. // for abigen v2 is based on.
// //
//go:embed source2.go.tpl //go:embed source2.go.tpl
var tmplSourceV2 string var TmplSourceV2 string

View file

@ -40,6 +40,26 @@ import (
// TestBindingGeneration tests that re-running generation of bindings does not result in // TestBindingGeneration tests that re-running generation of bindings does not result in
// mutations to the binding code. // mutations to the binding code.
func TestBindingGeneration(t *testing.T) { func TestBindingGeneration(t *testing.T) {
BindGenerationWithExpectedOutput(t, abigen.TmplSourceV2, getCodeFromBindingsDotGo)
}
func TestBindingGenerationWithDifferentTemplate(t *testing.T) {
BindGenerationWithExpectedOutput(t, "package {{.Package}} //test template", getHarcodedCodeFromBindingsDotGo)
}
func getHarcodedCodeFromBindingsDotGo(t *testing.T, basePath string) string {
return "package " + strings.Replace(basePath, "internal/contracts/", "", -1) + " //test template\n"
}
func getCodeFromBindingsDotGo(t *testing.T, basePath string) string {
existingBindings, err := os.ReadFile(filepath.Join(basePath, "bindings.go"))
if err != nil {
t.Fatalf("ReadFile returned error: %v", err)
}
return string(existingBindings)
}
func BindGenerationWithExpectedOutput(t *testing.T, templateContent string, getExpectedCodeFunc func(t *testing.T, basePath string) string) {
matches, _ := filepath.Glob("internal/contracts/*") matches, _ := filepath.Glob("internal/contracts/*")
var dirs []string var dirs []string
for _, match := range matches { for _, match := range matches {
@ -86,16 +106,13 @@ func TestBindingGeneration(t *testing.T) {
libPattern := crypto.Keccak256Hash([]byte(name)).String()[2:36] // the first 2 chars are 0x libPattern := crypto.Keccak256Hash([]byte(name)).String()[2:36] // the first 2 chars are 0x
libs[libPattern] = typeName libs[libPattern] = typeName
} }
code, err := abigen.BindV2(types, abis, bins, dir, libs, make(map[string]string)) code, err := abigen.BindV2(types, abis, bins, dir, libs, make(map[string]string), templateContent)
if err != nil { if err != nil {
t.Fatalf("error creating bindings for package %s: %v", dir, err) t.Fatalf("error creating bindings for package %s: %v", dir, err)
} }
existingBindings, err := os.ReadFile(filepath.Join(basePath, "bindings.go")) expectedCode := getExpectedCodeFunc(t, basePath)
if err != nil { if code != expectedCode {
t.Fatalf("ReadFile returned error: %v", err)
}
if code != string(existingBindings) {
t.Fatalf("code mismatch for %s", dir) t.Fatalf("code mismatch for %s", dir)
} }
} }

View file

@ -67,6 +67,10 @@ var (
Name: "alias", Name: "alias",
Usage: "Comma separated aliases for function and event renaming. If --v2 is set, errors are aliased as well. e.g. original1=alias1, original2=alias2", Usage: "Comma separated aliases for function and event renaming. If --v2 is set, errors are aliased as well. e.g. original1=alias1, original2=alias2",
} }
templateFlag = &cli.StringFlag{
Name: "template",
Usage: "Input file to use as template for generating bindings",
}
v2Flag = &cli.BoolFlag{ v2Flag = &cli.BoolFlag{
Name: "v2", Name: "v2",
Usage: "Generates v2 bindings", Usage: "Generates v2 bindings",
@ -86,6 +90,7 @@ func init() {
pkgFlag, pkgFlag,
outFlag, outFlag,
aliasFlag, aliasFlag,
templateFlag,
v2Flag, v2Flag,
} }
app.Action = generate app.Action = generate
@ -100,6 +105,9 @@ func generate(c *cli.Context) error {
if c.String(abiFlag.Name) == "" && c.String(jsonFlag.Name) == "" { if c.String(abiFlag.Name) == "" && c.String(jsonFlag.Name) == "" {
utils.Fatalf("Either contract ABI source (--abi) or combined-json (--combined-json) are required") utils.Fatalf("Either contract ABI source (--abi) or combined-json (--combined-json) are required")
} }
if !c.Bool(v2Flag.Name) && c.IsSet(templateFlag.Name) {
utils.Fatalf("Template configurabion only works with abigen v2")
}
// If the entire solidity code was specified, build and bind based on that // If the entire solidity code was specified, build and bind based on that
var ( var (
abis []string abis []string
@ -216,7 +224,16 @@ func generate(c *cli.Context) error {
err error err error
) )
if c.IsSet(v2Flag.Name) { if c.IsSet(v2Flag.Name) {
code, err = abigen.BindV2(types, abis, bins, c.String(pkgFlag.Name), libs, aliases) template := abigen.TmplSourceV2
if c.IsSet(templateFlag.Name) {
templateLoc := c.String(templateFlag.Name)
templateData, err := os.ReadFile(templateLoc)
if err != nil {
utils.Fatalf("Error reading template file: %s, error: %", templateLoc, err)
}
template = string(templateData)
}
code, err = abigen.BindV2(types, abis, bins, c.String(pkgFlag.Name), libs, aliases, template)
} else { } else {
code, err = abigen.Bind(types, abis, bins, sigs, c.String(pkgFlag.Name), libs, aliases) code, err = abigen.Bind(types, abis, bins, sigs, c.String(pkgFlag.Name), libs, aliases)
} }