From d0b3536593fafe19ad31225387d09a62cac6196c Mon Sep 17 00:00:00 2001
From: zelig <viktor.tron@gmail.com>
Date: Tue, 31 Mar 2015 00:49:38 +0100
Subject: [PATCH] new natspec - constructor takes abidoc, userdoc - json
 parsing of userdoc - method found by abi data - notice found from method

---
 common/natspec/natspec.go      |  93 +++++++++++++++++++--
 common/natspec/natspec_test.go | 144 +++++++++++++++++++++++----------
 2 files changed, 186 insertions(+), 51 deletions(-)

diff --git a/common/natspec/natspec.go b/common/natspec/natspec.go
index 8e6568cef1..f6b91d03d6 100644
--- a/common/natspec/natspec.go
+++ b/common/natspec/natspec.go
@@ -1,19 +1,40 @@
 package natspec
 
 import (
+	"encoding/json"
 	"fmt"
 	"github.com/robertkrimen/otto"
+	"strings"
+
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/crypto"
 )
 
+type abi2method map[[8]byte]*method
+
 type NatSpec struct {
-	jsvm *otto.Otto
+	jsvm                    *otto.Otto
+	methods                 abi2method
+	userDocJson, abiDocJson []byte
+	userDoc                 userDoc
+	// abiDoc abiDoc
 }
 
 // TODO: should initialise with abi and userdoc jsons
 func New() (self *NatSpec, err error) {
+	// fetch abi, userdoc
+	abi := []byte("")
+	userdoc := []byte("")
+	return NewWithDocs(abi, userdoc)
+}
 
-	self = new(NatSpec)
-	self.jsvm = otto.New()
+func NewWithDocs(abiDocJson, userDocJson []byte) (self *NatSpec, err error) {
+
+	self = &NatSpec{
+		jsvm:        otto.New(),
+		abiDocJson:  abiDocJson,
+		userDocJson: userDocJson,
+	}
 
 	_, err = self.jsvm.Run(natspecJS)
 	if err != nil {
@@ -24,20 +45,76 @@ func New() (self *NatSpec, err error) {
 		return
 	}
 
+	err = json.Unmarshal(userDocJson, &self.userDoc)
+	// err = parseAbiJson(abiDocJson, &self.abiDoc)
+
+	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`
+// }
+
+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) {
+	if self.methods != nil {
+		meth = self.methods[abiKey]
+		return
+	}
+	self.methods = make(abi2method)
+	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])
+		self.methods[key] = meth
+		meth.name = name
+		if key == abiKey {
+			meth = m
+		}
+	}
+	return
+}
+
+func (self *NatSpec) Notice(tx string, abi string) (notice string, err error) {
+	var abiKey [8]byte
+	copy(abiKey[:], []byte(abi)[:8])
+	meth := self.makeAbi2method(abiKey)
+	if meth == nil {
+		err = fmt.Errorf("abi key %x does not match any method %v")
+		return
+	}
+	notice, err = self.noticeForMethod(tx, meth.name, meth.Notice)
 	return
 }
 
-func (self *NatSpec) Notice(transaction, abi, method, expression string) (string, error) {
-	var err error
-	if _, err = self.jsvm.Run("var transaction = " + transaction + ";"); err != nil {
+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 = " + abi + ";"); err != nil {
+	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 = '" + method + "';"); err != nil {
+	if _, err = self.jsvm.Run("var method = '" + name + "';"); err != nil {
 		return "", fmt.Errorf("natspec.js error setting method: %v", err)
 	}
 
diff --git a/common/natspec/natspec_test.go b/common/natspec/natspec_test.go
index 3b548817b9..39c80a1552 100644
--- a/common/natspec/natspec_test.go
+++ b/common/natspec/natspec_test.go
@@ -4,87 +4,145 @@ import (
 	"testing"
 )
 
-func TestNotice(t *testing.T) {
+func makeUserdoc(desc string) []byte {
+	return []byte(`
+{
+  "source": "...",
+  "language": "Solidity",
+  "languageVersion": 1,
+  "methods": {
+    "multiply(uint256)": {
+      "notice":  ` + desc + `
+    },
+    "balance(address)": {
+      "notice": "` + "`(balanceInmGAV / 1000).fixed(0,3)`" + ` GAV is the total funds available to ` + "`who.address()`." + `
+    }
+  },
+  "invariants": [
+    { "notice": "The sum total amount of GAV in the system is 1 million." }
+  ],
+  "construction": [
+    { "notice": "Endows ` + "`message.caller.address()`" + ` with 1m GAV." }
+  ]
+}
+`)
+}
 
-	tx := `
-	{
-    "jsonrpc": "2.0",
-    "method": "eth_call",
-    "params": [{
-        "to": "0x8521742d3f456bd237e312d6e30724960f72517a",
-        "data": "0xc6888fa1000000000000000000000000000000000000000000000000000000000000007a"
-    }],
-    "id": 6
-  }
-	`
-
-	abi := `
-	[{
-    "name": "multiply",
-    "constant": false,
-    "type": "function",
-    "inputs": [{
-      "name": "a",
-      "type": "uint256"
-    }],
-    "outputs": [{
-      "name": "d",
-      "type": "uint256"
-    }]
+var data = "0xc6888fa1000000000000000000000000000000000000000000000000000000000000007a"
+
+var tx = `
+{
+  "jsonrpc": "2.0",
+  "method": "eth_call",
+  "params": [{
+      "to": "0x8521742d3f456bd237e312d6e30724960f72517a",
+      "data": "0xc6888fa1000000000000000000000000000000000000000000000000000000000000007a"
+  }],
+  "id": 6
+}
+`
+
+var abi = []byte(`
+[{
+  "name": "multiply",
+  "constant": false,
+  "type": "function",
+  "inputs": [{
+    "name": "a",
+    "type": "uint256"
+  }],
+  "outputs": [{
+    "name": "d",
+    "type": "uint256"
   }]
-	`
+}]
+`)
+
+func TestNotice(t *testing.T) {
 
 	desc := "Will multiply `a` by 7 and return `a * 7`."
+	expected := "Will multiply 122 by 7 and return 854."
 
-	method := "multiply"
+	userdoc := makeUserdoc(desc)
 
-	ns, err := New()
+	ns, err := NewWithDocs(abi, userdoc)
 	if err != nil {
-		t.Errorf("NewNATSpec error %v", err)
+		t.Errorf("New: error: %v", err)
 	}
 
-	notice, err := ns.Notice(tx, abi, method, desc)
+	notice, err := ns.Notice(tx, desc)
 
 	if err != nil {
-		t.Errorf("expected no error got %v", err)
+		t.Errorf("expected no error, got %v", err)
 	}
 
-	expected := "Will multiply 122 by 7 and return 854."
 	if notice != expected {
 		t.Errorf("incorrect notice. expected %v, got %v", expected, notice)
 	} else {
 		t.Logf("returned notice \"%v\"", notice)
 	}
+}
 
-	notice, err = ns.Notice(tx, abi, method, "Will multiply 122 by \"7\" and return 854.")
+// test missing method
+func TestMissingMethod(t *testing.T) {
 
-	expected = "natspec.js error setting expression: (anonymous): Line 1:41 Unexpected number"
+	desc := "Will multiply `a` by 7 and return `a * 7`."
+	userdoc := makeUserdoc(desc)
+	expected := "natspec.js error evaluating expression: Natspec evaluation failed, method does not exist"
+
+	ns, err := NewWithDocs(abi, userdoc)
+	if err != nil {
+		t.Errorf("New: error: %v", err)
+	}
+
+	notice, err := ns.noticeForMethod(tx, "missing_method", "")
 
 	if err == nil {
-		t.Errorf("expected error, got nothing (notice: '%v')", err, notice)
+		t.Errorf("expected error, got nothing (notice: '%v')", notice)
 	} else {
 		if err.Error() != expected {
 			t.Errorf("expected error '%s' got '%v' (notice: '%v')", expected, err, notice)
 		}
 	}
+}
 
-	// https://github.com/ethereum/natspec.js/issues/1
-	badDesc := "Will multiply `e` by 7 and return `a * 7`."
-	notice, err = ns.Notice(tx, abi, method, badDesc)
+// test invalid desc
+func TestInvalidDesc(t *testing.T) {
 
-	expected = "natspec.js error evaluating expression: Natspec evaluation failed, wrong input params"
+	desc := "Will multiply 122 by \"7\" and return 854."
+	expected := "natspec.js error setting expression: (anonymous): Line 1:41 Unexpected number"
+
+	userdoc := makeUserdoc(desc)
+
+	ns, err := NewWithDocs(abi, userdoc)
+	if err != nil {
+		t.Errorf("New: error: %v", err)
+	}
+	notice, err := ns.Notice(tx, data)
 
 	if err == nil {
-		t.Errorf("expected error, got nothing (notice: '%v')", notice)
+		t.Errorf("expected error, got nothing (notice: '%v')", err, notice)
 	} else {
 		if err.Error() != expected {
 			t.Errorf("expected error '%s' got '%v' (notice: '%v')", expected, err, notice)
 		}
 	}
+}
+
+// test wrong input params
+func TestWrongInputParams(t *testing.T) {
+
+	desc := "Will multiply `e` by 7 and return `a * 7`."
+	expected := "natspec.js error evaluating expression: Natspec evaluation failed, wrong input params"
 
-	notice, err = ns.Notice(tx, abi, "missing_method", desc)
+	userdoc := makeUserdoc(desc)
+
+	ns, err := NewWithDocs(abi, userdoc)
+	if err != nil {
+		t.Errorf("New: error: %v", err)
+	}
 
-	expected = "natspec.js error evaluating expression: Natspec evaluation failed, method does not exist"
+	notice, err := ns.Notice(tx, desc)
 
 	if err == nil {
 		t.Errorf("expected error, got nothing (notice: '%v')", notice)