github.com/0xPolygon/supernets2-node@v0.0.0-20230711153321-2fe574524eaa/test/scripts/cmd/compilesc/manager.go (about)

     1  package compilesc
     2  
     3  import (
     4  	"fmt"
     5  	"io/fs"
     6  	"os"
     7  	"os/exec"
     8  	"os/user"
     9  	"path"
    10  	"path/filepath"
    11  	"runtime"
    12  	"strings"
    13  
    14  	"github.com/0xPolygon/supernets2-node/log"
    15  	"golang.org/x/sync/errgroup"
    16  	"gopkg.in/yaml.v2"
    17  )
    18  
    19  const (
    20  	defaultAbigenImage = "ethereum/client-go:alltools-latest"
    21  	defaultSolcImage   = "ethereum/solc"
    22  
    23  	containerBase = "/contracts"
    24  )
    25  
    26  // CompileUnit represents a single contract to be compiled.
    27  type CompileUnit struct {
    28  	Name        string `yaml:"name"`
    29  	SolcVersion string `yaml:"solcVersion"`
    30  	InputPath   string `yaml:"inputPath"`
    31  	OutputPath  string `yaml:"outputPath"`
    32  }
    33  
    34  // CompileUnits represents data for all the contracts to be compiled.
    35  type CompileUnits struct {
    36  	Parallel   []CompileUnit `yaml:"parallel"`
    37  	Sequential []CompileUnit `yaml:"sequential"`
    38  }
    39  
    40  type compileIndex struct {
    41  	CompileUnits CompileUnits `yaml:"compileUnits"`
    42  }
    43  
    44  // Manager handles smart contract compilation.
    45  type Manager struct {
    46  	basePath         string
    47  	absoluteBasePath string
    48  	currentUser      *user.User
    49  }
    50  
    51  // NewManager is the Manager constructor.
    52  func NewManager(basePath string) (*Manager, error) {
    53  	_, filename, _, _ := runtime.Caller(0)
    54  	dir := path.Join(path.Dir(filename), "../../../")
    55  	absoluteBasePath := path.Join(dir, basePath)
    56  
    57  	currentUser, err := user.Current()
    58  	if err != nil {
    59  		return nil, err
    60  	}
    61  
    62  	return &Manager{
    63  		basePath:         basePath,
    64  		absoluteBasePath: absoluteBasePath,
    65  		currentUser:      currentUser,
    66  	}, nil
    67  }
    68  
    69  // Run executes the compilation of smart contracts and generation of golang
    70  // bindings.
    71  func (cm *Manager) Run() error {
    72  	yamlFile, err := os.ReadFile(path.Join(cm.absoluteBasePath, "index.yaml"))
    73  	if err != nil {
    74  		return err
    75  	}
    76  	ci := &compileIndex{}
    77  	err = yaml.Unmarshal(yamlFile, ci)
    78  	if err != nil {
    79  		return err
    80  	}
    81  
    82  	g := new(errgroup.Group)
    83  
    84  	g.Go(func() error {
    85  		for _, item := range ci.CompileUnits.Parallel {
    86  			err = cm.parallelActions(item)
    87  			if err != nil {
    88  				return err
    89  			}
    90  		}
    91  		return nil
    92  	})
    93  
    94  	g.Go(func() error {
    95  		for _, item := range ci.CompileUnits.Sequential {
    96  			err = cm.sequentialActions(item)
    97  			if err != nil {
    98  				return err
    99  			}
   100  		}
   101  		return nil
   102  	})
   103  
   104  	return g.Wait()
   105  }
   106  
   107  // parallelActions performs compile and generate actions in parallel for a given
   108  // compile unit.
   109  func (cm *Manager) parallelActions(item CompileUnit) error {
   110  	entryPoint := path.Join(cm.basePath, item.InputPath)
   111  	file, err := os.Open(entryPoint) // #nosec G304
   112  	if err != nil {
   113  		return err
   114  	}
   115  	defer func() {
   116  		if err := file.Close(); err != nil {
   117  			log.Errorf("Could not close file %q, %v", file.Name(), err)
   118  		}
   119  	}()
   120  
   121  	fileInfo, err := file.Stat()
   122  	if err != nil {
   123  		return err
   124  	}
   125  
   126  	if !fileInfo.IsDir() {
   127  		return cm.fileActions(path.Base(file.Name()), item.SolcVersion, item.InputPath, item.OutputPath)
   128  	}
   129  
   130  	g := new(errgroup.Group)
   131  
   132  	g.Go(func() error {
   133  		return filepath.WalkDir(file.Name(), func(target string, info fs.DirEntry, err error) error {
   134  			if err != nil {
   135  				return err
   136  			}
   137  			if info == nil || info.IsDir() {
   138  				return nil
   139  			}
   140  			if filepath.Ext(target) != ".sol" {
   141  				return nil
   142  			}
   143  			fileName := strings.TrimSuffix(target, ".sol")
   144  
   145  			g.Go(func() error {
   146  				return cm.fileActions(path.Base(fileName), item.SolcVersion, item.InputPath, item.OutputPath)
   147  			})
   148  			return nil
   149  		})
   150  	})
   151  	return g.Wait()
   152  }
   153  
   154  // sequentialActions performs compile and generate actions sequentially for a given
   155  // compile unit.
   156  func (cm *Manager) sequentialActions(item CompileUnit) error {
   157  	entryPoint := path.Join(cm.basePath, item.InputPath, fmt.Sprintf("%s.sol", item.Name))
   158  	file, err := os.Open(entryPoint) // #nosec G304
   159  	if err != nil && err != os.ErrNotExist {
   160  		return err
   161  	}
   162  	defer func() {
   163  		if err := file.Close(); err != nil {
   164  			log.Errorf("Could not close file %q, %v", file.Name(), err)
   165  		}
   166  	}()
   167  
   168  	return cm.fileActions(item.Name, item.SolcVersion, item.InputPath, item.OutputPath)
   169  }
   170  
   171  func (cm *Manager) fileActions(name, solcVersion, inputPath, outputPath string) error {
   172  	err := cm.Compile(path.Base(name), solcVersion, inputPath, outputPath)
   173  	if err != nil {
   174  		return err
   175  	}
   176  
   177  	return cm.Abigen(path.Base(name), inputPath, outputPath)
   178  }
   179  
   180  // Compile invokes solc on the given sol file.
   181  func (cm *Manager) Compile(name, solcVersion, inputPath, outputPath string) error {
   182  	log.Infof("Compiling %s.sol with version %s", path.Join(cm.basePath, inputPath, name), solcVersion)
   183  
   184  	solcImage := fmt.Sprintf("%s:%s", defaultSolcImage, solcVersion)
   185  
   186  	c := exec.Command(
   187  		"docker", "run", "--rm",
   188  		"--user", fmt.Sprintf("%s:%s", cm.currentUser.Uid, cm.currentUser.Gid),
   189  		"-v", fmt.Sprintf("%s:%s", cm.absoluteBasePath, containerBase),
   190  		solcImage,
   191  		"-",
   192  		fmt.Sprintf("%s.sol", path.Join(containerBase, inputPath, name)),
   193  		"-o", path.Join(containerBase, "bin", outputPath, name),
   194  		"--abi", "--bin", "--overwrite", "--optimize") // #nosec G204
   195  
   196  	envPath := os.Getenv("PATH")
   197  	c.Env = []string{fmt.Sprintf("PATH=%s", envPath)}
   198  
   199  	err := c.Run()
   200  	if err != nil {
   201  		return err
   202  	}
   203  	log.Infof("Compiler run successfully, artifacts can be found at %q", path.Join(cm.basePath, "bin", name))
   204  	return nil
   205  }
   206  
   207  // Abigen generates bindings for the given file
   208  func (cm *Manager) Abigen(name, inputPath, outputPath string) error {
   209  	log.Infof("Generating go code for %q...", name)
   210  
   211  	c := exec.Command(
   212  		"docker", "run", "--rm",
   213  		"--user", fmt.Sprintf("%s:%s", cm.currentUser.Uid, cm.currentUser.Gid),
   214  		"-v", fmt.Sprintf("%s:%s", cm.absoluteBasePath, containerBase),
   215  		defaultAbigenImage,
   216  		"abigen",
   217  		"--bin", path.Join(containerBase, "bin", outputPath, name, fmt.Sprintf("%s.bin", name)),
   218  		"--abi", path.Join(containerBase, "bin", outputPath, name, fmt.Sprintf("%s.abi", name)),
   219  		"--pkg", name,
   220  		"--out", path.Join(containerBase, "bin", outputPath, name, fmt.Sprintf("%s.go", name))) // #nosec G204
   221  
   222  	envPath := os.Getenv("PATH")
   223  	c.Env = []string{fmt.Sprintf("PATH=%s", envPath)}
   224  
   225  	err := c.Run()
   226  	if err != nil {
   227  		return err
   228  	}
   229  	log.Infof("Code generated at %q", path.Join(cm.basePath, "bin", outputPath, name, fmt.Sprintf("%s.go", name)))
   230  	return nil
   231  }