solidity.go 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  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. package compiler
  17. import (
  18. "bytes"
  19. "encoding/json"
  20. "errors"
  21. "fmt"
  22. "io/ioutil"
  23. "os"
  24. "os/exec"
  25. "path/filepath"
  26. "regexp"
  27. "strings"
  28. "github.com/ethereum/go-ethereum/common"
  29. "github.com/ethereum/go-ethereum/crypto"
  30. "github.com/ethereum/go-ethereum/logger"
  31. "github.com/ethereum/go-ethereum/logger/glog"
  32. )
  33. var (
  34. versionRegExp = regexp.MustCompile("[0-9]+\\.[0-9]+\\.[0-9]+")
  35. newAPIRegexp = regexp.MustCompile("0\\.1\\.[2-9][0-9]*")
  36. paramsLegacy = []string{
  37. "--binary", // Request to output the contract in binary (hexadecimal).
  38. "file", //
  39. "--json-abi", // Request to output the contract's JSON ABI interface.
  40. "file", //
  41. "--natspec-user", // Request to output the contract's Natspec user documentation.
  42. "file", //
  43. "--natspec-dev", // Request to output the contract's Natspec developer documentation.
  44. "file",
  45. "--add-std",
  46. "1",
  47. }
  48. paramsNew = []string{
  49. "--bin", // Request to output the contract in binary (hexadecimal).
  50. "--abi", // Request to output the contract's JSON ABI interface.
  51. "--userdoc", // Request to output the contract's Natspec user documentation.
  52. "--devdoc", // Request to output the contract's Natspec developer documentation.
  53. "--add-std", // include standard lib contracts
  54. "--optimize=1", // code optimizer switched on
  55. "-o", // output directory
  56. }
  57. )
  58. type Contract struct {
  59. Code string `json:"code"`
  60. Info ContractInfo `json:"info"`
  61. }
  62. type ContractInfo struct {
  63. Source string `json:"source"`
  64. Language string `json:"language"`
  65. LanguageVersion string `json:"languageVersion"`
  66. CompilerVersion string `json:"compilerVersion"`
  67. CompilerOptions string `json:"compilerOptions"`
  68. AbiDefinition interface{} `json:"abiDefinition"`
  69. UserDoc interface{} `json:"userDoc"`
  70. DeveloperDoc interface{} `json:"developerDoc"`
  71. }
  72. type Solidity struct {
  73. solcPath string
  74. version string
  75. fullVersion string
  76. legacy bool
  77. }
  78. func New(solcPath string) (sol *Solidity, err error) {
  79. // set default solc
  80. if len(solcPath) == 0 {
  81. solcPath = "solc"
  82. }
  83. solcPath, err = exec.LookPath(solcPath)
  84. if err != nil {
  85. return
  86. }
  87. cmd := exec.Command(solcPath, "--version")
  88. var out bytes.Buffer
  89. cmd.Stdout = &out
  90. err = cmd.Run()
  91. if err != nil {
  92. return
  93. }
  94. fullVersion := out.String()
  95. version := versionRegExp.FindString(fullVersion)
  96. legacy := !newAPIRegexp.MatchString(version)
  97. sol = &Solidity{
  98. solcPath: solcPath,
  99. version: version,
  100. fullVersion: fullVersion,
  101. legacy: legacy,
  102. }
  103. glog.V(logger.Info).Infoln(sol.Info())
  104. return
  105. }
  106. func (sol *Solidity) Info() string {
  107. return fmt.Sprintf("%s\npath: %s", sol.fullVersion, sol.solcPath)
  108. }
  109. func (sol *Solidity) Version() string {
  110. return sol.version
  111. }
  112. // Compile builds and returns all the contracts contained within a source string.
  113. func (sol *Solidity) Compile(source string) (map[string]*Contract, error) {
  114. // Short circuit if no source code was specified
  115. if len(source) == 0 {
  116. return nil, errors.New("solc: empty source string")
  117. }
  118. // Create a safe place to dump compilation output
  119. wd, err := ioutil.TempDir("", "solc")
  120. if err != nil {
  121. return nil, fmt.Errorf("solc: failed to create temporary build folder: %v", err)
  122. }
  123. defer os.RemoveAll(wd)
  124. // Assemble the compiler command, change to the temp folder and capture any errors
  125. stderr := new(bytes.Buffer)
  126. var params []string
  127. if sol.legacy {
  128. params = paramsLegacy
  129. } else {
  130. params = paramsNew
  131. params = append(params, wd)
  132. }
  133. compilerOptions := strings.Join(params, " ")
  134. cmd := exec.Command(sol.solcPath, params...)
  135. cmd.Dir = wd
  136. cmd.Stdin = strings.NewReader(source)
  137. cmd.Stderr = stderr
  138. if err := cmd.Run(); err != nil {
  139. return nil, fmt.Errorf("solc: %v\n%s", err, string(stderr.Bytes()))
  140. }
  141. // Sanity check that something was actually built
  142. matches, _ := filepath.Glob(wd + "/*\\.bin*")
  143. if len(matches) < 1 {
  144. return nil, fmt.Errorf("solc: no build results found")
  145. }
  146. // Compilation succeeded, assemble and return the contracts
  147. contracts := make(map[string]*Contract)
  148. for _, path := range matches {
  149. _, file := filepath.Split(path)
  150. base := strings.Split(file, ".")[0]
  151. // Parse the individual compilation results (code binary, ABI definitions, user and dev docs)
  152. var binary []byte
  153. binext := ".bin"
  154. if sol.legacy {
  155. binext = ".binary"
  156. }
  157. if binary, err = ioutil.ReadFile(filepath.Join(wd, base+binext)); err != nil {
  158. return nil, fmt.Errorf("solc: error reading compiler output for code: %v", err)
  159. }
  160. var abi interface{}
  161. if blob, err := ioutil.ReadFile(filepath.Join(wd, base+".abi")); err != nil {
  162. return nil, fmt.Errorf("solc: error reading abi definition: %v", err)
  163. } else if err = json.Unmarshal(blob, &abi); err != nil {
  164. return nil, fmt.Errorf("solc: error parsing abi definition: %v", err)
  165. }
  166. var userdoc interface{}
  167. if blob, err := ioutil.ReadFile(filepath.Join(wd, base+".docuser")); err != nil {
  168. return nil, fmt.Errorf("solc: error reading user doc: %v", err)
  169. } else if err = json.Unmarshal(blob, &userdoc); err != nil {
  170. return nil, fmt.Errorf("solc: error parsing user doc: %v", err)
  171. }
  172. var devdoc interface{}
  173. if blob, err := ioutil.ReadFile(filepath.Join(wd, base+".docdev")); err != nil {
  174. return nil, fmt.Errorf("solc: error reading dev doc: %v", err)
  175. } else if err = json.Unmarshal(blob, &devdoc); err != nil {
  176. return nil, fmt.Errorf("solc: error parsing dev doc: %v", err)
  177. }
  178. // Assemble the final contract
  179. contracts[base] = &Contract{
  180. Code: "0x" + string(binary),
  181. Info: ContractInfo{
  182. Source: source,
  183. Language: "Solidity",
  184. LanguageVersion: sol.version,
  185. CompilerVersion: sol.version,
  186. CompilerOptions: compilerOptions,
  187. AbiDefinition: abi,
  188. UserDoc: userdoc,
  189. DeveloperDoc: devdoc,
  190. },
  191. }
  192. }
  193. return contracts, nil
  194. }
  195. func SaveInfo(info *ContractInfo, filename string) (contenthash common.Hash, err error) {
  196. infojson, err := json.Marshal(info)
  197. if err != nil {
  198. return
  199. }
  200. contenthash = common.BytesToHash(crypto.Sha3(infojson))
  201. err = ioutil.WriteFile(filename, infojson, 0600)
  202. return
  203. }