solidity.go 8 KB
Newer Older
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 6 7 8
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
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 13 14
// GNU Lesser General Public License for more details.
//
// 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 wraps the Solidity and Vyper compiler executables (solc; vyper).
18 19 20 21 22
package compiler

import (
	"bytes"
	"encoding/json"
23
	"errors"
24 25
	"fmt"
	"os/exec"
26
	"strconv"
27 28 29
	"strings"
)

30
// Solidity contains information about the solidity compiler.
31
type Solidity struct {
32
	Path, Version, FullVersion string
33
	Major, Minor, Patch        int
34
	ExtraAllowedPath           []string
35 36
}

37 38
// --combined-output format
type solcOutput struct {
39
	Contracts map[string]struct {
40 41 42
		BinRuntime                                  string `json:"bin-runtime"`
		SrcMapRuntime                               string `json:"srcmap-runtime"`
		Bin, SrcMap, Abi, Devdoc, Userdoc, Metadata string
43
		Hashes                                      map[string]string
44 45 46 47
	}
	Version string
}

48 49 50 51 52 53 54 55 56 57 58 59 60 61
// solidity v.0.8 changes the way ABI, Devdoc and Userdoc are serialized
type solcOutputV8 struct {
	Contracts map[string]struct {
		BinRuntime            string `json:"bin-runtime"`
		SrcMapRuntime         string `json:"srcmap-runtime"`
		Bin, SrcMap, Metadata string
		Abi                   interface{}
		Devdoc                interface{}
		Userdoc               interface{}
		Hashes                map[string]string
	}
	Version string
}

62 63 64 65 66 67 68 69
func (s *Solidity) allowedPaths() string {
	paths := []string{".", "./", "../"} // default to support relative paths
	if len(s.ExtraAllowedPath) > 0 {
		paths = append(paths, s.ExtraAllowedPath...)
	}
	return strings.Join(paths, ", ")
}

70 71
func (s *Solidity) makeArgs() []string {
	p := []string{
72
		"--combined-json", "bin,bin-runtime,srcmap,srcmap-runtime,abi,userdoc,devdoc",
73 74
		"--optimize", // code optimizer switched on
		"--allow-paths", s.allowedPaths(),
75 76
	}
	if s.Major > 0 || s.Minor > 4 || s.Patch > 6 {
77
		p[1] += ",metadata,hashes"
78 79
	}
	return p
80
}
81

82 83 84 85 86
// SolidityVersion runs solc and parses its version output.
func SolidityVersion(solc string) (*Solidity, error) {
	if solc == "" {
		solc = "solc"
	}
87
	var out bytes.Buffer
88
	cmd := exec.Command(solc, "--version")
89
	cmd.Stdout = &out
90 91 92 93 94 95 96 97 98 99
	err := cmd.Run()
	if err != nil {
		return nil, err
	}
	matches := versionRegexp.FindStringSubmatch(out.String())
	if len(matches) != 4 {
		return nil, fmt.Errorf("can't parse solc version %q", out.String())
	}
	s := &Solidity{Path: cmd.Path, FullVersion: out.String(), Version: matches[0]}
	if s.Major, err = strconv.Atoi(matches[1]); err != nil {
100
		return nil, err
101
	}
102 103 104 105 106
	if s.Minor, err = strconv.Atoi(matches[2]); err != nil {
		return nil, err
	}
	if s.Patch, err = strconv.Atoi(matches[3]); err != nil {
		return nil, err
107
	}
108
	return s, nil
109 110
}

111 112
// CompileSolidityString builds and returns all the contracts contained within a source string.
func CompileSolidityString(solc, source string) (map[string]*Contract, error) {
113
	if len(source) == 0 {
114
		return nil, errors.New("solc: empty source string")
115
	}
116 117 118
	s, err := SolidityVersion(solc)
	if err != nil {
		return nil, err
119
	}
120
	return s.CompileSource(source)
121
}
122

123 124 125
// CompileSolidity compiles all given Solidity source files.
func CompileSolidity(solc string, sourcefiles ...string) (map[string]*Contract, error) {
	if len(sourcefiles) == 0 {
126
		return nil, errors.New("solc: no source files")
127
	}
128
	s, err := SolidityVersion(solc)
129 130 131
	if err != nil {
		return nil, err
	}
132 133 134 135 136 137 138 139 140 141 142 143 144 145 146

	return s.CompileFiles(sourcefiles...)
}

// CompileSource builds and returns all the contracts contained within a source string.
func (s *Solidity) CompileSource(source string) (map[string]*Contract, error) {
	args := append(s.makeArgs(), "--")
	cmd := exec.Command(s.Path, append(args, "-")...)
	cmd.Stdin = strings.NewReader(source)
	return s.run(cmd, source)
}

// CompileFiles compiles all given Solidity source files.
func (s *Solidity) CompileFiles(sourcefiles ...string) (map[string]*Contract, error) {
	source, err := slurpFiles(sourcefiles)
147 148
	if err != nil {
		return nil, err
149
	}
150 151 152
	args := append(s.makeArgs(), "--")
	cmd := exec.Command(s.Path, append(args, sourcefiles...)...)
	return s.run(cmd, source)
153 154
}

155
func (s *Solidity) run(cmd *exec.Cmd, source string) (map[string]*Contract, error) {
156
	var stderr, stdout bytes.Buffer
157 158
	cmd.Stderr = &stderr
	cmd.Stdout = &stdout
159
	if err := cmd.Run(); err != nil {
160
		return nil, fmt.Errorf("solc: %v\n%s", err, stderr.Bytes())
161
	}
162 163 164 165 166 167 168 169
	return ParseCombinedJSON(stdout.Bytes(), source, s.Version, s.Version, strings.Join(s.makeArgs(), " "))
}

// ParseCombinedJSON takes the direct output of a solc --combined-output run and
// parses it into a map of string contract name to Contract structs. The
// provided source, language and compiler version, and compiler options are all
// passed through into the Contract structs.
//
170
// The solc output is expected to contain ABI, source mapping, user docs, and dev docs.
171 172 173 174
//
// Returns an error if the JSON is malformed or missing data, or if the JSON
// embedded within the JSON is malformed.
func ParseCombinedJSON(combinedJSON []byte, source string, languageVersion string, compilerVersion string, compilerOptions string) (map[string]*Contract, error) {
175
	var output solcOutput
176
	if err := json.Unmarshal(combinedJSON, &output); err != nil {
177 178
		// Try to parse the output with the new solidity v.0.8.0 rules
		return parseCombinedJSONV8(combinedJSON, source, languageVersion, compilerVersion, compilerOptions)
179
	}
180 181 182 183
	// Compilation succeeded, assemble and return the contracts.
	contracts := make(map[string]*Contract)
	for name, info := range output.Contracts {
		// Parse the individual compilation results.
184
		var abi interface{}
185 186
		if err := json.Unmarshal([]byte(info.Abi), &abi); err != nil {
			return nil, fmt.Errorf("solc: error reading abi definition (%v)", err)
187
		}
188 189 190 191
		var userdoc, devdoc interface{}
		json.Unmarshal([]byte(info.Userdoc), &userdoc)
		json.Unmarshal([]byte(info.Devdoc), &devdoc)

192
		contracts[name] = &Contract{
193 194
			Code:        "0x" + info.Bin,
			RuntimeCode: "0x" + info.BinRuntime,
195
			Hashes:      info.Hashes,
196 197 198
			Info: ContractInfo{
				Source:          source,
				Language:        "Solidity",
199 200 201
				LanguageVersion: languageVersion,
				CompilerVersion: compilerVersion,
				CompilerOptions: compilerOptions,
202 203
				SrcMap:          info.SrcMap,
				SrcMapRuntime:   info.SrcMapRuntime,
204 205 206
				AbiDefinition:   abi,
				UserDoc:         userdoc,
				DeveloperDoc:    devdoc,
207
				Metadata:        info.Metadata,
208 209
			},
		}
210
	}
211
	return contracts, nil
212
}
213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244

// parseCombinedJSONV8 parses the direct output of solc --combined-output
// and parses it using the rules from solidity v.0.8.0 and later.
func parseCombinedJSONV8(combinedJSON []byte, source string, languageVersion string, compilerVersion string, compilerOptions string) (map[string]*Contract, error) {
	var output solcOutputV8
	if err := json.Unmarshal(combinedJSON, &output); err != nil {
		return nil, err
	}
	// Compilation succeeded, assemble and return the contracts.
	contracts := make(map[string]*Contract)
	for name, info := range output.Contracts {
		contracts[name] = &Contract{
			Code:        "0x" + info.Bin,
			RuntimeCode: "0x" + info.BinRuntime,
			Hashes:      info.Hashes,
			Info: ContractInfo{
				Source:          source,
				Language:        "Solidity",
				LanguageVersion: languageVersion,
				CompilerVersion: compilerVersion,
				CompilerOptions: compilerOptions,
				SrcMap:          info.SrcMap,
				SrcMapRuntime:   info.SrcMapRuntime,
				AbiDefinition:   info.Abi,
				UserDoc:         info.Userdoc,
				DeveloperDoc:    info.Devdoc,
				Metadata:        info.Metadata,
			},
		}
	}
	return contracts, nil
}