aboutsummaryrefslogtreecommitdiffstats
path: root/ethutil
diff options
context:
space:
mode:
authorobscuren <geffobscura@gmail.com>2014-08-21 21:46:26 +0800
committerobscuren <geffobscura@gmail.com>2014-08-21 21:46:26 +0800
commit0af0f0d890120e007ce42f072e1ee179a62115d3 (patch)
tree5ae9ecafbb729d1636fadfcfa49fd9100959560c /ethutil
parentd761af84c83ae8d9d723e6766abb7950ff59cdf3 (diff)
parentc173e9f4ab463cf3a44d35215bc29d846d6f6b02 (diff)
downloaddexon-0af0f0d890120e007ce42f072e1ee179a62115d3.tar.gz
dexon-0af0f0d890120e007ce42f072e1ee179a62115d3.tar.zst
dexon-0af0f0d890120e007ce42f072e1ee179a62115d3.zip
Merge branch 'release/0.6.3'
Diffstat (limited to 'ethutil')
-rw-r--r--ethutil/bytes.go77
-rw-r--r--ethutil/bytes_test.go14
-rw-r--r--ethutil/common.go35
-rw-r--r--ethutil/config.go6
-rw-r--r--ethutil/path.go40
-rw-r--r--ethutil/reactor.go87
-rw-r--r--ethutil/reactor_test.go30
-rw-r--r--ethutil/rlp.go153
-rw-r--r--ethutil/rlp_test.go11
-rw-r--r--ethutil/value.go103
-rw-r--r--ethutil/value_test.go15
11 files changed, 352 insertions, 219 deletions
diff --git a/ethutil/bytes.go b/ethutil/bytes.go
index 34fff7d42..e38f89454 100644
--- a/ethutil/bytes.go
+++ b/ethutil/bytes.go
@@ -44,26 +44,28 @@ func BytesToNumber(b []byte) uint64 {
// Read variable int
//
// Read a variable length number in big endian byte order
-func ReadVarint(reader *bytes.Reader) (ret uint64) {
- if reader.Len() == 8 {
- var num uint64
- binary.Read(reader, binary.BigEndian, &num)
- ret = uint64(num)
- } else if reader.Len() == 4 {
+func ReadVarInt(buff []byte) (ret uint64) {
+ switch l := len(buff); {
+ case l > 4:
+ d := LeftPadBytes(buff, 8)
+ binary.Read(bytes.NewReader(d), binary.BigEndian, &ret)
+ case l > 2:
var num uint32
- binary.Read(reader, binary.BigEndian, &num)
+ d := LeftPadBytes(buff, 4)
+ binary.Read(bytes.NewReader(d), binary.BigEndian, &num)
ret = uint64(num)
- } else if reader.Len() == 2 {
+ case l > 1:
var num uint16
- binary.Read(reader, binary.BigEndian, &num)
+ d := LeftPadBytes(buff, 2)
+ binary.Read(bytes.NewReader(d), binary.BigEndian, &num)
ret = uint64(num)
- } else {
+ default:
var num uint8
- binary.Read(reader, binary.BigEndian, &num)
+ binary.Read(bytes.NewReader(buff), binary.BigEndian, &num)
ret = uint64(num)
}
- return ret
+ return
}
// Binary length
@@ -98,6 +100,7 @@ func Bytes2Hex(d []byte) string {
func Hex2Bytes(str string) []byte {
h, _ := hex.DecodeString(str)
+
return h
}
@@ -128,6 +131,26 @@ func FormatData(data string) []byte {
return BigToBytes(d, 256)
}
+func ParseData(data ...interface{}) (ret []byte) {
+ for _, item := range data {
+ switch t := item.(type) {
+ case string:
+ var str []byte
+ if IsHex(t) {
+ str = Hex2Bytes(t[2:])
+ } else {
+ str = []byte(t)
+ }
+
+ ret = append(ret, RightPadBytes(str, 32)...)
+ case []byte:
+ ret = append(ret, LeftPadBytes(t, 32)...)
+ }
+ }
+
+ return
+}
+
func RightPadBytes(slice []byte, l int) []byte {
if l < len(slice) {
return slice
@@ -150,6 +173,28 @@ func LeftPadBytes(slice []byte, l int) []byte {
return padded
}
+func LeftPadString(str string, l int) string {
+ if l < len(str) {
+ return str
+ }
+
+ zeros := Bytes2Hex(make([]byte, (l-len(str))/2))
+
+ return zeros + str
+
+}
+
+func RightPadString(str string, l int) string {
+ if l < len(str) {
+ return str
+ }
+
+ zeros := Bytes2Hex(make([]byte, (l-len(str))/2))
+
+ return str + zeros
+
+}
+
func Address(slice []byte) (addr []byte) {
if len(slice) < 20 {
addr = LeftPadBytes(slice, 20)
@@ -163,3 +208,11 @@ func Address(slice []byte) (addr []byte) {
return
}
+
+func ByteSliceToInterface(slice [][]byte) (ret []interface{}) {
+ for _, i := range slice {
+ ret = append(ret, i)
+ }
+
+ return
+}
diff --git a/ethutil/bytes_test.go b/ethutil/bytes_test.go
new file mode 100644
index 000000000..381efe7a2
--- /dev/null
+++ b/ethutil/bytes_test.go
@@ -0,0 +1,14 @@
+package ethutil
+
+import (
+ "bytes"
+ "testing"
+)
+
+func TestParseData(t *testing.T) {
+ data := ParseData("hello", "world", "0x0106")
+ exp := "68656c6c6f000000000000000000000000000000000000000000000000000000776f726c640000000000000000000000000000000000000000000000000000000106000000000000000000000000000000000000000000000000000000000000"
+ if bytes.Compare(data, Hex2Bytes(exp)) != 0 {
+ t.Error("Error parsing data")
+ }
+}
diff --git a/ethutil/common.go b/ethutil/common.go
index cbd94e7ad..8532d80f2 100644
--- a/ethutil/common.go
+++ b/ethutil/common.go
@@ -34,26 +34,43 @@ var (
// Currency to string
// Returns a string representing a human readable format
func CurrencyToString(num *big.Int) string {
+ var (
+ fin *big.Int = num
+ denom string = "Wei"
+ )
+
switch {
case num.Cmp(Douglas) >= 0:
- return fmt.Sprintf("%v Douglas", new(big.Int).Div(num, Douglas))
+ fin = new(big.Int).Div(num, Douglas)
+ denom = "Douglas"
case num.Cmp(Einstein) >= 0:
- return fmt.Sprintf("%v Einstein", new(big.Int).Div(num, Einstein))
+ fin = new(big.Int).Div(num, Einstein)
+ denom = "Einstein"
case num.Cmp(Ether) >= 0:
- return fmt.Sprintf("%v Ether", new(big.Int).Div(num, Ether))
+ fin = new(big.Int).Div(num, Ether)
+ denom = "Ether"
case num.Cmp(Finney) >= 0:
- return fmt.Sprintf("%v Finney", new(big.Int).Div(num, Finney))
+ fin = new(big.Int).Div(num, Finney)
+ denom = "Finney"
case num.Cmp(Szabo) >= 0:
- return fmt.Sprintf("%v Szabo", new(big.Int).Div(num, Szabo))
+ fin = new(big.Int).Div(num, Szabo)
+ denom = "Szabo"
case num.Cmp(Shannon) >= 0:
- return fmt.Sprintf("%v Shannon", new(big.Int).Div(num, Shannon))
+ fin = new(big.Int).Div(num, Shannon)
+ denom = "Shannon"
case num.Cmp(Babbage) >= 0:
- return fmt.Sprintf("%v Babbage", new(big.Int).Div(num, Babbage))
+ fin = new(big.Int).Div(num, Babbage)
+ denom = "Babbage"
case num.Cmp(Ada) >= 0:
- return fmt.Sprintf("%v Ada", new(big.Int).Div(num, Ada))
+ fin = new(big.Int).Div(num, Ada)
+ denom = "Ada"
+ }
+
+ if len(fin.String()) > 5 {
+ return fmt.Sprintf("%sE%d %s", fin.String()[0:5], len(fin.String())-5, denom)
}
- return fmt.Sprintf("%v Wei", num)
+ return fmt.Sprintf("%v %s", fin, denom)
}
// Common big integers often used
diff --git a/ethutil/config.go b/ethutil/config.go
index 41bece21d..81052318e 100644
--- a/ethutil/config.go
+++ b/ethutil/config.go
@@ -3,8 +3,9 @@ package ethutil
import (
"flag"
"fmt"
- "github.com/rakyll/globalconf"
"os"
+
+ "github.com/rakyll/globalconf"
)
// Config struct
@@ -28,8 +29,7 @@ var Config *ConfigManager
func ReadConfig(ConfigFile string, Datadir string, EnvPrefix string) *ConfigManager {
if Config == nil {
// create ConfigFile if does not exist, otherwise globalconf panic when trying to persist flags
- _, err := os.Stat(ConfigFile)
- if err != nil && os.IsNotExist(err) {
+ if !FileExist(ConfigFile) {
fmt.Printf("config file '%s' doesn't exist, creating it\n", ConfigFile)
os.Create(ConfigFile)
}
diff --git a/ethutil/path.go b/ethutil/path.go
index 97f58ab7e..27022bcfa 100644
--- a/ethutil/path.go
+++ b/ethutil/path.go
@@ -1,6 +1,8 @@
package ethutil
import (
+ "io/ioutil"
+ "os"
"os/user"
"strings"
)
@@ -18,3 +20,41 @@ func ExpandHomePath(p string) (path string) {
return
}
+
+func FileExist(filePath string) bool {
+ _, err := os.Stat(filePath)
+ if err != nil && os.IsNotExist(err) {
+ return false
+ }
+
+ return true
+}
+
+func ReadAllFile(filePath string) (string, error) {
+ file, err := os.Open(filePath)
+ if err != nil {
+ return "", err
+ }
+
+ data, err := ioutil.ReadAll(file)
+ if err != nil {
+ return "", err
+ }
+
+ return string(data), nil
+}
+
+func WriteFile(filePath string, content []byte) error {
+ fh, err := os.OpenFile(filePath, os.O_RDWR|os.O_CREATE, os.ModePerm)
+ if err != nil {
+ return err
+ }
+ defer fh.Close()
+
+ _, err = fh.Write(content)
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
diff --git a/ethutil/reactor.go b/ethutil/reactor.go
deleted file mode 100644
index 7cf145245..000000000
--- a/ethutil/reactor.go
+++ /dev/null
@@ -1,87 +0,0 @@
-package ethutil
-
-import (
- "sync"
-)
-
-type ReactorEvent struct {
- mut sync.Mutex
- event string
- chans []chan React
-}
-
-// Post the specified reactor resource on the channels
-// currently subscribed
-func (e *ReactorEvent) Post(react React) {
- e.mut.Lock()
- defer e.mut.Unlock()
-
- for _, ch := range e.chans {
- go func(ch chan React) {
- ch <- react
- }(ch)
- }
-}
-
-// Add a subscriber to this event
-func (e *ReactorEvent) Add(ch chan React) {
- e.mut.Lock()
- defer e.mut.Unlock()
-
- e.chans = append(e.chans, ch)
-}
-
-// Remove a subscriber
-func (e *ReactorEvent) Remove(ch chan React) {
- e.mut.Lock()
- defer e.mut.Unlock()
-
- for i, c := range e.chans {
- if c == ch {
- e.chans = append(e.chans[:i], e.chans[i+1:]...)
- }
- }
-}
-
-// Basic reactor resource
-type React struct {
- Resource interface{}
- Event string
-}
-
-// The reactor basic engine. Acts as bridge
-// between the events and the subscribers/posters
-type ReactorEngine struct {
- patterns map[string]*ReactorEvent
-}
-
-func NewReactorEngine() *ReactorEngine {
- return &ReactorEngine{patterns: make(map[string]*ReactorEvent)}
-}
-
-// Subscribe a channel to the specified event
-func (reactor *ReactorEngine) Subscribe(event string, ch chan React) {
- ev := reactor.patterns[event]
- // Create a new event if one isn't available
- if ev == nil {
- ev = &ReactorEvent{event: event}
- reactor.patterns[event] = ev
- }
-
- // Add the channel to reactor event handler
- ev.Add(ch)
-}
-
-func (reactor *ReactorEngine) Unsubscribe(event string, ch chan React) {
- ev := reactor.patterns[event]
- if ev != nil {
- ev.Remove(ch)
- }
-}
-
-func (reactor *ReactorEngine) Post(event string, resource interface{}) {
- ev := reactor.patterns[event]
- if ev != nil {
- ev.Post(React{Resource: resource, Event: event})
- }
-}
diff --git a/ethutil/reactor_test.go b/ethutil/reactor_test.go
deleted file mode 100644
index 48c2f0df3..000000000
--- a/ethutil/reactor_test.go
+++ /dev/null
@@ -1,30 +0,0 @@
-package ethutil
-
-import "testing"
-
-func TestReactorAdd(t *testing.T) {
- engine := NewReactorEngine()
- ch := make(chan React)
- engine.Subscribe("test", ch)
- if len(engine.patterns) != 1 {
- t.Error("Expected patterns to be 1, got", len(engine.patterns))
- }
-}
-
-func TestReactorEvent(t *testing.T) {
- engine := NewReactorEngine()
-
- // Buffer 1, so it doesn't block for this test
- ch := make(chan React, 1)
- engine.Subscribe("test", ch)
- engine.Post("test", "hello")
-
- value := <-ch
- if val, ok := value.Resource.(string); ok {
- if val != "hello" {
- t.Error("Expected Resource to be 'hello', got", val)
- }
- } else {
- t.Error("Unable to cast")
- }
-}
diff --git a/ethutil/rlp.go b/ethutil/rlp.go
index 195ef0efb..17ff627eb 100644
--- a/ethutil/rlp.go
+++ b/ethutil/rlp.go
@@ -2,15 +2,16 @@ package ethutil
import (
"bytes"
- _ "encoding/binary"
"fmt"
- _ "log"
- _ "math"
"math/big"
)
-type RlpEncodable interface {
+type RlpEncode interface {
RlpEncode() []byte
+}
+
+type RlpEncodeDecode interface {
+ RlpEncode
RlpValue() []interface{}
}
@@ -32,12 +33,14 @@ const (
RlpEmptyStr = 0x40
)
+const rlpEof = -1
+
func Char(c []byte) int {
if len(c) > 0 {
return int(c[0])
}
- return 0
+ return rlpEof
}
func DecodeWithReader(reader *bytes.Buffer) interface{} {
@@ -46,8 +49,6 @@ func DecodeWithReader(reader *bytes.Buffer) interface{} {
// Read the next byte
char := Char(reader.Next(1))
switch {
- case char == 0:
- return nil
case char <= 0x7f:
return char
@@ -55,8 +56,7 @@ func DecodeWithReader(reader *bytes.Buffer) interface{} {
return reader.Next(int(char - 0x80))
case char <= 0xbf:
- buff := bytes.NewReader(reader.Next(int(char - 0xb8)))
- length := ReadVarint(buff)
+ length := ReadVarInt(reader.Next(int(char - 0xb7)))
return reader.Next(int(length))
@@ -64,82 +64,23 @@ func DecodeWithReader(reader *bytes.Buffer) interface{} {
length := int(char - 0xc0)
for i := 0; i < length; i++ {
obj := DecodeWithReader(reader)
- if obj != nil {
- slice = append(slice, obj)
- } else {
- break
- }
- }
-
- return slice
-
- }
-
- return slice
-}
-
-// TODO Use a bytes.Buffer instead of a raw byte slice.
-// Cleaner code, and use draining instead of seeking the next bytes to read
-func Decode(data []byte, pos uint64) (interface{}, uint64) {
- var slice []interface{}
- char := int(data[pos])
- switch {
- case char <= 0x7f:
- return data[pos], pos + 1
-
- case char <= 0xb7:
- b := uint64(data[pos]) - 0x80
-
- return data[pos+1 : pos+1+b], pos + 1 + b
-
- case char <= 0xbf:
- b := uint64(data[pos]) - 0xb7
-
- b2 := ReadVarint(bytes.NewReader(data[pos+1 : pos+1+b]))
-
- return data[pos+1+b : pos+1+b+b2], pos + 1 + b + b2
-
- case char <= 0xf7:
- b := uint64(data[pos]) - 0xc0
- prevPos := pos
- pos++
- for i := uint64(0); i < b; {
- var obj interface{}
-
- // Get the next item in the data list and append it
- obj, prevPos = Decode(data, pos)
slice = append(slice, obj)
-
- // Increment i by the amount bytes read in the previous
- // read
- i += (prevPos - pos)
- pos = prevPos
}
- return slice, pos
+ return slice
case char <= 0xff:
- l := uint64(data[pos]) - 0xf7
- b := ReadVarint(bytes.NewReader(data[pos+1 : pos+1+l]))
-
- pos = pos + l + 1
-
- prevPos := b
- for i := uint64(0); i < uint64(b); {
- var obj interface{}
-
- obj, prevPos = Decode(data, pos)
+ length := ReadVarInt(reader.Next(int(char - 0xf7)))
+ for i := uint64(0); i < length; i++ {
+ obj := DecodeWithReader(reader)
slice = append(slice, obj)
-
- i += (prevPos - pos)
- pos = prevPos
}
- return slice, pos
+ return slice
default:
panic(fmt.Sprintf("byte not supported: %q", char))
}
- return slice, 0
+ return slice
}
var (
@@ -223,3 +164,67 @@ func Encode(object interface{}) []byte {
return buff.Bytes()
}
+
+// TODO Use a bytes.Buffer instead of a raw byte slice.
+// Cleaner code, and use draining instead of seeking the next bytes to read
+func Decode(data []byte, pos uint64) (interface{}, uint64) {
+ var slice []interface{}
+ char := int(data[pos])
+ switch {
+ case char <= 0x7f:
+ return data[pos], pos + 1
+
+ case char <= 0xb7:
+ b := uint64(data[pos]) - 0x80
+
+ return data[pos+1 : pos+1+b], pos + 1 + b
+
+ case char <= 0xbf:
+ b := uint64(data[pos]) - 0xb7
+
+ b2 := ReadVarInt(data[pos+1 : pos+1+b])
+
+ return data[pos+1+b : pos+1+b+b2], pos + 1 + b + b2
+
+ case char <= 0xf7:
+ b := uint64(data[pos]) - 0xc0
+ prevPos := pos
+ pos++
+ for i := uint64(0); i < b; {
+ var obj interface{}
+
+ // Get the next item in the data list and append it
+ obj, prevPos = Decode(data, pos)
+ slice = append(slice, obj)
+
+ // Increment i by the amount bytes read in the previous
+ // read
+ i += (prevPos - pos)
+ pos = prevPos
+ }
+ return slice, pos
+
+ case char <= 0xff:
+ l := uint64(data[pos]) - 0xf7
+ b := ReadVarInt(data[pos+1 : pos+1+l])
+
+ pos = pos + l + 1
+
+ prevPos := b
+ for i := uint64(0); i < uint64(b); {
+ var obj interface{}
+
+ obj, prevPos = Decode(data, pos)
+ slice = append(slice, obj)
+
+ i += (prevPos - pos)
+ pos = prevPos
+ }
+ return slice, pos
+
+ default:
+ panic(fmt.Sprintf("byte not supported: %q", char))
+ }
+
+ return slice, 0
+}
diff --git a/ethutil/rlp_test.go b/ethutil/rlp_test.go
index 095c01ecc..90057ab42 100644
--- a/ethutil/rlp_test.go
+++ b/ethutil/rlp_test.go
@@ -44,6 +44,17 @@ func TestValueSlice(t *testing.T) {
}
}
+func TestLargeData(t *testing.T) {
+ data := make([]byte, 100000)
+ enc := Encode(data)
+ value := NewValue(enc)
+ value.Decode()
+
+ if value.Len() != len(data) {
+ t.Error("Expected data to be", len(data), "got", value.Len())
+ }
+}
+
func TestValue(t *testing.T) {
value := NewValueFromBytes([]byte("\xcd\x83dog\x83god\x83cat\x01"))
if value.Get(0).Str() != "dog" {
diff --git a/ethutil/value.go b/ethutil/value.go
index 735a71dbc..608d332ba 100644
--- a/ethutil/value.go
+++ b/ethutil/value.go
@@ -1,7 +1,6 @@
package ethutil
import (
- "bytes"
"fmt"
"math/big"
"reflect"
@@ -67,7 +66,7 @@ func (val *Value) Uint() uint64 {
} else if Val, ok := val.Val.(uint); ok {
return uint64(Val)
} else if Val, ok := val.Val.([]byte); ok {
- return ReadVarint(bytes.NewReader(Val))
+ return new(big.Int).SetBytes(Val).Uint64()
} else if Val, ok := val.Val.(*big.Int); ok {
return Val.Uint64()
}
@@ -75,6 +74,30 @@ func (val *Value) Uint() uint64 {
return 0
}
+func (val *Value) Int() int64 {
+ if Val, ok := val.Val.(int8); ok {
+ return int64(Val)
+ } else if Val, ok := val.Val.(int16); ok {
+ return int64(Val)
+ } else if Val, ok := val.Val.(int32); ok {
+ return int64(Val)
+ } else if Val, ok := val.Val.(int64); ok {
+ return Val
+ } else if Val, ok := val.Val.(int); ok {
+ return int64(Val)
+ } else if Val, ok := val.Val.(float32); ok {
+ return int64(Val)
+ } else if Val, ok := val.Val.(float64); ok {
+ return int64(Val)
+ } else if Val, ok := val.Val.([]byte); ok {
+ return new(big.Int).SetBytes(Val).Int64()
+ } else if Val, ok := val.Val.(*big.Int); ok {
+ return Val.Int64()
+ }
+
+ return 0
+}
+
func (val *Value) Byte() byte {
if Val, ok := val.Val.(byte); ok {
return Val
@@ -123,6 +146,14 @@ func (val *Value) Bytes() []byte {
return []byte{}
}
+func (val *Value) Err() error {
+ if err, ok := val.Val.(error); ok {
+ return err
+ }
+
+ return nil
+}
+
func (val *Value) Slice() []interface{} {
if d, ok := val.Val.([]interface{}); ok {
return d
@@ -158,6 +189,11 @@ func (val *Value) IsStr() bool {
return val.Type() == reflect.String
}
+func (self *Value) IsErr() bool {
+ _, ok := self.Val.(error)
+ return ok
+}
+
// Special list checking function. Something is considered
// a list if it's of type []interface{}. The list is usually
// used in conjunction with rlp decoded streams.
@@ -207,6 +243,13 @@ func (val *Value) Cmp(o *Value) bool {
return reflect.DeepEqual(val.Val, o.Val)
}
+func (self *Value) DeepCmp(o *Value) bool {
+ a := NewValue(self.BigInt())
+ b := NewValue(o.BigInt())
+
+ return a.Cmp(b)
+}
+
func (val *Value) Encode() []byte {
return Encode(val.Val)
}
@@ -215,12 +258,15 @@ func (val *Value) Encode() []byte {
func (self *Value) Decode() {
v, _ := Decode(self.Bytes(), 0)
self.Val = v
+ //self.Val = DecodeWithReader(bytes.NewBuffer(self.Bytes()))
}
func NewValueFromBytes(data []byte) *Value {
if len(data) != 0 {
- data, _ := Decode(data, 0)
- return NewValue(data)
+ value := NewValue(data)
+ value.Decode()
+
+ return value
}
return NewValue(nil)
@@ -262,6 +308,55 @@ func (val *Value) Append(v interface{}) *Value {
return val
}
+const (
+ valOpAdd = iota
+ valOpDiv
+ valOpMul
+ valOpPow
+ valOpSub
+)
+
+// Math stuff
+func (self *Value) doOp(op int, other interface{}) *Value {
+ left := self.BigInt()
+ right := NewValue(other).BigInt()
+
+ switch op {
+ case valOpAdd:
+ self.Val = left.Add(left, right)
+ case valOpDiv:
+ self.Val = left.Div(left, right)
+ case valOpMul:
+ self.Val = left.Mul(left, right)
+ case valOpPow:
+ self.Val = left.Exp(left, right, Big0)
+ case valOpSub:
+ self.Val = left.Sub(left, right)
+ }
+
+ return self
+}
+
+func (self *Value) Add(other interface{}) *Value {
+ return self.doOp(valOpAdd, other)
+}
+
+func (self *Value) Sub(other interface{}) *Value {
+ return self.doOp(valOpSub, other)
+}
+
+func (self *Value) Div(other interface{}) *Value {
+ return self.doOp(valOpDiv, other)
+}
+
+func (self *Value) Mul(other interface{}) *Value {
+ return self.doOp(valOpMul, other)
+}
+
+func (self *Value) Pow(other interface{}) *Value {
+ return self.doOp(valOpPow, other)
+}
+
type ValueIterator struct {
value *Value
currentValue *Value
diff --git a/ethutil/value_test.go b/ethutil/value_test.go
index a100f44bc..710cbd887 100644
--- a/ethutil/value_test.go
+++ b/ethutil/value_test.go
@@ -63,3 +63,18 @@ func TestIterator(t *testing.T) {
i++
}
}
+
+func TestMath(t *testing.T) {
+ a := NewValue(1)
+ a.Add(1).Add(1)
+
+ if !a.DeepCmp(NewValue(3)) {
+ t.Error("Expected 3, got", a)
+ }
+
+ a = NewValue(2)
+ a.Sub(1).Sub(1)
+ if !a.DeepCmp(NewValue(0)) {
+ t.Error("Expected 0, got", a)
+ }
+}