github.com/iotexproject/iotex-core@v1.14.1-rc1/ioctl/util/compiler_contract.go (about)

     1  // Copyright (c) 2022 IoTeX Foundation
     2  // This source code is provided 'as is' and no warranties are given as to title or non-infringement, merchantability
     3  // or fitness for purpose and, to the extent permitted by law, all liability for your use of the code is disclaimed.
     4  // This source code is governed by Apache License 2.0 that can be found in the LICENSE file.
     5  
     6  package util
     7  
     8  import (
     9  	"bytes"
    10  	"os"
    11  	"os/exec"
    12  	"regexp"
    13  	"strconv"
    14  	"strings"
    15  
    16  	"github.com/ethereum/go-ethereum/common/compiler"
    17  	"github.com/pkg/errors"
    18  )
    19  
    20  var versionRegexp = regexp.MustCompile(`([0-9]+)\.([0-9]+)\.([0-9]+)`)
    21  
    22  // Solidity contains information about the solidity compiler.
    23  type Solidity struct {
    24  	Path, Version, FullVersion string
    25  	Major, Minor, Patch        int
    26  	ExtraAllowedPath           []string
    27  }
    28  
    29  // SolidityVersion runs solc and parses its version output.
    30  func SolidityVersion(solc string) (*Solidity, error) {
    31  	if solc == "" {
    32  		solc = "solc"
    33  	}
    34  	var out bytes.Buffer
    35  	cmd := exec.Command(solc, "--version")
    36  	cmd.Stdout = &out
    37  	err := cmd.Run()
    38  	if err != nil {
    39  		return nil, err
    40  	}
    41  	matches := versionRegexp.FindStringSubmatch(out.String())
    42  	if len(matches) != 4 {
    43  		return nil, errors.Errorf("can't parse solc version %q", out.String())
    44  	}
    45  	s := &Solidity{Path: cmd.Path, FullVersion: out.String(), Version: matches[0]}
    46  	if s.Major, err = strconv.Atoi(matches[1]); err != nil {
    47  		return nil, err
    48  	}
    49  	if s.Minor, err = strconv.Atoi(matches[2]); err != nil {
    50  		return nil, err
    51  	}
    52  	if s.Patch, err = strconv.Atoi(matches[3]); err != nil {
    53  		return nil, err
    54  	}
    55  	return s, nil
    56  }
    57  
    58  // CompileSolidityString builds and returns all the contracts contained within a source string.
    59  func CompileSolidityString(solc, source string) (map[string]*compiler.Contract, error) {
    60  	if len(source) == 0 {
    61  		return nil, errors.New("solc: empty source string")
    62  	}
    63  	s, err := SolidityVersion(solc)
    64  	if err != nil {
    65  		return nil, err
    66  	}
    67  	return s.CompileSource(source)
    68  }
    69  
    70  // CompileSolidity compiles all given Solidity source files.
    71  func CompileSolidity(solc string, sourcefiles ...string) (map[string]*compiler.Contract, error) {
    72  	if len(sourcefiles) == 0 {
    73  		return nil, errors.New("solc: no source files")
    74  	}
    75  	s, err := SolidityVersion(solc)
    76  	if err != nil {
    77  		return nil, err
    78  	}
    79  
    80  	return s.CompileFiles(sourcefiles...)
    81  }
    82  
    83  // CompileSource builds and returns all the contracts contained within a source string.
    84  func (s *Solidity) CompileSource(source string) (map[string]*compiler.Contract, error) {
    85  	args := append(s.makeArgs(), "--")
    86  	cmd := exec.Command(s.Path, append(args, "-")...)
    87  	cmd.Stdin = strings.NewReader(source)
    88  	return s.run(cmd, source)
    89  }
    90  
    91  // CompileFiles compiles all given Solidity source files.
    92  func (s *Solidity) CompileFiles(sourcefiles ...string) (map[string]*compiler.Contract, error) {
    93  	source, err := slurpFiles(sourcefiles)
    94  	if err != nil {
    95  		return nil, err
    96  	}
    97  	args := append(s.makeArgs(), "--")
    98  	cmd := exec.Command(s.Path, append(args, sourcefiles...)...)
    99  	return s.run(cmd, source)
   100  }
   101  
   102  func (s *Solidity) run(cmd *exec.Cmd, source string) (map[string]*compiler.Contract, error) {
   103  	var stderr, stdout bytes.Buffer
   104  	cmd.Stderr = &stderr
   105  	cmd.Stdout = &stdout
   106  	if err := cmd.Run(); err != nil {
   107  		return nil, errors.Errorf("solc: %v\n%s", err, stderr.Bytes())
   108  	}
   109  	return compiler.ParseCombinedJSON(stdout.Bytes(), source, s.Version, s.Version, strings.Join(s.makeArgs(), " "))
   110  }
   111  
   112  func (s *Solidity) allowedPaths() string {
   113  	paths := []string{".", "./", "../"} // default to support relative paths
   114  	if len(s.ExtraAllowedPath) > 0 {
   115  		paths = append(paths, s.ExtraAllowedPath...)
   116  	}
   117  	return strings.Join(paths, ", ")
   118  }
   119  
   120  func (s *Solidity) makeArgs() []string {
   121  	p := []string{
   122  		"--combined-json", "bin,bin-runtime,srcmap,srcmap-runtime,abi,userdoc,devdoc",
   123  		"--optimize", // code optimizer switched on
   124  		"--allow-paths", s.allowedPaths(),
   125  	}
   126  	if s.Major > 0 || s.Minor > 4 || s.Patch > 6 {
   127  		p[1] += ",metadata,hashes"
   128  	}
   129  	return p
   130  }
   131  
   132  func slurpFiles(files []string) (string, error) {
   133  	var concat bytes.Buffer
   134  	for _, file := range files {
   135  		content, err := os.ReadFile(file)
   136  		if err != nil {
   137  			return "", err
   138  		}
   139  		concat.Write(content)
   140  	}
   141  	return concat.String(), nil
   142  }