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