solidity.go 7.39 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 35
}

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

47 48 49 50 51 52 53 54 55 56 57 58 59 60
// 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
}

61 62
func (s *Solidity) makeArgs() []string {
	p := []string{
63
		"--combined-json", "bin,bin-runtime,srcmap,srcmap-runtime,abi,userdoc,devdoc",
64 65
		"--optimize",                  // code optimizer switched on
		"--allow-paths", "., ./, ../", // default to support relative paths
66 67
	}
	if s.Major > 0 || s.Minor > 4 || s.Patch > 6 {
68
		p[1] += ",metadata,hashes"
69 70
	}
	return p
71
}
72

73 74 75 76 77
// SolidityVersion runs solc and parses its version output.
func SolidityVersion(solc string) (*Solidity, error) {
	if solc == "" {
		solc = "solc"
	}
78
	var out bytes.Buffer
79
	cmd := exec.Command(solc, "--version")
80
	cmd.Stdout = &out
81 82 83 84 85 86 87 88 89 90
	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 {
91
		return nil, err
92
	}
93 94 95 96 97
	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
98
	}
99
	return s, nil
100 101
}

102 103
// CompileSolidityString builds and returns all the contracts contained within a source string.
func CompileSolidityString(solc, source string) (map[string]*Contract, error) {
104
	if len(source) == 0 {
105
		return nil, errors.New("solc: empty source string")
106
	}
107 108 109
	s, err := SolidityVersion(solc)
	if err != nil {
		return nil, err
110
	}
111 112
	args := append(s.makeArgs(), "--")
	cmd := exec.Command(s.Path, append(args, "-")...)
113
	cmd.Stdin = strings.NewReader(source)
114
	return s.run(cmd, source)
115
}
116

117 118 119
// CompileSolidity compiles all given Solidity source files.
func CompileSolidity(solc string, sourcefiles ...string) (map[string]*Contract, error) {
	if len(sourcefiles) == 0 {
120
		return nil, errors.New("solc: no source files")
121 122 123 124 125
	}
	source, err := slurpFiles(sourcefiles)
	if err != nil {
		return nil, err
	}
126 127 128
	s, err := SolidityVersion(solc)
	if err != nil {
		return nil, err
129
	}
130 131 132
	args := append(s.makeArgs(), "--")
	cmd := exec.Command(s.Path, append(args, sourcefiles...)...)
	return s.run(cmd, source)
133 134
}

135
func (s *Solidity) run(cmd *exec.Cmd, source string) (map[string]*Contract, error) {
136
	var stderr, stdout bytes.Buffer
137 138
	cmd.Stderr = &stderr
	cmd.Stdout = &stdout
139
	if err := cmd.Run(); err != nil {
140
		return nil, fmt.Errorf("solc: %v\n%s", err, stderr.Bytes())
141
	}
142 143 144 145 146 147 148 149
	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.
//
150
// The solc output is expected to contain ABI, source mapping, user docs, and dev docs.
151 152 153 154
//
// 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) {
155
	var output solcOutput
156
	if err := json.Unmarshal(combinedJSON, &output); err != nil {
157 158
		// Try to parse the output with the new solidity v.0.8.0 rules
		return parseCombinedJSONV8(combinedJSON, source, languageVersion, compilerVersion, compilerOptions)
159
	}
160 161 162 163
	// Compilation succeeded, assemble and return the contracts.
	contracts := make(map[string]*Contract)
	for name, info := range output.Contracts {
		// Parse the individual compilation results.
164
		var abi interface{}
165 166
		if err := json.Unmarshal([]byte(info.Abi), &abi); err != nil {
			return nil, fmt.Errorf("solc: error reading abi definition (%v)", err)
167
		}
168 169 170 171
		var userdoc, devdoc interface{}
		json.Unmarshal([]byte(info.Userdoc), &userdoc)
		json.Unmarshal([]byte(info.Devdoc), &devdoc)

172
		contracts[name] = &Contract{
173 174
			Code:        "0x" + info.Bin,
			RuntimeCode: "0x" + info.BinRuntime,
175
			Hashes:      info.Hashes,
176 177 178
			Info: ContractInfo{
				Source:          source,
				Language:        "Solidity",
179 180 181
				LanguageVersion: languageVersion,
				CompilerVersion: compilerVersion,
				CompilerOptions: compilerOptions,
182 183
				SrcMap:          info.SrcMap,
				SrcMapRuntime:   info.SrcMapRuntime,
184 185 186
				AbiDefinition:   abi,
				UserDoc:         userdoc,
				DeveloperDoc:    devdoc,
187
				Metadata:        info.Metadata,
188 189
			},
		}
190
	}
191
	return contracts, nil
192
}
193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224

// 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
}