new natspec

- constructor takes abidoc, userdoc
- json parsing of userdoc
- method found by abi data
- notice found from method
This commit is contained in:
zelig 2015-03-31 00:49:38 +01:00
parent ea11dba00b
commit d0b3536593
2 changed files with 200 additions and 65 deletions

@ -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
}
func (self *NatSpec) Notice(transaction, abi, method, expression string) (string, error) {
var err error
if _, err = self.jsvm.Run("var transaction = " + transaction + ";"); err != nil {
// 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) 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)
}

@ -4,75 +4,98 @@ import (
"testing"
)
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." }
]
}
`)
}
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) {
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"
}]
}]
`
desc := "Will multiply `a` by 7 and return `a * 7`."
method := "multiply"
ns, err := New()
if err != nil {
t.Errorf("NewNATSpec error %v", err)
}
notice, err := ns.Notice(tx, abi, method, desc)
if err != nil {
t.Errorf("expected no error got %v", err)
}
expected := "Will multiply 122 by 7 and return 854."
userdoc := makeUserdoc(desc)
ns, err := NewWithDocs(abi, userdoc)
if err != nil {
t.Errorf("New: error: %v", err)
}
notice, err := ns.Notice(tx, desc)
if err != nil {
t.Errorf("expected no error, got %v", err)
}
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"
if err == nil {
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)
}
ns, err := NewWithDocs(abi, userdoc)
if err != nil {
t.Errorf("New: error: %v", err)
}
// 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)
expected = "natspec.js error evaluating expression: Natspec evaluation failed, wrong input params"
notice, err := ns.noticeForMethod(tx, "missing_method", "")
if err == nil {
t.Errorf("expected error, got nothing (notice: '%v')", notice)
@ -81,10 +104,45 @@ func TestNotice(t *testing.T) {
t.Errorf("expected error '%s' got '%v' (notice: '%v')", expected, err, notice)
}
}
}
notice, err = ns.Notice(tx, abi, "missing_method", desc)
// test invalid desc
func TestInvalidDesc(t *testing.T) {
expected = "natspec.js error evaluating expression: Natspec evaluation failed, method does not exist"
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')", 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"
userdoc := makeUserdoc(desc)
ns, err := NewWithDocs(abi, userdoc)
if err != nil {
t.Errorf("New: error: %v", err)
}
notice, err := ns.Notice(tx, desc)
if err == nil {
t.Errorf("expected error, got nothing (notice: '%v')", notice)