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