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 }