bind.go 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329
  1. // Copyright 2016 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 bind generates Ethereum contract Go bindings.
  17. //
  18. // Detailed usage document and tutorial available on the go-ethereum Wiki page:
  19. // https://github.com/ethereum/go-ethereum/wiki/Native-DApps:-Go-bindings-to-Ethereum-contracts
  20. package bind
  21. import (
  22. "bytes"
  23. "fmt"
  24. "regexp"
  25. "strings"
  26. "text/template"
  27. "unicode"
  28. "github.com/ethereum/go-ethereum/accounts/abi"
  29. "golang.org/x/tools/imports"
  30. )
  31. // Lang is a target programming language selector to generate bindings for.
  32. type Lang int
  33. const (
  34. LangGo Lang = iota
  35. LangJava
  36. LangObjC
  37. )
  38. // Bind generates a Go wrapper around a contract ABI. This wrapper isn't meant
  39. // to be used as is in client code, but rather as an intermediate struct which
  40. // enforces compile time type safety and naming convention opposed to having to
  41. // manually maintain hard coded strings that break on runtime.
  42. func Bind(types []string, abis []string, bytecodes []string, pkg string, lang Lang) (string, error) {
  43. // Process each individual contract requested binding
  44. contracts := make(map[string]*tmplContract)
  45. for i := 0; i < len(types); i++ {
  46. // Parse the actual ABI to generate the binding for
  47. evmABI, err := abi.JSON(strings.NewReader(abis[i]))
  48. if err != nil {
  49. return "", err
  50. }
  51. // Strip any whitespace from the JSON ABI
  52. strippedABI := strings.Map(func(r rune) rune {
  53. if unicode.IsSpace(r) {
  54. return -1
  55. }
  56. return r
  57. }, abis[i])
  58. // Extract the call and transact methods, and sort them alphabetically
  59. var (
  60. calls = make(map[string]*tmplMethod)
  61. transacts = make(map[string]*tmplMethod)
  62. )
  63. for _, original := range evmABI.Methods {
  64. // Normalize the method for capital cases and non-anonymous inputs/outputs
  65. normalized := original
  66. normalized.Name = methodNormalizer[lang](original.Name)
  67. normalized.Inputs = make([]abi.Argument, len(original.Inputs))
  68. copy(normalized.Inputs, original.Inputs)
  69. for j, input := range normalized.Inputs {
  70. if input.Name == "" {
  71. normalized.Inputs[j].Name = fmt.Sprintf("arg%d", j)
  72. }
  73. }
  74. normalized.Outputs = make([]abi.Argument, len(original.Outputs))
  75. copy(normalized.Outputs, original.Outputs)
  76. for j, output := range normalized.Outputs {
  77. if output.Name != "" {
  78. normalized.Outputs[j].Name = capitalise(output.Name)
  79. }
  80. }
  81. // Append the methods to the call or transact lists
  82. if original.Const {
  83. calls[original.Name] = &tmplMethod{Original: original, Normalized: normalized, Structured: structured(original)}
  84. } else {
  85. transacts[original.Name] = &tmplMethod{Original: original, Normalized: normalized, Structured: structured(original)}
  86. }
  87. }
  88. contracts[types[i]] = &tmplContract{
  89. Type: capitalise(types[i]),
  90. InputABI: strings.Replace(strippedABI, "\"", "\\\"", -1),
  91. InputBin: strings.TrimSpace(bytecodes[i]),
  92. Constructor: evmABI.Constructor,
  93. Calls: calls,
  94. Transacts: transacts,
  95. }
  96. }
  97. // Generate the contract template data content and render it
  98. data := &tmplData{
  99. Package: pkg,
  100. Contracts: contracts,
  101. }
  102. buffer := new(bytes.Buffer)
  103. funcs := map[string]interface{}{
  104. "bindtype": bindType[lang],
  105. "namedtype": namedType[lang],
  106. "capitalise": capitalise,
  107. "decapitalise": decapitalise,
  108. }
  109. tmpl := template.Must(template.New("").Funcs(funcs).Parse(tmplSource[lang]))
  110. if err := tmpl.Execute(buffer, data); err != nil {
  111. return "", err
  112. }
  113. // For Go bindings pass the code through goimports to clean it up and double check
  114. if lang == LangGo {
  115. code, err := imports.Process("", buffer.Bytes(), nil)
  116. if err != nil {
  117. return "", fmt.Errorf("%v\n%s", err, buffer)
  118. }
  119. return string(code), nil
  120. }
  121. // For all others just return as is for now
  122. return string(buffer.Bytes()), nil
  123. }
  124. // bindType is a set of type binders that convert Solidity types to some supported
  125. // programming language.
  126. var bindType = map[Lang]func(kind abi.Type) string{
  127. LangGo: bindTypeGo,
  128. LangJava: bindTypeJava,
  129. }
  130. // bindTypeGo converts a Solidity type to a Go one. Since there is no clear mapping
  131. // from all Solidity types to Go ones (e.g. uint17), those that cannot be exactly
  132. // mapped will use an upscaled type (e.g. *big.Int).
  133. func bindTypeGo(kind abi.Type) string {
  134. stringKind := kind.String()
  135. switch {
  136. case strings.HasPrefix(stringKind, "address"):
  137. parts := regexp.MustCompile(`address(\[[0-9]*\])?`).FindStringSubmatch(stringKind)
  138. if len(parts) != 2 {
  139. return stringKind
  140. }
  141. return fmt.Sprintf("%scommon.Address", parts[1])
  142. case strings.HasPrefix(stringKind, "bytes"):
  143. parts := regexp.MustCompile(`bytes([0-9]*)(\[[0-9]*\])?`).FindStringSubmatch(stringKind)
  144. if len(parts) != 3 {
  145. return stringKind
  146. }
  147. return fmt.Sprintf("%s[%s]byte", parts[2], parts[1])
  148. case strings.HasPrefix(stringKind, "int") || strings.HasPrefix(stringKind, "uint"):
  149. parts := regexp.MustCompile(`(u)?int([0-9]*)(\[[0-9]*\])?`).FindStringSubmatch(stringKind)
  150. if len(parts) != 4 {
  151. return stringKind
  152. }
  153. switch parts[2] {
  154. case "8", "16", "32", "64":
  155. return fmt.Sprintf("%s%sint%s", parts[3], parts[1], parts[2])
  156. }
  157. return fmt.Sprintf("%s*big.Int", parts[3])
  158. case strings.HasPrefix(stringKind, "bool") || strings.HasPrefix(stringKind, "string"):
  159. parts := regexp.MustCompile(`([a-z]+)(\[[0-9]*\])?`).FindStringSubmatch(stringKind)
  160. if len(parts) != 3 {
  161. return stringKind
  162. }
  163. return fmt.Sprintf("%s%s", parts[2], parts[1])
  164. default:
  165. return stringKind
  166. }
  167. }
  168. // bindTypeJava converts a Solidity type to a Java one. Since there is no clear mapping
  169. // from all Solidity types to Java ones (e.g. uint17), those that cannot be exactly
  170. // mapped will use an upscaled type (e.g. BigDecimal).
  171. func bindTypeJava(kind abi.Type) string {
  172. stringKind := kind.String()
  173. switch {
  174. case strings.HasPrefix(stringKind, "address"):
  175. parts := regexp.MustCompile(`address(\[[0-9]*\])?`).FindStringSubmatch(stringKind)
  176. if len(parts) != 2 {
  177. return stringKind
  178. }
  179. if parts[1] == "" {
  180. return fmt.Sprintf("Address")
  181. }
  182. return fmt.Sprintf("Addresses")
  183. case strings.HasPrefix(stringKind, "bytes"):
  184. parts := regexp.MustCompile(`bytes([0-9]*)(\[[0-9]*\])?`).FindStringSubmatch(stringKind)
  185. if len(parts) != 3 {
  186. return stringKind
  187. }
  188. if parts[2] != "" {
  189. return "byte[][]"
  190. }
  191. return "byte[]"
  192. case strings.HasPrefix(stringKind, "int") || strings.HasPrefix(stringKind, "uint"):
  193. parts := regexp.MustCompile(`(u)?int([0-9]*)(\[[0-9]*\])?`).FindStringSubmatch(stringKind)
  194. if len(parts) != 4 {
  195. return stringKind
  196. }
  197. switch parts[2] {
  198. case "8", "16", "32", "64":
  199. if parts[1] == "" {
  200. if parts[3] == "" {
  201. return fmt.Sprintf("int%s", parts[2])
  202. }
  203. return fmt.Sprintf("int%s[]", parts[2])
  204. }
  205. }
  206. if parts[3] == "" {
  207. return fmt.Sprintf("BigInt")
  208. }
  209. return fmt.Sprintf("BigInts")
  210. case strings.HasPrefix(stringKind, "bool"):
  211. parts := regexp.MustCompile(`bool(\[[0-9]*\])?`).FindStringSubmatch(stringKind)
  212. if len(parts) != 2 {
  213. return stringKind
  214. }
  215. if parts[1] == "" {
  216. return fmt.Sprintf("bool")
  217. }
  218. return fmt.Sprintf("bool[]")
  219. case strings.HasPrefix(stringKind, "string"):
  220. parts := regexp.MustCompile(`string(\[[0-9]*\])?`).FindStringSubmatch(stringKind)
  221. if len(parts) != 2 {
  222. return stringKind
  223. }
  224. if parts[1] == "" {
  225. return fmt.Sprintf("String")
  226. }
  227. return fmt.Sprintf("String[]")
  228. default:
  229. return stringKind
  230. }
  231. }
  232. // namedType is a set of functions that transform language specific types to
  233. // named versions that my be used inside method names.
  234. var namedType = map[Lang]func(string, abi.Type) string{
  235. LangGo: func(string, abi.Type) string { panic("this shouldn't be needed") },
  236. LangJava: namedTypeJava,
  237. }
  238. // namedTypeJava converts some primitive data types to named variants that can
  239. // be used as parts of method names.
  240. func namedTypeJava(javaKind string, solKind abi.Type) string {
  241. switch javaKind {
  242. case "byte[]":
  243. return "Binary"
  244. case "byte[][]":
  245. return "Binaries"
  246. case "string":
  247. return "String"
  248. case "string[]":
  249. return "Strings"
  250. case "bool":
  251. return "Bool"
  252. case "bool[]":
  253. return "Bools"
  254. case "BigInt":
  255. parts := regexp.MustCompile(`(u)?int([0-9]*)(\[[0-9]*\])?`).FindStringSubmatch(solKind.String())
  256. if len(parts) != 4 {
  257. return javaKind
  258. }
  259. switch parts[2] {
  260. case "8", "16", "32", "64":
  261. if parts[3] == "" {
  262. return capitalise(fmt.Sprintf("%sint%s", parts[1], parts[2]))
  263. }
  264. return capitalise(fmt.Sprintf("%sint%ss", parts[1], parts[2]))
  265. default:
  266. return javaKind
  267. }
  268. default:
  269. return javaKind
  270. }
  271. }
  272. // methodNormalizer is a name transformer that modifies Solidity method names to
  273. // conform to target language naming concentions.
  274. var methodNormalizer = map[Lang]func(string) string{
  275. LangGo: capitalise,
  276. LangJava: decapitalise,
  277. }
  278. // capitalise makes the first character of a string upper case.
  279. func capitalise(input string) string {
  280. return strings.ToUpper(input[:1]) + input[1:]
  281. }
  282. // decapitalise makes the first character of a string lower case.
  283. func decapitalise(input string) string {
  284. return strings.ToLower(input[:1]) + input[1:]
  285. }
  286. // structured checks whether a method has enough information to return a proper
  287. // Go struct ot if flat returns are needed.
  288. func structured(method abi.Method) bool {
  289. if len(method.Outputs) < 2 {
  290. return false
  291. }
  292. for _, out := range method.Outputs {
  293. if out.Name == "" {
  294. return false
  295. }
  296. }
  297. return true
  298. }