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