Skip to content

Commit

Permalink
Add minimal implementation of the hardhat abigen generator helper
Browse files Browse the repository at this point in the history
  • Loading branch information
vpavlin committed Jul 29, 2022
1 parent d7b4dca commit 2def14d
Show file tree
Hide file tree
Showing 9 changed files with 294 additions and 0 deletions.
2 changes: 2 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
build:
go build -o hardhat-abigen main.go
8 changes: 8 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
module github.com/vpavlin/hardhat-abigen

go 1.18

require (
github.com/sirupsen/logrus v1.9.0 // indirect
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect
)
11 changes: 11 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
28 changes: 28 additions & 0 deletions internal/abigen/abigen.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package abigen

import (
"bytes"
"fmt"
"os/exec"
)

func Run(in string, typ string, pkg string, output string) error {
args := []string{
"-abi", in,
"-out", output,
"-type", typ,
"-pkg", pkg,
}

cmd := exec.Command("abigen", args...)

var stdErr bytes.Buffer
cmd.Stderr = &stdErr

err := cmd.Run()
if err != nil {
return fmt.Errorf("Failed to generate bindings: %s, stderr: %s", err, stdErr.String())
}

return nil
}
165 changes: 165 additions & 0 deletions internal/extractor/extractor.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
package extractor

import (
"encoding/json"
"fmt"
"io"
"io/fs"
"io/ioutil"
"os"
"path"
"path/filepath"
"strings"

"github.com/sirupsen/logrus"
"github.com/vpavlin/hardhat-abigen/internal/abigen"
"github.com/vpavlin/hardhat-abigen/internal/types"
"github.com/vpavlin/hardhat-abigen/internal/utils"
)

func Extract(in io.Reader) (*types.ABI, error) {
bytes, err := ioutil.ReadAll(in)
if err != nil {
return nil, err
}
abi := new(types.ABI)
err = json.Unmarshal(bytes, abi)
if err != nil {
return nil, err
}

if abi.ABI == nil || len(abi.ABI.([]interface{})) == 0 || abi.ByteCode == "" || abi.ByteCode == "0x" {
return nil, nil
}

return abi, nil
}

func Dump(out io.Writer, abi *types.ABI) error {
marshalled, err := json.Marshal(abi.ABI)
if err != nil {
return err
}

_, err = out.Write(marshalled)
if err != nil {
return err
}

return nil
}

func ExtractFromFile(in string, out string) (string, error) {
path, err := filepath.Abs(in)
if err != nil {
return "", nil
}

fp, err := os.Open(path)
if err != nil {
return "", err
}
defer fp.Close()

abi, err := Extract(fp)
if err != nil {
return "", err
}

if abi == nil {
return "", nil
}

fpOut, err := os.Create(out)
if err != nil {
return "", err
}
defer fpOut.Close()

err = Dump(fpOut, abi)
if err != nil {
return "", nil
}

return abi.ContractName, nil
}

func ExtractWalk(outDir string, p string, info fs.FileInfo, err error) error {
if err != nil {
return err
}

if info.IsDir() {
dir, err := ioutil.ReadDir(p)
if err != nil {
return err
}
for _, fInfo := range dir {
newPath := path.Join(p, fInfo.Name())
filepath.WalkDir(newPath, func(path string, d fs.DirEntry, err error) error {
return nil //ExtractWalk(path, info, err)
})
}
}

//outFile := path.Join(outDir, info.Name

return nil

}

func Excluded(toCheck string, exclude string) bool {
for _, item := range strings.Split(exclude, ",") {
if strings.Contains(toCheck, item) {
return true
}
}
return false
}

func ProcessSingle(inFile string, outDir string, generateBindings bool, exclude string) error {
if Excluded(inFile, exclude) {
logrus.Infof("Skipping path %s based on --exclude", inFile)
return nil
}

abiOutDir, err := utils.MkOutDirIfNotExist(outDir, utils.AbiDir)
if err != nil {
return err
}

if len(inFile) == 0 {
return fmt.Errorf("You need to provide a patch to the JSON file containing ABI produced by Hardhat")
}

abiOutFile := path.Join(abiOutDir, filepath.Base(inFile))

contractName, err := ExtractFromFile(inFile, abiOutFile)
if err != nil {
return fmt.Errorf("Failed to extract abi: %s", err)
}

if generateBindings && len(contractName) > 0 {
bindingsOutDir, err := utils.MkOutDirIfNotExist(outDir, utils.BindingsDir)
if err != nil {
return err
}
bindingsOutFile := path.Join(bindingsOutDir, fmt.Sprintf("%s.%s", contractName, "go"))
logrus.Infof("Generating bindings for %s", contractName)
err = abigen.Run(abiOutFile, contractName, "bindings", bindingsOutFile)
if err != nil {
return err
}
}

return nil
}

func ProcessAll(inFile string, outDir string, generateBindings bool, exclude string) error {
return filepath.Walk(inFile, func(p string, info fs.FileInfo, err error) error {
if filepath.Ext(p) == ".json" {
return ProcessSingle(p, outDir, generateBindings, exclude)
}
return nil
})
}
7 changes: 7 additions & 0 deletions internal/types/types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package types

type ABI struct {
ContractName string `json:"contractName"`
ABI interface{} `json:"abi"`
ByteCode string `json:"bytecode"`
}
6 changes: 6 additions & 0 deletions internal/utils/constants.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package utils

const (
AbiDir = "abi"
BindingsDir = "bindings"
)
24 changes: 24 additions & 0 deletions internal/utils/utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package utils

import (
"fmt"
"os"
"path"
)

func MkOutDirIfNotExist(prefix string, dir string) (string, error) {
newPath := path.Join(prefix, dir)
info, err := os.Stat(newPath)
if err != nil {
err = os.MkdirAll(newPath, 0777)
if err != nil {
return "", fmt.Errorf("Failed to create output dir: %s", err)
}
} else {
if !info.IsDir() {
return "", fmt.Errorf("Output path already exists, but is not a directory.")
}
}

return newPath, nil
}
43 changes: 43 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package main

import (
"flag"
"fmt"
"os"

"github.com/sirupsen/logrus"
"github.com/vpavlin/hardhat-abigen/internal/extractor"
)

func main() {

flag.Usage = func() {
fmt.Fprintf(os.Stderr, "Usage: %s [options] [FILE|DIRECTORY]\n", os.Args[0])

flag.VisitAll(func(f *flag.Flag) {
fmt.Fprintf(os.Stderr, " --%s\n\t %v (default: '%s')\n", f.Name, f.Usage, f.DefValue) // f.Name, f.Value
})
}

outDir := flag.String("outDir", "output", "Destination directory for extracted ABI json files and generated bindings")
generateBindings := flag.Bool("bindings", true, "Use Abigen to generate bindings")
exclude := flag.String("exclude", "", "List of strings to match aginst paths to exclude them from processing")

flag.Parse()

inFile := flag.Arg(0)

info, err := os.Stat(inFile)
if err != nil {
logrus.Errorf("No file provided")
flag.Usage()
os.Exit(1)
}

if info.IsDir() {
extractor.ProcessAll(inFile, *outDir, *generateBindings, *exclude)
} else {
extractor.ProcessSingle(inFile, *outDir, *generateBindings, *exclude)
}

}

0 comments on commit 2def14d

Please sign in to comment.