github.com/ethereumproject/go-ethereum@v5.5.2+incompatible/common/compiler/solidity.go (about)

     1  // Copyright 2015 The go-ethereum Authors
     2  // This file is part of the go-ethereum library.
     3  //
     4  // The go-ethereum library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // The go-ethereum library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package compiler
    18  
    19  import (
    20  	"bytes"
    21  	"encoding/json"
    22  	"errors"
    23  	"fmt"
    24  	"io/ioutil"
    25  	"os"
    26  	"os/exec"
    27  	"path/filepath"
    28  	"regexp"
    29  	"strings"
    30  
    31  	"github.com/ethereumproject/go-ethereum/common"
    32  	"github.com/ethereumproject/go-ethereum/crypto"
    33  	"github.com/ethereumproject/go-ethereum/logger"
    34  	"github.com/ethereumproject/go-ethereum/logger/glog"
    35  )
    36  
    37  var (
    38  	versionRegexp = regexp.MustCompile("[0-9]+\\.[0-9]+\\.[0-9]+")
    39  	legacyRegexp  = regexp.MustCompile("0\\.(9\\..*|1\\.[01])")
    40  	paramsLegacy  = []string{
    41  		"--binary",       // Request to output the contract in binary (hexadecimal).
    42  		"file",           //
    43  		"--json-abi",     // Request to output the contract's JSON ABI interface.
    44  		"file",           //
    45  		"--natspec-user", // Request to output the contract's Natspec user documentation.
    46  		"file",           //
    47  		"--natspec-dev",  // Request to output the contract's Natspec developer documentation.
    48  		"file",
    49  		"1",
    50  	}
    51  	paramsNew = []string{
    52  		"--bin",      // Request to output the contract in binary (hexadecimal).
    53  		"--abi",      // Request to output the contract's JSON ABI interface.
    54  		"--userdoc",  // Request to output the contract's Natspec user documentation.
    55  		"--devdoc",   // Request to output the contract's Natspec developer documentation.
    56  		"--optimize", // code optimizer switched on
    57  		"-o",         // output directory
    58  	}
    59  )
    60  
    61  type Contract struct {
    62  	Code string       `json:"code"`
    63  	Info ContractInfo `json:"info"`
    64  }
    65  
    66  type ContractInfo struct {
    67  	Source          string      `json:"source"`
    68  	Language        string      `json:"language"`
    69  	LanguageVersion string      `json:"languageVersion"`
    70  	CompilerVersion string      `json:"compilerVersion"`
    71  	CompilerOptions string      `json:"compilerOptions"`
    72  	AbiDefinition   interface{} `json:"abiDefinition"`
    73  	UserDoc         interface{} `json:"userDoc"`
    74  	DeveloperDoc    interface{} `json:"developerDoc"`
    75  }
    76  
    77  type Solidity struct {
    78  	solcPath    string
    79  	version     string
    80  	fullVersion string
    81  	legacy      bool
    82  }
    83  
    84  func New(solcPath string) (sol *Solidity, err error) {
    85  	// set default solc
    86  	if len(solcPath) == 0 {
    87  		solcPath = "solc"
    88  	}
    89  	solcPath, err = exec.LookPath(solcPath)
    90  	if err != nil {
    91  		return
    92  	}
    93  
    94  	cmd := exec.Command(solcPath, "--version")
    95  	var out bytes.Buffer
    96  	cmd.Stdout = &out
    97  	err = cmd.Run()
    98  	if err != nil {
    99  		return
   100  	}
   101  
   102  	fullVersion := out.String()
   103  	version := versionRegexp.FindString(fullVersion)
   104  	legacy := legacyRegexp.MatchString(version)
   105  
   106  	sol = &Solidity{
   107  		solcPath:    solcPath,
   108  		version:     version,
   109  		fullVersion: fullVersion,
   110  		legacy:      legacy,
   111  	}
   112  	glog.V(logger.Info).Infoln(sol.Info())
   113  	return
   114  }
   115  
   116  func (sol *Solidity) Info() string {
   117  	return fmt.Sprintf("%s\npath: %s", sol.fullVersion, sol.solcPath)
   118  }
   119  
   120  func (sol *Solidity) Version() string {
   121  	return sol.version
   122  }
   123  
   124  // Compile builds and returns all the contracts contained within a source string.
   125  func (sol *Solidity) Compile(source string) (map[string]*Contract, error) {
   126  	// Short circuit if no source code was specified
   127  	if len(source) == 0 {
   128  		return nil, errors.New("solc: empty source string")
   129  	}
   130  	// Create a safe place to dump compilation output
   131  	wd, err := ioutil.TempDir("", "solc")
   132  	if err != nil {
   133  		return nil, fmt.Errorf("solc: failed to create temporary build folder: %v", err)
   134  	}
   135  	defer os.RemoveAll(wd)
   136  
   137  	// Assemble the compiler command, change to the temp folder and capture any errors
   138  	stderr := new(bytes.Buffer)
   139  
   140  	var params []string
   141  	if sol.legacy {
   142  		params = paramsLegacy
   143  	} else {
   144  		params = paramsNew
   145  		params = append(params, wd)
   146  	}
   147  	compilerOptions := strings.Join(params, " ")
   148  
   149  	cmd := exec.Command(sol.solcPath, params...)
   150  	cmd.Stdin = strings.NewReader(source)
   151  	cmd.Stderr = stderr
   152  
   153  	if err := cmd.Run(); err != nil {
   154  		return nil, fmt.Errorf("solc: %v\n%s", err, string(stderr.Bytes()))
   155  	}
   156  	// Sanity check that something was actually built
   157  	matches, _ := filepath.Glob(filepath.Join(wd, "*.bin*"))
   158  	if len(matches) < 1 {
   159  		return nil, fmt.Errorf("solc: no build results found")
   160  	}
   161  	// Compilation succeeded, assemble and return the contracts
   162  	contracts := make(map[string]*Contract)
   163  	for _, path := range matches {
   164  		_, file := filepath.Split(path)
   165  		base := strings.Split(file, ".")[0]
   166  
   167  		// Parse the individual compilation results (code binary, ABI definitions, user and dev docs)
   168  		var binary []byte
   169  		binext := ".bin"
   170  		if sol.legacy {
   171  			binext = ".binary"
   172  		}
   173  		if binary, err = ioutil.ReadFile(filepath.Join(wd, base+binext)); err != nil {
   174  			return nil, fmt.Errorf("solc: error reading compiler output for code: %v", err)
   175  		}
   176  
   177  		var abi interface{}
   178  		if blob, err := ioutil.ReadFile(filepath.Join(wd, base+".abi")); err != nil {
   179  			return nil, fmt.Errorf("solc: error reading abi definition: %v", err)
   180  		} else if err = json.Unmarshal(blob, &abi); err != nil {
   181  			return nil, fmt.Errorf("solc: error parsing abi definition: %v", err)
   182  		}
   183  
   184  		var userdoc interface{}
   185  		if blob, err := ioutil.ReadFile(filepath.Join(wd, base+".docuser")); err != nil {
   186  			return nil, fmt.Errorf("solc: error reading user doc: %v", err)
   187  		} else if err = json.Unmarshal(blob, &userdoc); err != nil {
   188  			return nil, fmt.Errorf("solc: error parsing user doc: %v", err)
   189  		}
   190  
   191  		var devdoc interface{}
   192  		if blob, err := ioutil.ReadFile(filepath.Join(wd, base+".docdev")); err != nil {
   193  			return nil, fmt.Errorf("solc: error reading dev doc: %v", err)
   194  		} else if err = json.Unmarshal(blob, &devdoc); err != nil {
   195  			return nil, fmt.Errorf("solc: error parsing dev doc: %v", err)
   196  		}
   197  		// Assemble the final contract
   198  		contracts[base] = &Contract{
   199  			Code: "0x" + string(binary),
   200  			Info: ContractInfo{
   201  				Source:          source,
   202  				Language:        "Solidity",
   203  				LanguageVersion: sol.version,
   204  				CompilerVersion: sol.version,
   205  				CompilerOptions: compilerOptions,
   206  				AbiDefinition:   abi,
   207  				UserDoc:         userdoc,
   208  				DeveloperDoc:    devdoc,
   209  			},
   210  		}
   211  	}
   212  	return contracts, nil
   213  }
   214  
   215  func SaveInfo(info *ContractInfo, filename string) (contenthash common.Hash, err error) {
   216  	infojson, err := json.Marshal(info)
   217  	if err != nil {
   218  		return
   219  	}
   220  	contenthash = common.BytesToHash(crypto.Keccak256(infojson))
   221  	err = ioutil.WriteFile(filename, infojson, 0600)
   222  	return
   223  }