github.com/klaytn/klaytn@v1.12.1/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 "os" 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 // 149 // solc --combined-json bin,bin-runtime,srcmap,srcmap-runtime,abi,userdoc,devdoc \ 150 // --optimize --allow-paths '., ./, ../' test.sol > test.sol.json 151 func CompileSolidityOrLoad(solc string, sourcefiles ...string) (map[string]*Contract, error) { 152 // Extract solidity version requirements from source codes 153 if len(sourcefiles) == 0 { 154 return nil, errors.New("solc: no source files") 155 } 156 source, err := slurpFiles(sourcefiles) 157 if err != nil { 158 return nil, err 159 } 160 161 // Find available compiler, if any 162 s, err := SolidityVersion(solc) 163 if err != nil { 164 logger.Warn("Solidity compiler not found. Loading from file.", "err", err) 165 return loadCombinedJSON(source, sourcefiles...) 166 } 167 168 args := append(s.makeArgs(), "--") 169 cmd := exec.Command(s.Path, append(args, sourcefiles...)...) 170 contracts, err := s.run(cmd, source) 171 if err != nil { 172 logger.Warn("Solidity compiler cannot compile source versions. Loading from file.", "err", err) 173 return loadCombinedJSON(source, sourcefiles...) 174 } 175 return contracts, nil 176 } 177 178 // Search for CombinedJSON at <solidity_file_name>.json 179 func loadCombinedJSON(source string, sourcefiles ...string) (map[string]*Contract, error) { 180 path := sourcefiles[0] + ".json" 181 combinedJSON, err := os.ReadFile(path) 182 if err != nil { 183 return nil, fmt.Errorf("Cannot open combined json at %s", path) 184 } 185 186 // Find compiler version from the loaded json 187 v := struct { 188 Version string `json:"version"` 189 }{} 190 if err := json.Unmarshal(combinedJSON, &v); err != nil { 191 return nil, fmt.Errorf("can't parse combined json version %s", err) 192 } 193 matches := versionRegexp.FindStringSubmatch(v.Version) 194 if len(matches) != 4 { 195 return nil, fmt.Errorf("can't parse combined json version %q", v.Version) 196 } 197 version := matches[0] 198 199 return ParseCombinedJSON(combinedJSON, source, version, version, "") 200 } 201 202 func (s *Solidity) run(cmd *exec.Cmd, source string) (map[string]*Contract, error) { 203 var stderr, stdout bytes.Buffer 204 cmd.Stderr = &stderr 205 cmd.Stdout = &stdout 206 if err := cmd.Run(); err != nil { 207 return nil, fmt.Errorf("solc: %v\n%s", err, stderr.Bytes()) 208 } 209 210 return ParseCombinedJSON(stdout.Bytes(), source, s.Version, s.Version, strings.Join(s.makeArgs(), " ")) 211 } 212 213 // ParseCombinedJSON takes the direct output of a solc --combined-output run and 214 // parses it into a map of string contract name to Contract structs. The 215 // provided source, language and compiler version, and compiler options are all 216 // passed through into the Contract structs. 217 // 218 // The solc output is expected to contain ABI, source mapping, user docs, and dev docs. 219 // 220 // Returns an error if the JSON is malformed or missing data, or if the JSON 221 // embedded within the JSON is malformed. 222 func ParseCombinedJSON(combinedJSON []byte, source string, languageVersion string, compilerVersion string, compilerOptions string) (map[string]*Contract, error) { 223 var output solcOutput 224 if err := json.Unmarshal(combinedJSON, &output); err != nil { 225 // Try to parse the output with the new solidity v.0.8.0 rules 226 return parseCombinedJSONV8(combinedJSON, source, languageVersion, compilerVersion, compilerOptions) 227 } 228 // Compilation succeeded, assemble and return the contracts. 229 contracts := make(map[string]*Contract) 230 for name, info := range output.Contracts { 231 // Parse the individual compilation results. 232 var abi interface{} 233 if err := json.Unmarshal([]byte(info.Abi), &abi); err != nil { 234 return nil, fmt.Errorf("solc: error reading abi definition (%v)", err) 235 } 236 var userdoc, devdoc interface{} 237 json.Unmarshal([]byte(info.Userdoc), &userdoc) 238 json.Unmarshal([]byte(info.Devdoc), &devdoc) 239 240 contracts[name] = &Contract{ 241 Code: "0x" + info.Bin, 242 RuntimeCode: "0x" + info.BinRuntime, 243 Hashes: info.Hashes, 244 Info: ContractInfo{ 245 Source: source, 246 Language: "Solidity", 247 LanguageVersion: languageVersion, 248 CompilerVersion: compilerVersion, 249 CompilerOptions: compilerOptions, 250 SrcMap: info.SrcMap, 251 SrcMapRuntime: info.SrcMapRuntime, 252 AbiDefinition: abi, 253 UserDoc: userdoc, 254 DeveloperDoc: devdoc, 255 Metadata: info.Metadata, 256 }, 257 } 258 } 259 return contracts, nil 260 } 261 262 // parseCombinedJSONV8 parses the direct output of solc --combined-output 263 // and parses it using the rules from solidity v.0.8.0 and later. 264 func parseCombinedJSONV8(combinedJSON []byte, source string, languageVersion string, compilerVersion string, compilerOptions string) (map[string]*Contract, error) { 265 var output solcOutputV8 266 if err := json.Unmarshal(combinedJSON, &output); err != nil { 267 return nil, err 268 } 269 // Compilation succeeded, assemble and return the contracts. 270 contracts := make(map[string]*Contract) 271 for name, info := range output.Contracts { 272 contracts[name] = &Contract{ 273 Code: "0x" + info.Bin, 274 RuntimeCode: "0x" + info.BinRuntime, 275 Hashes: info.Hashes, 276 Info: ContractInfo{ 277 Source: source, 278 Language: "Solidity", 279 LanguageVersion: languageVersion, 280 CompilerVersion: compilerVersion, 281 CompilerOptions: compilerOptions, 282 SrcMap: info.SrcMap, 283 SrcMapRuntime: info.SrcMapRuntime, 284 AbiDefinition: info.Abi, 285 UserDoc: info.Userdoc, 286 DeveloperDoc: info.Devdoc, 287 Metadata: info.Metadata, 288 }, 289 } 290 } 291 return contracts, nil 292 }