github.com/klaytn/klaytn@v1.10.2/common/compiler/solidity.go (about) 1 // Modifications Copyright 2018 The klaytn Authors 2 // Copyright 2019 The go-ethereum Authors 3 // This file is part of the go-ethereum library. 4 // 5 // The go-ethereum library is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU Lesser General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // The go-ethereum library is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU Lesser General Public License for more details. 14 // 15 // You should have received a copy of the GNU Lesser General Public License 16 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 17 // 18 // This file is derived from common/compiler/solidity.go (2018/06/04). 19 // Modified and improved for the klaytn development. 20 21 // Package compiler wraps the Solidity compiler executables (solc). 22 package compiler 23 24 import ( 25 "bytes" 26 "encoding/json" 27 "errors" 28 "fmt" 29 "io/ioutil" 30 "os/exec" 31 "strconv" 32 "strings" 33 34 "github.com/klaytn/klaytn/log" 35 ) 36 37 var logger = log.NewModuleLogger(log.Common) 38 39 // Solidity contains information about the solidity compiler. 40 type Solidity struct { 41 Path, Version, FullVersion string 42 Major, Minor, Patch int 43 } 44 45 // --combined-output format 46 type solcOutput struct { 47 Contracts map[string]struct { 48 BinRuntime string `json:"bin-runtime"` 49 SrcMapRuntime string `json:"srcmap-runtime"` 50 Bin, SrcMap, Abi, Devdoc, Userdoc, Metadata string 51 Hashes map[string]string 52 } 53 Version string `json:"version"` 54 } 55 56 // solidity v.0.8 changes the way ABI, Devdoc and Userdoc are serialized 57 type solcOutputV8 struct { 58 Contracts map[string]struct { 59 BinRuntime string `json:"bin-runtime"` 60 SrcMapRuntime string `json:"srcmap-runtime"` 61 Bin, SrcMap, Metadata string 62 Abi interface{} 63 Devdoc interface{} 64 Userdoc interface{} 65 Hashes map[string]string 66 } 67 Version string `json:"version"` 68 } 69 70 func (s *Solidity) makeArgs() []string { 71 p := []string{ 72 "--combined-json", "bin,bin-runtime,srcmap,srcmap-runtime,abi,userdoc,devdoc", 73 "--optimize", // code optimizer switched on 74 "--allow-paths", "., ./, ../", // default to support relative path: ./ ../ . 75 } 76 if s.Major > 0 || s.Minor > 4 || s.Patch > 6 { 77 p[1] += ",metadata,hashes" 78 } 79 return p 80 } 81 82 // SolidityVersion runs solc and parses its version output. 83 func SolidityVersion(solc string) (*Solidity, error) { 84 if solc == "" { 85 solc = "solc" 86 } 87 var out bytes.Buffer 88 cmd := exec.Command(solc, "--version") 89 cmd.Stdout = &out 90 err := cmd.Run() 91 if err != nil { 92 return nil, err 93 } 94 matches := versionRegexp.FindStringSubmatch(out.String()) 95 if len(matches) != 4 { 96 return nil, fmt.Errorf("can't parse solc version %q", out.String()) 97 } 98 s := &Solidity{Path: cmd.Path, FullVersion: out.String(), Version: matches[0]} 99 if s.Major, err = strconv.Atoi(matches[1]); err != nil { 100 return nil, err 101 } 102 if s.Minor, err = strconv.Atoi(matches[2]); err != nil { 103 return nil, err 104 } 105 if s.Patch, err = strconv.Atoi(matches[3]); err != nil { 106 return nil, err 107 } 108 return s, nil 109 } 110 111 // CompileSolidityString builds and returns all the contracts contained within a source string. 112 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 s, err := SolidityVersion(solc) 117 if err != nil { 118 return nil, err 119 } 120 args := append(s.makeArgs(), "--") 121 cmd := exec.Command(s.Path, append(args, "-")...) 122 cmd.Stdin = strings.NewReader(source) 123 return s.run(cmd, source) 124 } 125 126 // CompileSolidity compiles all given Solidity source files. 127 func CompileSolidity(solc string, sourcefiles ...string) (map[string]*Contract, error) { 128 if len(sourcefiles) == 0 { 129 return nil, errors.New("solc: no source files") 130 } 131 source, err := slurpFiles(sourcefiles) 132 if err != nil { 133 return nil, err 134 } 135 s, err := SolidityVersion(solc) 136 if err != nil { 137 return nil, err 138 } 139 args := append(s.makeArgs(), "--") 140 cmd := exec.Command(s.Path, append(args, sourcefiles...)...) 141 return s.run(cmd, source) 142 } 143 144 // CompileSolidityOrLoad compiles all given Solidity source files. 145 // If suitable compiler is not available, try to load combinedJSON stored as file. 146 // The combinedJSON file should be named as *.sol.json. 147 // Create combinedJSON with following command: 148 // solc --combined-json bin,bin-runtime,srcmap,srcmap-runtime,abi,userdoc,devdoc \ 149 // --optimize --allow-paths '., ./, ../' test.sol > test.sol.json 150 func CompileSolidityOrLoad(solc string, sourcefiles ...string) (map[string]*Contract, error) { 151 // Extract solidity version requirements from source codes 152 if len(sourcefiles) == 0 { 153 return nil, errors.New("solc: no source files") 154 } 155 source, err := slurpFiles(sourcefiles) 156 if err != nil { 157 return nil, err 158 } 159 160 // Find available compiler, if any 161 s, err := SolidityVersion(solc) 162 if err != nil { 163 logger.Warn("Solidity compiler not found. Loading from file.", "err", err) 164 return loadCombinedJSON(source, sourcefiles...) 165 } 166 167 args := append(s.makeArgs(), "--") 168 cmd := exec.Command(s.Path, append(args, sourcefiles...)...) 169 contracts, err := s.run(cmd, source) 170 if err != nil { 171 logger.Warn("Solidity compiler cannot compile source versions. Loading from file.", "err", err) 172 return loadCombinedJSON(source, sourcefiles...) 173 } 174 return contracts, nil 175 } 176 177 // Search for CombinedJSON at <solidity_file_name>.json 178 func loadCombinedJSON(source string, sourcefiles ...string) (map[string]*Contract, error) { 179 path := sourcefiles[0] + ".json" 180 combinedJSON, err := ioutil.ReadFile(path) 181 if err != nil { 182 return nil, fmt.Errorf("Cannot open combined json at %s", path) 183 } 184 185 // Find compiler version from the loaded json 186 v := struct { 187 Version string `json:"version"` 188 }{} 189 if err := json.Unmarshal(combinedJSON, &v); err != nil { 190 return nil, fmt.Errorf("can't parse combined json version %s", err) 191 } 192 matches := versionRegexp.FindStringSubmatch(v.Version) 193 if len(matches) != 4 { 194 return nil, fmt.Errorf("can't parse combined json version %q", v.Version) 195 } 196 version := matches[0] 197 198 return ParseCombinedJSON(combinedJSON, source, version, version, "") 199 } 200 201 func (s *Solidity) run(cmd *exec.Cmd, source string) (map[string]*Contract, error) { 202 var stderr, stdout bytes.Buffer 203 cmd.Stderr = &stderr 204 cmd.Stdout = &stdout 205 if err := cmd.Run(); err != nil { 206 return nil, fmt.Errorf("solc: %v\n%s", err, stderr.Bytes()) 207 } 208 209 return ParseCombinedJSON(stdout.Bytes(), source, s.Version, s.Version, strings.Join(s.makeArgs(), " ")) 210 } 211 212 // ParseCombinedJSON takes the direct output of a solc --combined-output run and 213 // parses it into a map of string contract name to Contract structs. The 214 // provided source, language and compiler version, and compiler options are all 215 // passed through into the Contract structs. 216 // 217 // The solc output is expected to contain ABI, source mapping, user docs, and dev docs. 218 // 219 // Returns an error if the JSON is malformed or missing data, or if the JSON 220 // embedded within the JSON is malformed. 221 func ParseCombinedJSON(combinedJSON []byte, source string, languageVersion string, compilerVersion string, compilerOptions string) (map[string]*Contract, error) { 222 var output solcOutput 223 if err := json.Unmarshal(combinedJSON, &output); err != nil { 224 // Try to parse the output with the new solidity v.0.8.0 rules 225 return parseCombinedJSONV8(combinedJSON, source, languageVersion, compilerVersion, compilerOptions) 226 } 227 // Compilation succeeded, assemble and return the contracts. 228 contracts := make(map[string]*Contract) 229 for name, info := range output.Contracts { 230 // Parse the individual compilation results. 231 var abi interface{} 232 if err := json.Unmarshal([]byte(info.Abi), &abi); err != nil { 233 return nil, fmt.Errorf("solc: error reading abi definition (%v)", err) 234 } 235 var userdoc, devdoc interface{} 236 json.Unmarshal([]byte(info.Userdoc), &userdoc) 237 json.Unmarshal([]byte(info.Devdoc), &devdoc) 238 239 contracts[name] = &Contract{ 240 Code: "0x" + info.Bin, 241 RuntimeCode: "0x" + info.BinRuntime, 242 Hashes: info.Hashes, 243 Info: ContractInfo{ 244 Source: source, 245 Language: "Solidity", 246 LanguageVersion: languageVersion, 247 CompilerVersion: compilerVersion, 248 CompilerOptions: compilerOptions, 249 SrcMap: info.SrcMap, 250 SrcMapRuntime: info.SrcMapRuntime, 251 AbiDefinition: abi, 252 UserDoc: userdoc, 253 DeveloperDoc: devdoc, 254 Metadata: info.Metadata, 255 }, 256 } 257 } 258 return contracts, nil 259 } 260 261 // parseCombinedJSONV8 parses the direct output of solc --combined-output 262 // and parses it using the rules from solidity v.0.8.0 and later. 263 func parseCombinedJSONV8(combinedJSON []byte, source string, languageVersion string, compilerVersion string, compilerOptions string) (map[string]*Contract, error) { 264 var output solcOutputV8 265 if err := json.Unmarshal(combinedJSON, &output); err != nil { 266 return nil, err 267 } 268 // Compilation succeeded, assemble and return the contracts. 269 contracts := make(map[string]*Contract) 270 for name, info := range output.Contracts { 271 contracts[name] = &Contract{ 272 Code: "0x" + info.Bin, 273 RuntimeCode: "0x" + info.BinRuntime, 274 Hashes: info.Hashes, 275 Info: ContractInfo{ 276 Source: source, 277 Language: "Solidity", 278 LanguageVersion: languageVersion, 279 CompilerVersion: compilerVersion, 280 CompilerOptions: compilerOptions, 281 SrcMap: info.SrcMap, 282 SrcMapRuntime: info.SrcMapRuntime, 283 AbiDefinition: info.Abi, 284 UserDoc: info.Userdoc, 285 DeveloperDoc: info.Devdoc, 286 Metadata: info.Metadata, 287 }, 288 } 289 } 290 return contracts, nil 291 }