| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260 |
- // Copyright 2015 The go-ethereum Authors
- // This file is part of go-ethereum.
- //
- // go-ethereum is free software: you can redistribute it and/or modify
- // it under the terms of the GNU Lesser General Public License as published by
- // the Free Software Foundation, either version 3 of the License, or
- // (at your option) any later version.
- //
- // go-ethereum is distributed in the hope that it will be useful,
- // but WITHOUT ANY WARRANTY; without even the implied warranty of
- // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- // GNU Lesser General Public License for more details.
- //
- // You should have received a copy of the GNU Lesser General Public License
- // along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
- package natspec
- import (
- "bytes"
- "encoding/json"
- "fmt"
- "github.com/robertkrimen/otto"
- "strings"
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/common/docserver"
- "github.com/ethereum/go-ethereum/common/registrar"
- "github.com/ethereum/go-ethereum/crypto"
- "github.com/ethereum/go-ethereum/xeth"
- )
- type abi2method map[[8]byte]*method
- type NatSpec struct {
- jsvm *otto.Otto
- abiDocJson []byte
- userDoc userDoc
- tx, data string
- }
- // main entry point for to get natspec notice for a transaction
- // the implementation is frontend friendly in that it always gives back
- // a notice that is safe to display
- // :FIXME: the second return value is an error, which can be used to fine-tune bahaviour
- func GetNotice(xeth *xeth.XEth, tx string, http *docserver.DocServer) (notice string) {
- ns, err := New(xeth, tx, http)
- if err != nil {
- if ns == nil {
- return getFallbackNotice(fmt.Sprintf("no NatSpec info found for contract: %v", err), tx)
- } else {
- return getFallbackNotice(fmt.Sprintf("invalid NatSpec info: %v", err), tx)
- }
- }
- notice, err = ns.Notice()
- if err != nil {
- return getFallbackNotice(fmt.Sprintf("NatSpec notice error: %v", err), tx)
- }
- return
- }
- func getFallbackNotice(comment, tx string) string {
- return fmt.Sprintf("About to submit transaction (%s): %s", comment, tx)
- }
- type transaction struct {
- To string `json:"to"`
- Data string `json:"data"`
- }
- type jsonTx struct {
- Params []transaction `json:"params"`
- }
- type contractInfo struct {
- Source string `json:"source"`
- Language string `json:"language"`
- Version string `json:"compilerVersion"`
- AbiDefinition json.RawMessage `json:"abiDefinition"`
- UserDoc userDoc `json:"userDoc"`
- DeveloperDoc json.RawMessage `json:"developerDoc"`
- }
- func New(xeth *xeth.XEth, jsontx string, http *docserver.DocServer) (self *NatSpec, err error) {
- // extract contract address from tx
- var tx jsonTx
- err = json.Unmarshal([]byte(jsontx), &tx)
- if err != nil {
- return
- }
- t := tx.Params[0]
- contractAddress := t.To
- content, err := FetchDocsForContract(contractAddress, xeth, http)
- if err != nil {
- return
- }
- self, err = NewWithDocs(content, jsontx, t.Data)
- return
- }
- // also called by admin.contractInfo.get
- func FetchDocsForContract(contractAddress string, xeth *xeth.XEth, ds *docserver.DocServer) (content []byte, err error) {
- // retrieve contract hash from state
- codehex := xeth.CodeAt(contractAddress)
- codeb := xeth.CodeAtBytes(contractAddress)
- if codehex == "0x" {
- err = fmt.Errorf("contract (%v) not found", contractAddress)
- return
- }
- codehash := common.BytesToHash(crypto.Sha3(codeb))
- // set up nameresolver with natspecreg + urlhint contract addresses
- reg := registrar.New(xeth)
- // resolve host via HashReg/UrlHint Resolver
- hash, err := reg.HashToHash(codehash)
- if err != nil {
- return
- }
- if ds.HasScheme("bzz") {
- content, err = ds.Get("bzz://"+hash.Hex()[2:], "")
- if err == nil { // non-fatal
- return
- }
- err = nil
- //falling back to urlhint
- }
- uri, err := reg.HashToUrl(hash)
- if err != nil {
- return
- }
- // get content via http client and authenticate content using hash
- content, err = ds.GetAuthContent(uri, hash)
- if err != nil {
- return
- }
- return
- }
- func NewWithDocs(infoDoc []byte, tx string, data string) (self *NatSpec, err error) {
- var contract contractInfo
- err = json.Unmarshal(infoDoc, &contract)
- if err != nil {
- return
- }
- self = &NatSpec{
- jsvm: otto.New(),
- abiDocJson: []byte(contract.AbiDefinition),
- userDoc: contract.UserDoc,
- tx: tx,
- data: data,
- }
- // load and require natspec js (but it is meant to be protected environment)
- _, err = self.jsvm.Run(natspecJS)
- if err != nil {
- return
- }
- _, err = self.jsvm.Run("var natspec = require('natspec');")
- return
- }
- // type abiDoc []method
- // type method struct {
- // Name string `json:name`
- // Inputs []input `json:inputs`
- // abiKey [8]byte
- // }
- // type input struct {
- // Name string `json:name`
- // Type string `json:type`
- // }
- // json skeleton for abi doc (contract method definitions)
- type method struct {
- Notice string `json:notice`
- name string
- }
- type userDoc struct {
- Methods map[string]*method `json:methods`
- }
- func (self *NatSpec) makeAbi2method(abiKey [8]byte) (meth *method) {
- for signature, m := range self.userDoc.Methods {
- name := strings.Split(signature, "(")[0]
- hash := []byte(common.Bytes2Hex(crypto.Sha3([]byte(signature))))
- var key [8]byte
- copy(key[:], hash[:8])
- if bytes.Equal(key[:], abiKey[:]) {
- meth = m
- meth.name = name
- return
- }
- }
- return
- }
- func (self *NatSpec) Notice() (notice string, err error) {
- var abiKey [8]byte
- if len(self.data) < 10 {
- err = fmt.Errorf("Invalid transaction data")
- return
- }
- copy(abiKey[:], self.data[2:10])
- meth := self.makeAbi2method(abiKey)
- if meth == nil {
- err = fmt.Errorf("abi key does not match any method")
- return
- }
- notice, err = self.noticeForMethod(self.tx, meth.name, meth.Notice)
- return
- }
- func (self *NatSpec) noticeForMethod(tx string, name, expression string) (notice string, err error) {
- if _, err = self.jsvm.Run("var transaction = " + tx + ";"); err != nil {
- return "", fmt.Errorf("natspec.js error setting transaction: %v", err)
- }
- if _, err = self.jsvm.Run("var abi = " + string(self.abiDocJson) + ";"); err != nil {
- return "", fmt.Errorf("natspec.js error setting abi: %v", err)
- }
- if _, err = self.jsvm.Run("var method = '" + name + "';"); err != nil {
- return "", fmt.Errorf("natspec.js error setting method: %v", err)
- }
- if _, err = self.jsvm.Run("var expression = \"" + expression + "\";"); err != nil {
- return "", fmt.Errorf("natspec.js error setting expression: %v", err)
- }
- self.jsvm.Run("var call = {method: method,abi: abi,transaction: transaction};")
- value, err := self.jsvm.Run("natspec.evaluateExpression(expression, call);")
- if err != nil {
- return "", fmt.Errorf("natspec.js error evaluating expression: %v", err)
- }
- evalError := "Natspec evaluation failed, wrong input params"
- if value.String() == evalError {
- return "", fmt.Errorf("natspec.js error evaluating expression: wrong input params in expression '%s'", expression)
- }
- if len(value.String()) == 0 {
- return "", fmt.Errorf("natspec.js error evaluating expression")
- }
- return value.String(), nil
- }
|