github.com/Elemental-core/elementalcore@v0.0.0-20191206075037-63891242267a/common/compiler/solidity.go (about) 1 // Copyright 2015 The elementalcore Authors 2 // This file is part of the elementalcore library. 3 // 4 // The elementalcore 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 elementalcore 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 elementalcore library. If not, see <http://www.gnu.org/licenses/>. 16 17 // Package compiler wraps the Solidity compiler executable (solc). 18 package compiler 19 20 import ( 21 "bytes" 22 "encoding/json" 23 "errors" 24 "fmt" 25 "io/ioutil" 26 "os/exec" 27 "regexp" 28 "strconv" 29 "strings" 30 ) 31 32 var versionRegexp = regexp.MustCompile(`([0-9]+)\.([0-9]+)\.([0-9]+)`) 33 34 type Contract struct { 35 Code string `json:"code"` 36 Info ContractInfo `json:"info"` 37 } 38 39 type ContractInfo struct { 40 Source string `json:"source"` 41 Language string `json:"language"` 42 LanguageVersion string `json:"languageVersion"` 43 CompilerVersion string `json:"compilerVersion"` 44 CompilerOptions string `json:"compilerOptions"` 45 AbiDefinition interface{} `json:"abiDefinition"` 46 UserDoc interface{} `json:"userDoc"` 47 DeveloperDoc interface{} `json:"developerDoc"` 48 Metadata string `json:"metadata"` 49 } 50 51 // Solidity contains information about the solidity compiler. 52 type Solidity struct { 53 Path, Version, FullVersion string 54 Major, Minor, Patch int 55 } 56 57 // --combined-output format 58 type solcOutput struct { 59 Contracts map[string]struct { 60 Bin, Abi, Devdoc, Userdoc, Metadata string 61 } 62 Version string 63 } 64 65 func (s *Solidity) makeArgs() []string { 66 p := []string{ 67 "--combined-json", "bin,abi,userdoc,devdoc", 68 "--add-std", // include standard lib contracts 69 "--optimize", // code optimizer switched on 70 } 71 if s.Major > 0 || s.Minor > 4 || s.Patch > 6 { 72 p[1] += ",metadata" 73 } 74 return p 75 } 76 77 // SolidityVersion runs solc and parses its version output. 78 func SolidityVersion(solc string) (*Solidity, error) { 79 if solc == "" { 80 solc = "solc" 81 } 82 var out bytes.Buffer 83 cmd := exec.Command(solc, "--version") 84 cmd.Stdout = &out 85 err := cmd.Run() 86 if err != nil { 87 return nil, err 88 } 89 matches := versionRegexp.FindStringSubmatch(out.String()) 90 if len(matches) != 4 { 91 return nil, fmt.Errorf("can't parse solc version %q", out.String()) 92 } 93 s := &Solidity{Path: cmd.Path, FullVersion: out.String(), Version: matches[0]} 94 if s.Major, err = strconv.Atoi(matches[1]); err != nil { 95 return nil, err 96 } 97 if s.Minor, err = strconv.Atoi(matches[2]); err != nil { 98 return nil, err 99 } 100 if s.Patch, err = strconv.Atoi(matches[3]); err != nil { 101 return nil, err 102 } 103 return s, nil 104 } 105 106 // CompileSolidityString builds and returns all the contracts contained within a source string. 107 func CompileSolidityString(solc, source string) (map[string]*Contract, error) { 108 if len(source) == 0 { 109 return nil, errors.New("solc: empty source string") 110 } 111 s, err := SolidityVersion(solc) 112 if err != nil { 113 return nil, err 114 } 115 args := append(s.makeArgs(), "--") 116 cmd := exec.Command(s.Path, append(args, "-")...) 117 cmd.Stdin = strings.NewReader(source) 118 return s.run(cmd, source) 119 } 120 121 // CompileSolidity compiles all given Solidity source files. 122 func CompileSolidity(solc string, sourcefiles ...string) (map[string]*Contract, error) { 123 if len(sourcefiles) == 0 { 124 return nil, errors.New("solc: no source files") 125 } 126 source, err := slurpFiles(sourcefiles) 127 if err != nil { 128 return nil, err 129 } 130 s, err := SolidityVersion(solc) 131 if err != nil { 132 return nil, err 133 } 134 args := append(s.makeArgs(), "--") 135 cmd := exec.Command(s.Path, append(args, sourcefiles...)...) 136 return s.run(cmd, source) 137 } 138 139 func (s *Solidity) run(cmd *exec.Cmd, source string) (map[string]*Contract, error) { 140 var stderr, stdout bytes.Buffer 141 cmd.Stderr = &stderr 142 cmd.Stdout = &stdout 143 if err := cmd.Run(); err != nil { 144 return nil, fmt.Errorf("solc: %v\n%s", err, stderr.Bytes()) 145 } 146 var output solcOutput 147 if err := json.Unmarshal(stdout.Bytes(), &output); err != nil { 148 return nil, err 149 } 150 151 // Compilation succeeded, assemble and return the contracts. 152 contracts := make(map[string]*Contract) 153 for name, info := range output.Contracts { 154 // Parse the individual compilation results. 155 var abi interface{} 156 if err := json.Unmarshal([]byte(info.Abi), &abi); err != nil { 157 return nil, fmt.Errorf("solc: error reading abi definition (%v)", err) 158 } 159 var userdoc interface{} 160 if err := json.Unmarshal([]byte(info.Userdoc), &userdoc); err != nil { 161 return nil, fmt.Errorf("solc: error reading user doc: %v", err) 162 } 163 var devdoc interface{} 164 if err := json.Unmarshal([]byte(info.Devdoc), &devdoc); err != nil { 165 return nil, fmt.Errorf("solc: error reading dev doc: %v", err) 166 } 167 contracts[name] = &Contract{ 168 Code: "0x" + info.Bin, 169 Info: ContractInfo{ 170 Source: source, 171 Language: "Solidity", 172 LanguageVersion: s.Version, 173 CompilerVersion: s.Version, 174 CompilerOptions: strings.Join(s.makeArgs(), " "), 175 AbiDefinition: abi, 176 UserDoc: userdoc, 177 DeveloperDoc: devdoc, 178 Metadata: info.Metadata, 179 }, 180 } 181 } 182 return contracts, nil 183 } 184 185 func slurpFiles(files []string) (string, error) { 186 var concat bytes.Buffer 187 for _, file := range files { 188 content, err := ioutil.ReadFile(file) 189 if err != nil { 190 return "", err 191 } 192 concat.Write(content) 193 } 194 return concat.String(), nil 195 }