natspec.go 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  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. // +build ignore
  17. package natspec
  18. import (
  19. "bytes"
  20. "encoding/json"
  21. "fmt"
  22. "strings"
  23. "github.com/ethereum/go-ethereum/common"
  24. "github.com/ethereum/go-ethereum/common/httpclient"
  25. "github.com/ethereum/go-ethereum/common/registrar"
  26. "github.com/ethereum/go-ethereum/crypto"
  27. "github.com/ethereum/go-ethereum/xeth"
  28. "github.com/robertkrimen/otto"
  29. )
  30. type abi2method map[[8]byte]*method
  31. type NatSpec struct {
  32. jsvm *otto.Otto
  33. abiDocJson []byte
  34. userDoc userDoc
  35. tx, data string
  36. }
  37. // main entry point for to get natspec notice for a transaction
  38. // the implementation is frontend friendly in that it always gives back
  39. // a notice that is safe to display
  40. // :FIXME: the second return value is an error, which can be used to fine-tune bahaviour
  41. func GetNotice(xeth *xeth.XEth, tx string, http *httpclient.HTTPClient) (notice string) {
  42. ns, err := New(xeth, tx, http)
  43. if err != nil {
  44. if ns == nil {
  45. return getFallbackNotice(fmt.Sprintf("no NatSpec info found for contract: %v", err), tx)
  46. } else {
  47. return getFallbackNotice(fmt.Sprintf("invalid NatSpec info: %v", err), tx)
  48. }
  49. }
  50. notice, err = ns.Notice()
  51. if err != nil {
  52. return getFallbackNotice(fmt.Sprintf("NatSpec notice error: %v", err), tx)
  53. }
  54. return
  55. }
  56. func getFallbackNotice(comment, tx string) string {
  57. return fmt.Sprintf("About to submit transaction (%s): %s", comment, tx)
  58. }
  59. type transaction struct {
  60. To string `json:"to"`
  61. Data string `json:"data"`
  62. }
  63. type jsonTx struct {
  64. Params []transaction `json:"params"`
  65. }
  66. type contractInfo struct {
  67. Source string `json:"source"`
  68. Language string `json:"language"`
  69. Version string `json:"compilerVersion"`
  70. AbiDefinition json.RawMessage `json:"abiDefinition"`
  71. UserDoc userDoc `json:"userDoc"`
  72. DeveloperDoc json.RawMessage `json:"developerDoc"`
  73. }
  74. func New(xeth *xeth.XEth, jsontx string, http *httpclient.HTTPClient) (self *NatSpec, err error) {
  75. // extract contract address from tx
  76. var tx jsonTx
  77. err = json.Unmarshal([]byte(jsontx), &tx)
  78. if err != nil {
  79. return
  80. }
  81. t := tx.Params[0]
  82. contractAddress := t.To
  83. content, err := FetchDocsForContract(contractAddress, xeth, http)
  84. if err != nil {
  85. return
  86. }
  87. self, err = NewWithDocs(content, jsontx, t.Data)
  88. return
  89. }
  90. // also called by admin.contractInfo.get
  91. func FetchDocsForContract(contractAddress string, xeth *xeth.XEth, client *httpclient.HTTPClient) (content []byte, err error) {
  92. // retrieve contract hash from state
  93. codehex := xeth.CodeAt(contractAddress)
  94. codeb := xeth.CodeAtBytes(contractAddress)
  95. if codehex == "0x" {
  96. err = fmt.Errorf("contract (%v) not found", contractAddress)
  97. return
  98. }
  99. codehash := common.BytesToHash(crypto.Keccak256(codeb))
  100. // set up nameresolver with natspecreg + urlhint contract addresses
  101. reg := registrar.New(xeth)
  102. // resolve host via HashReg/UrlHint Resolver
  103. hash, err := reg.HashToHash(codehash)
  104. if err != nil {
  105. return
  106. }
  107. if client.HasScheme("bzz") {
  108. content, err = client.Get("bzz://"+hash.Hex()[2:], "")
  109. if err == nil { // non-fatal
  110. return
  111. }
  112. err = nil
  113. //falling back to urlhint
  114. }
  115. uri, err := reg.HashToUrl(hash)
  116. if err != nil {
  117. return
  118. }
  119. // get content via http client and authenticate content using hash
  120. content, err = client.GetAuthContent(uri, hash)
  121. if err != nil {
  122. return
  123. }
  124. return
  125. }
  126. func NewWithDocs(infoDoc []byte, tx string, data string) (self *NatSpec, err error) {
  127. var contract contractInfo
  128. err = json.Unmarshal(infoDoc, &contract)
  129. if err != nil {
  130. return
  131. }
  132. self = &NatSpec{
  133. jsvm: otto.New(),
  134. abiDocJson: []byte(contract.AbiDefinition),
  135. userDoc: contract.UserDoc,
  136. tx: tx,
  137. data: data,
  138. }
  139. // load and require natspec js (but it is meant to be protected environment)
  140. _, err = self.jsvm.Run(natspecJS)
  141. if err != nil {
  142. return
  143. }
  144. _, err = self.jsvm.Run("var natspec = require('natspec');")
  145. return
  146. }
  147. // type abiDoc []method
  148. // type method struct {
  149. // Name string `json:name`
  150. // Inputs []input `json:inputs`
  151. // abiKey [8]byte
  152. // }
  153. // type input struct {
  154. // Name string `json:name`
  155. // Type string `json:type`
  156. // }
  157. // json skeleton for abi doc (contract method definitions)
  158. type method struct {
  159. Notice string `json:notice`
  160. name string
  161. }
  162. type userDoc struct {
  163. Methods map[string]*method `json:methods`
  164. }
  165. func (self *NatSpec) makeAbi2method(abiKey [8]byte) (meth *method) {
  166. for signature, m := range self.userDoc.Methods {
  167. name := strings.Split(signature, "(")[0]
  168. hash := []byte(common.Bytes2Hex(crypto.Keccak256([]byte(signature))))
  169. var key [8]byte
  170. copy(key[:], hash[:8])
  171. if bytes.Equal(key[:], abiKey[:]) {
  172. meth = m
  173. meth.name = name
  174. return
  175. }
  176. }
  177. return
  178. }
  179. func (self *NatSpec) Notice() (notice string, err error) {
  180. var abiKey [8]byte
  181. if len(self.data) < 10 {
  182. err = fmt.Errorf("Invalid transaction data")
  183. return
  184. }
  185. copy(abiKey[:], self.data[2:10])
  186. meth := self.makeAbi2method(abiKey)
  187. if meth == nil {
  188. err = fmt.Errorf("abi key does not match any method")
  189. return
  190. }
  191. notice, err = self.noticeForMethod(self.tx, meth.name, meth.Notice)
  192. return
  193. }
  194. func (self *NatSpec) noticeForMethod(tx string, name, expression string) (notice string, err error) {
  195. if _, err = self.jsvm.Run("var transaction = " + tx + ";"); err != nil {
  196. return "", fmt.Errorf("natspec.js error setting transaction: %v", err)
  197. }
  198. if _, err = self.jsvm.Run("var abi = " + string(self.abiDocJson) + ";"); err != nil {
  199. return "", fmt.Errorf("natspec.js error setting abi: %v", err)
  200. }
  201. if _, err = self.jsvm.Run("var method = '" + name + "';"); err != nil {
  202. return "", fmt.Errorf("natspec.js error setting method: %v", err)
  203. }
  204. if _, err = self.jsvm.Run("var expression = \"" + expression + "\";"); err != nil {
  205. return "", fmt.Errorf("natspec.js error setting expression: %v", err)
  206. }
  207. self.jsvm.Run("var call = {method: method,abi: abi,transaction: transaction};")
  208. value, err := self.jsvm.Run("natspec.evaluateExpression(expression, call);")
  209. if err != nil {
  210. return "", fmt.Errorf("natspec.js error evaluating expression: %v", err)
  211. }
  212. evalError := "Natspec evaluation failed, wrong input params"
  213. if value.String() == evalError {
  214. return "", fmt.Errorf("natspec.js error evaluating expression: wrong input params in expression '%s'", expression)
  215. }
  216. if len(value.String()) == 0 {
  217. return "", fmt.Errorf("natspec.js error evaluating expression")
  218. }
  219. return value.String(), nil
  220. }