diff options
author | Jeffrey Wilcke <obscuren@users.noreply.github.com> | 2014-10-23 22:46:18 +0800 |
---|---|---|
committer | Jeffrey Wilcke <obscuren@users.noreply.github.com> | 2014-10-23 22:46:18 +0800 |
commit | 119c5b40a7ed1aea1c871c0cb56956b8ef9303d9 (patch) | |
tree | b021423da04c9ff319a77549b473b6bad4930dd4 /ethutil | |
parent | 50fd46924900869e7210217c6a07979b544991c8 (diff) | |
parent | 184055b3e2995894ccaba364484223e488730627 (diff) | |
download | dexon-119c5b40a7ed1aea1c871c0cb56956b8ef9303d9.tar.gz dexon-119c5b40a7ed1aea1c871c0cb56956b8ef9303d9.tar.zst dexon-119c5b40a7ed1aea1c871c0cb56956b8ef9303d9.zip |
Merge pull request #150 from fjl/develop
Merge eth-go repo into go-ethereum
Diffstat (limited to 'ethutil')
-rw-r--r-- | ethutil/.gitignore | 12 | ||||
-rw-r--r-- | ethutil/.travis.yml | 3 | ||||
-rw-r--r-- | ethutil/README.md | 139 | ||||
-rw-r--r-- | ethutil/big.go | 104 | ||||
-rw-r--r-- | ethutil/bytes.go | 234 | ||||
-rw-r--r-- | ethutil/bytes_test.go | 14 | ||||
-rw-r--r-- | ethutil/common.go | 85 | ||||
-rw-r--r-- | ethutil/common_test.go | 44 | ||||
-rw-r--r-- | ethutil/config.go | 72 | ||||
-rw-r--r-- | ethutil/db.go | 12 | ||||
-rw-r--r-- | ethutil/list.go | 80 | ||||
-rw-r--r-- | ethutil/package.go | 123 | ||||
-rw-r--r-- | ethutil/path.go | 60 | ||||
-rw-r--r-- | ethutil/rand.go | 24 | ||||
-rw-r--r-- | ethutil/rlp.go | 236 | ||||
-rw-r--r-- | ethutil/rlp_test.go | 146 | ||||
-rw-r--r-- | ethutil/script_unix.go | 47 | ||||
-rw-r--r-- | ethutil/script_windows.go | 31 | ||||
-rw-r--r-- | ethutil/set.go | 36 | ||||
-rw-r--r-- | ethutil/size.go | 15 | ||||
-rw-r--r-- | ethutil/size_test.go | 12 | ||||
-rw-r--r-- | ethutil/value.go | 397 | ||||
-rw-r--r-- | ethutil/value_test.go | 86 |
23 files changed, 2012 insertions, 0 deletions
diff --git a/ethutil/.gitignore b/ethutil/.gitignore new file mode 100644 index 000000000..f725d58d1 --- /dev/null +++ b/ethutil/.gitignore @@ -0,0 +1,12 @@ +# See http://help.github.com/ignore-files/ for more about ignoring files. +# +# If you find yourself ignoring temporary files generated by your text editor +# or operating system, you probably want to add a global ignore instead: +# git config --global core.excludesfile ~/.gitignore_global + +/tmp +*/**/*un~ +*un~ +.DS_Store +*/**/.DS_Store + diff --git a/ethutil/.travis.yml b/ethutil/.travis.yml new file mode 100644 index 000000000..69359072d --- /dev/null +++ b/ethutil/.travis.yml @@ -0,0 +1,3 @@ +language: go +go: + - 1.2 diff --git a/ethutil/README.md b/ethutil/README.md new file mode 100644 index 000000000..1ed56b71b --- /dev/null +++ b/ethutil/README.md @@ -0,0 +1,139 @@ +# ethutil + +[![Build +Status](https://travis-ci.org/ethereum/go-ethereum.png?branch=master)](https://travis-ci.org/ethereum/go-ethereum) + +The ethutil package contains the ethereum utility library. + +# Installation + +`go get github.com/ethereum/ethutil-go` + +# Usage + +## RLP (Recursive Linear Prefix) Encoding + +RLP Encoding is an encoding scheme utilized by the Ethereum project. It +encodes any native value or list to string. + +More in depth information about the Encoding scheme see the [Wiki](http://wiki.ethereum.org/index.php/RLP) +article. + +```go +rlp := ethutil.Encode("doge") +fmt.Printf("%q\n", rlp) // => "\0x83dog" + +rlp = ethutil.Encode([]interface{}{"dog", "cat"}) +fmt.Printf("%q\n", rlp) // => "\0xc8\0x83dog\0x83cat" +decoded := ethutil.Decode(rlp) +fmt.Println(decoded) // => ["dog" "cat"] +``` + +## Patricia Trie + +Patricie Tree is a merkle trie utilized by the Ethereum project. + +More in depth information about the (modified) Patricia Trie can be +found on the [Wiki](http://wiki.ethereum.org/index.php/Patricia_Tree). + +The patricia trie uses a db as backend and could be anything as long as +it satisfies the Database interface found in `ethutil/db.go`. + +```go +db := NewDatabase() + +// db, root +trie := ethutil.NewTrie(db, "") + +trie.Put("puppy", "dog") +trie.Put("horse", "stallion") +trie.Put("do", "verb") +trie.Put("doge", "coin") + +// Look up the key "do" in the trie +out := trie.Get("do") +fmt.Println(out) // => verb + +trie.Delete("puppy") +``` + +The patricia trie, in combination with RLP, provides a robust, +cryptographically authenticated data structure that can be used to store +all (key, value) bindings. + +```go +// ... Create db/trie + +// Note that RLP uses interface slices as list +value := ethutil.Encode([]interface{}{"one", 2, "three", []interface{}{42}}) +// Store the RLP encoded value of the list +trie.Put("mykey", value) +``` + +## Value + +Value is a Generic Value which is used in combination with RLP data or +`([])interface{}` structures. It may serve as a bridge between RLP data +and actual real values and takes care of all the type checking and +casting. Unlike Go's `reflect.Value` it does not panic if it's unable to +cast to the requested value. It simple returns the base value of that +type (e.g. `Slice()` returns []interface{}, `Uint()` return 0, etc). + +### Creating a new Value + +`NewEmptyValue()` returns a new \*Value with it's initial value set to a +`[]interface{}` + +`AppendList()` appends a list to the current value. + +`Append(v)` appends the value (v) to the current value/list. + +```go +val := ethutil.NewEmptyValue().Append(1).Append("2") +val.AppendList().Append(3) +``` + +### Retrieving values + +`Get(i)` returns the `i` item in the list. + +`Uint()` returns the value as an unsigned int64. + +`Slice()` returns the value as a interface slice. + +`Str()` returns the value as a string. + +`Bytes()` returns the value as a byte slice. + +`Len()` assumes current to be a slice and returns its length. + +`Byte()` returns the value as a single byte. + +```go +val := ethutil.NewValue([]interface{}{1,"2",[]interface{}{3}}) +val.Get(0).Uint() // => 1 +val.Get(1).Str() // => "2" +s := val.Get(2) // => Value([]interface{}{3}) +s.Get(0).Uint() // => 3 +``` + +## Decoding + +Decoding streams of RLP data is simplified + +```go +val := ethutil.NewValueFromBytes(rlpData) +val.Get(0).Uint() +``` + +## Encoding + +Encoding from Value to RLP is done with the `Encode` method. The +underlying value can be anything RLP can encode (int, str, lists, bytes) + +```go +val := ethutil.NewValue([]interface{}{1,"2",[]interface{}{3}}) +rlp := val.Encode() +// Store the rlp data +Store(rlp) +``` diff --git a/ethutil/big.go b/ethutil/big.go new file mode 100644 index 000000000..bdcf86421 --- /dev/null +++ b/ethutil/big.go @@ -0,0 +1,104 @@ +package ethutil + +import "math/big" + +var MaxInt256 *big.Int = BigD(Hex2Bytes("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")) + +// Big pow +// +// Returns the power of two big integers +func BigPow(a, b int) *big.Int { + c := new(big.Int) + c.Exp(big.NewInt(int64(a)), big.NewInt(int64(b)), big.NewInt(0)) + + return c +} + +// Big +// +// Shortcut for new(big.Int).SetString(..., 0) +func Big(num string) *big.Int { + n := new(big.Int) + n.SetString(num, 0) + + return n +} + +// BigD +// +// Shortcut for new(big.Int).SetBytes(...) +func BigD(data []byte) *big.Int { + n := new(big.Int) + n.SetBytes(data) + + return n +} + +// To256 +// +// "cast" the big int to a 256 big int (i.e., limit to) +var tt256 = new(big.Int).Lsh(big.NewInt(1), 256) +var tt256m1 = new(big.Int).Sub(new(big.Int).Lsh(big.NewInt(1), 256), big.NewInt(1)) +var tt255 = new(big.Int).Lsh(big.NewInt(1), 255) + +func U256(x *big.Int) *big.Int { + //if x.Cmp(Big0) < 0 { + // return new(big.Int).Add(tt256, x) + // } + + x.And(x, tt256m1) + + return x +} + +func S256(x *big.Int) *big.Int { + if x.Cmp(tt255) < 0 { + return x + } else { + // We don't want to modify x, ever + return new(big.Int).Sub(x, tt256) + } +} + +// Big to bytes +// +// Returns the bytes of a big integer with the size specified by **base** +// Attempts to pad the byte array with zeros. +func BigToBytes(num *big.Int, base int) []byte { + ret := make([]byte, base/8) + + if len(num.Bytes()) > base/8 { + return num.Bytes() + } + + return append(ret[:len(ret)-len(num.Bytes())], num.Bytes()...) +} + +// Big copy +// +// Creates a copy of the given big integer +func BigCopy(src *big.Int) *big.Int { + return new(big.Int).Set(src) +} + +// Big max +// +// Returns the maximum size big integer +func BigMax(x, y *big.Int) *big.Int { + if x.Cmp(y) <= 0 { + return y + } + + return x +} + +// Big min +// +// Returns the minimum size big integer +func BigMin(x, y *big.Int) *big.Int { + if x.Cmp(y) >= 0 { + return y + } + + return x +} diff --git a/ethutil/bytes.go b/ethutil/bytes.go new file mode 100644 index 000000000..bd294f28a --- /dev/null +++ b/ethutil/bytes.go @@ -0,0 +1,234 @@ +package ethutil + +import ( + "bytes" + "encoding/binary" + "encoding/hex" + "fmt" + "math/big" + "strings" +) + +type Bytes []byte + +func (self Bytes) String() string { + return string(self) +} + +func DeleteFromByteSlice(s [][]byte, hash []byte) [][]byte { + for i, h := range s { + if bytes.Compare(h, hash) == 0 { + return append(s[:i:i], s[i+1:]...) + } + } + + return s +} + +// Number to bytes +// +// Returns the number in bytes with the specified base +func NumberToBytes(num interface{}, bits int) []byte { + buf := new(bytes.Buffer) + err := binary.Write(buf, binary.BigEndian, num) + if err != nil { + fmt.Println("NumberToBytes failed:", err) + } + + return buf.Bytes()[buf.Len()-(bits/8):] +} + +// Bytes to number +// +// Attempts to cast a byte slice to a unsigned integer +func BytesToNumber(b []byte) uint64 { + var number uint64 + + // Make sure the buffer is 64bits + data := make([]byte, 8) + data = append(data[:len(b)], b...) + + buf := bytes.NewReader(data) + err := binary.Read(buf, binary.BigEndian, &number) + if err != nil { + fmt.Println("BytesToNumber failed:", err) + } + + return number +} + +// Read variable int +// +// Read a variable length number in big endian byte order +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 + d := LeftPadBytes(buff, 4) + binary.Read(bytes.NewReader(d), binary.BigEndian, &num) + ret = uint64(num) + case l > 1: + var num uint16 + d := LeftPadBytes(buff, 2) + binary.Read(bytes.NewReader(d), binary.BigEndian, &num) + ret = uint64(num) + default: + var num uint8 + binary.Read(bytes.NewReader(buff), binary.BigEndian, &num) + ret = uint64(num) + } + + return +} + +// Binary length +// +// Returns the true binary length of the given number +func BinaryLength(num int) int { + if num == 0 { + return 0 + } + + return 1 + BinaryLength(num>>8) +} + +// Copy bytes +// +// Returns an exact copy of the provided bytes +func CopyBytes(b []byte) (copiedBytes []byte) { + copiedBytes = make([]byte, len(b)) + copy(copiedBytes, b) + + return +} + +func IsHex(str string) bool { + l := len(str) + return l >= 4 && l%2 == 0 && str[0:2] == "0x" +} + +func Bytes2Hex(d []byte) string { + return hex.EncodeToString(d) +} + +func Hex2Bytes(str string) []byte { + h, _ := hex.DecodeString(str) + + return h +} + +func StringToByteFunc(str string, cb func(str string) []byte) (ret []byte) { + if len(str) > 1 && str[0:2] == "0x" && !strings.Contains(str, "\n") { + ret = Hex2Bytes(str[2:]) + } else { + ret = cb(str) + } + + return +} + +func FormatData(data string) []byte { + if len(data) == 0 { + return nil + } + // Simple stupid + d := new(big.Int) + if data[0:1] == "\"" && data[len(data)-1:] == "\"" { + return RightPadBytes([]byte(data[1:len(data)-1]), 32) + } else if len(data) > 1 && data[:2] == "0x" { + d.SetBytes(Hex2Bytes(data[2:])) + } else { + d.SetString(data, 0) + } + + 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 + } + + padded := make([]byte, l) + copy(padded[0:len(slice)], slice) + + return padded +} + +func LeftPadBytes(slice []byte, l int) []byte { + if l < len(slice) { + return slice + } + + padded := make([]byte, l) + copy(padded[l-len(slice):], slice) + + 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) + } else if len(slice) > 20 { + addr = slice[len(slice)-20:] + } else { + addr = slice + } + + addr = CopyBytes(addr) + + 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 new file mode 100644 index 000000000..8532d80f2 --- /dev/null +++ b/ethutil/common.go @@ -0,0 +1,85 @@ +package ethutil + +import ( + "fmt" + "math/big" + "runtime" +) + +func IsWindows() bool { + return runtime.GOOS == "windows" +} + +func WindonizePath(path string) string { + if string(path[0]) == "/" && IsWindows() { + path = path[1:] + } + return path +} + +// The different number of units +var ( + Douglas = BigPow(10, 42) + Einstein = BigPow(10, 21) + Ether = BigPow(10, 18) + Finney = BigPow(10, 15) + Szabo = BigPow(10, 12) + Shannon = BigPow(10, 9) + Babbage = BigPow(10, 6) + Ada = BigPow(10, 3) + Wei = big.NewInt(1) +) + +// +// 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: + fin = new(big.Int).Div(num, Douglas) + denom = "Douglas" + case num.Cmp(Einstein) >= 0: + fin = new(big.Int).Div(num, Einstein) + denom = "Einstein" + case num.Cmp(Ether) >= 0: + fin = new(big.Int).Div(num, Ether) + denom = "Ether" + case num.Cmp(Finney) >= 0: + fin = new(big.Int).Div(num, Finney) + denom = "Finney" + case num.Cmp(Szabo) >= 0: + fin = new(big.Int).Div(num, Szabo) + denom = "Szabo" + case num.Cmp(Shannon) >= 0: + fin = new(big.Int).Div(num, Shannon) + denom = "Shannon" + case num.Cmp(Babbage) >= 0: + fin = new(big.Int).Div(num, Babbage) + denom = "Babbage" + case num.Cmp(Ada) >= 0: + 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 %s", fin, denom) +} + +// Common big integers often used +var ( + Big1 = big.NewInt(1) + Big2 = big.NewInt(2) + Big0 = big.NewInt(0) + BigTrue = Big1 + BigFalse = Big0 + Big32 = big.NewInt(32) + Big256 = big.NewInt(0xff) +) diff --git a/ethutil/common_test.go b/ethutil/common_test.go new file mode 100644 index 000000000..2667eaf3a --- /dev/null +++ b/ethutil/common_test.go @@ -0,0 +1,44 @@ +package ethutil + +import ( + "math/big" + "testing" +) + +func TestCommon(t *testing.T) { + ether := CurrencyToString(BigPow(10, 19)) + finney := CurrencyToString(BigPow(10, 16)) + szabo := CurrencyToString(BigPow(10, 13)) + vito := CurrencyToString(BigPow(10, 10)) + turing := CurrencyToString(BigPow(10, 7)) + eins := CurrencyToString(BigPow(10, 4)) + wei := CurrencyToString(big.NewInt(10)) + + if ether != "10 Ether" { + t.Error("Got", ether) + } + + if finney != "10 Finney" { + t.Error("Got", finney) + } + + if szabo != "10 Szabo" { + t.Error("Got", szabo) + } + + if vito != "10 Shannon" { + t.Error("Got", vito) + } + + if turing != "10 Babbage" { + t.Error("Got", turing) + } + + if eins != "10 Ada" { + t.Error("Got", eins) + } + + if wei != "10 Wei" { + t.Error("Got", wei) + } +} diff --git a/ethutil/config.go b/ethutil/config.go new file mode 100644 index 000000000..ccc7714d0 --- /dev/null +++ b/ethutil/config.go @@ -0,0 +1,72 @@ +package ethutil + +import ( + "flag" + "fmt" + "os" + + "github.com/rakyll/globalconf" +) + +// Config struct +type ConfigManager struct { + Db Database + + ExecPath string + Debug bool + Diff bool + DiffType string + Paranoia bool + VmType int + + conf *globalconf.GlobalConf +} + +var Config *ConfigManager + +// Read config +// +// Initialize Config from Config File +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 + if !FileExist(ConfigFile) { + fmt.Printf("config file '%s' doesn't exist, creating it\n", ConfigFile) + os.Create(ConfigFile) + } + g, err := globalconf.NewWithOptions(&globalconf.Options{ + Filename: ConfigFile, + EnvPrefix: EnvPrefix, + }) + if err != nil { + fmt.Println(err) + } else { + g.ParseAll() + } + Config = &ConfigManager{ExecPath: Datadir, Debug: true, conf: g, Paranoia: true} + } + return Config +} + +// provides persistence for flags +func (c *ConfigManager) Save(key string, value interface{}) { + f := &flag.Flag{Name: key, Value: newConfValue(value)} + c.conf.Set("", f) +} + +func (c *ConfigManager) Delete(key string) { + c.conf.Delete("", key) +} + +// private type implementing flag.Value +type confValue struct { + value string +} + +// generic constructor to allow persising non-string values directly +func newConfValue(value interface{}) *confValue { + return &confValue{fmt.Sprintf("%v", value)} +} + +func (self confValue) String() string { return self.value } +func (self confValue) Set(s string) error { self.value = s; return nil } diff --git a/ethutil/db.go b/ethutil/db.go new file mode 100644 index 000000000..e02a80fca --- /dev/null +++ b/ethutil/db.go @@ -0,0 +1,12 @@ +package ethutil + +// Database interface +type Database interface { + Put(key []byte, value []byte) + Get(key []byte) ([]byte, error) + //GetKeys() []*Key + Delete(key []byte) error + LastKnownTD() []byte + Close() + Print() +} diff --git a/ethutil/list.go b/ethutil/list.go new file mode 100644 index 000000000..6919a02f5 --- /dev/null +++ b/ethutil/list.go @@ -0,0 +1,80 @@ +package ethutil + +import ( + "encoding/json" + "reflect" + "sync" +) + +// The list type is an anonymous slice handler which can be used +// for containing any slice type to use in an environment which +// does not support slice types (e.g., JavaScript, QML) +type List struct { + mut sync.Mutex + val interface{} + list reflect.Value + Length int +} + +// Initialise a new list. Panics if non-slice type is given. +func NewList(t interface{}) *List { + list := reflect.ValueOf(t) + if list.Kind() != reflect.Slice { + panic("list container initialized with a non-slice type") + } + + return &List{sync.Mutex{}, t, list, list.Len()} +} + +func EmptyList() *List { + return NewList([]interface{}{}) +} + +// Get N element from the embedded slice. Returns nil if OOB. +func (self *List) Get(i int) interface{} { + if self.list.Len() > i { + self.mut.Lock() + defer self.mut.Unlock() + + i := self.list.Index(i).Interface() + + return i + } + + return nil +} + +func (self *List) GetAsJson(i int) interface{} { + e := self.Get(i) + + r, _ := json.Marshal(e) + + return string(r) +} + +// Appends value at the end of the slice. Panics when incompatible value +// is given. +func (self *List) Append(v interface{}) { + self.mut.Lock() + defer self.mut.Unlock() + + self.list = reflect.Append(self.list, reflect.ValueOf(v)) + self.Length = self.list.Len() +} + +// Returns the underlying slice as interface. +func (self *List) Interface() interface{} { + return self.list.Interface() +} + +// For JavaScript <3 +func (self *List) ToJSON() string { + var list []interface{} + for i := 0; i < self.Length; i++ { + list = append(list, self.Get(i)) + } + + data, _ := json.Marshal(list) + + return string(data) +} diff --git a/ethutil/package.go b/ethutil/package.go new file mode 100644 index 000000000..e5df989d2 --- /dev/null +++ b/ethutil/package.go @@ -0,0 +1,123 @@ +package ethutil + +import ( + "archive/zip" + "encoding/json" + "fmt" + "io" + "io/ioutil" + "strings" +) + +// Manifest object +// +// The manifest object holds all the relevant information supplied with the +// the manifest specified in the package +type Manifest struct { + Entry string + Height, Width int +} + +// External package +// +// External package contains the main html file and manifest +type ExtPackage struct { + EntryHtml string + Manifest *Manifest +} + +// Read file +// +// Read a given compressed file and returns the read bytes. +// Returns an error otherwise +func ReadFile(f *zip.File) ([]byte, error) { + rc, err := f.Open() + if err != nil { + return nil, err + } + defer rc.Close() + + content, err := ioutil.ReadAll(rc) + if err != nil { + return nil, err + } + + return content, nil +} + +// Reads manifest +// +// Reads and returns a manifest object. Returns error otherwise +func ReadManifest(m []byte) (*Manifest, error) { + var manifest Manifest + + dec := json.NewDecoder(strings.NewReader(string(m))) + if err := dec.Decode(&manifest); err == io.EOF { + } else if err != nil { + return nil, err + } + + return &manifest, nil +} + +// Find file in archive +// +// Returns the index of the given file name if it exists. -1 if file not found +func FindFileInArchive(fn string, files []*zip.File) (index int) { + index = -1 + // Find the manifest first + for i, f := range files { + if f.Name == fn { + index = i + } + } + + return +} + +// Open package +// +// Opens a prepared ethereum package +// Reads the manifest file and determines file contents and returns and +// the external package. +func OpenPackage(fn string) (*ExtPackage, error) { + r, err := zip.OpenReader(fn) + if err != nil { + return nil, err + } + defer r.Close() + + manifestIndex := FindFileInArchive("manifest.json", r.File) + + if manifestIndex < 0 { + return nil, fmt.Errorf("No manifest file found in archive") + } + + f, err := ReadFile(r.File[manifestIndex]) + if err != nil { + return nil, err + } + + manifest, err := ReadManifest(f) + if err != nil { + return nil, err + } + + if manifest.Entry == "" { + return nil, fmt.Errorf("Entry file specified but appears to be empty: %s", manifest.Entry) + } + + entryIndex := FindFileInArchive(manifest.Entry, r.File) + if entryIndex < 0 { + return nil, fmt.Errorf("Entry file not found: '%s'", manifest.Entry) + } + + f, err = ReadFile(r.File[entryIndex]) + if err != nil { + return nil, err + } + + extPackage := &ExtPackage{string(f), manifest} + + return extPackage, nil +} diff --git a/ethutil/path.go b/ethutil/path.go new file mode 100644 index 000000000..cfbc38950 --- /dev/null +++ b/ethutil/path.go @@ -0,0 +1,60 @@ +package ethutil + +import ( + "io/ioutil" + "os" + "os/user" + "strings" +) + +func ExpandHomePath(p string) (path string) { + path = p + + // Check in case of paths like "/something/~/something/" + if path[:2] == "~/" { + usr, _ := user.Current() + dir := usr.HomeDir + + path = strings.Replace(p, "~", dir, 1) + } + + 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_TRUNC|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/rand.go b/ethutil/rand.go new file mode 100644 index 000000000..91dafec7e --- /dev/null +++ b/ethutil/rand.go @@ -0,0 +1,24 @@ +package ethutil + +import ( + "crypto/rand" + "encoding/binary" + "io" +) + +func randomUint64(r io.Reader) (uint64, error) { + b := make([]byte, 8) + n, err := r.Read(b) + if n != len(b) { + return 0, io.ErrShortBuffer + } + if err != nil { + return 0, err + } + return binary.BigEndian.Uint64(b), nil +} + +// RandomUint64 returns a cryptographically random uint64 value. +func RandomUint64() (uint64, error) { + return randomUint64(rand.Reader) +} diff --git a/ethutil/rlp.go b/ethutil/rlp.go new file mode 100644 index 000000000..55406133b --- /dev/null +++ b/ethutil/rlp.go @@ -0,0 +1,236 @@ +package ethutil + +import ( + "bytes" + "fmt" + "math/big" +) + +type RlpEncode interface { + RlpEncode() []byte +} + +type RlpEncodeDecode interface { + RlpEncode + RlpValue() []interface{} +} + +func Rlp(encoder RlpEncode) []byte { + return encoder.RlpEncode() +} + +type RlpEncoder struct { + rlpData []byte +} + +func NewRlpEncoder() *RlpEncoder { + encoder := &RlpEncoder{} + + return encoder +} +func (coder *RlpEncoder) EncodeData(rlpData interface{}) []byte { + return Encode(rlpData) +} + +const ( + RlpEmptyList = 0x80 + RlpEmptyStr = 0x40 +) + +const rlpEof = -1 + +func Char(c []byte) int { + if len(c) > 0 { + return int(c[0]) + } + + return rlpEof +} + +func DecodeWithReader(reader *bytes.Buffer) interface{} { + var slice []interface{} + + // Read the next byte + char := Char(reader.Next(1)) + switch { + case char <= 0x7f: + return char + + case char <= 0xb7: + return reader.Next(int(char - 0x80)) + + case char <= 0xbf: + length := ReadVarInt(reader.Next(int(char - 0xb7))) + + return reader.Next(int(length)) + + case char <= 0xf7: + length := int(char - 0xc0) + for i := 0; i < length; i++ { + obj := DecodeWithReader(reader) + slice = append(slice, obj) + } + + return slice + case char <= 0xff: + length := ReadVarInt(reader.Next(int(char - 0xf7))) + for i := uint64(0); i < length; i++ { + obj := DecodeWithReader(reader) + slice = append(slice, obj) + } + + return slice + default: + panic(fmt.Sprintf("byte not supported: %q", char)) + } + + return slice +} + +var ( + directRlp = big.NewInt(0x7f) + numberRlp = big.NewInt(0xb7) + zeroRlp = big.NewInt(0x0) +) + +func Encode(object interface{}) []byte { + var buff bytes.Buffer + + if object != nil { + switch t := object.(type) { + case *Value: + buff.Write(Encode(t.Raw())) + // Code dup :-/ + case int: + buff.Write(Encode(big.NewInt(int64(t)))) + case uint: + buff.Write(Encode(big.NewInt(int64(t)))) + case int8: + buff.Write(Encode(big.NewInt(int64(t)))) + case int16: + buff.Write(Encode(big.NewInt(int64(t)))) + case int32: + buff.Write(Encode(big.NewInt(int64(t)))) + case int64: + buff.Write(Encode(big.NewInt(t))) + case uint16: + buff.Write(Encode(big.NewInt(int64(t)))) + case uint32: + buff.Write(Encode(big.NewInt(int64(t)))) + case uint64: + buff.Write(Encode(big.NewInt(int64(t)))) + case byte: + buff.Write(Encode(big.NewInt(int64(t)))) + case *big.Int: + // Not sure how this is possible while we check for + if t == nil { + buff.WriteByte(0xc0) + } else { + buff.Write(Encode(t.Bytes())) + } + case Bytes: + buff.Write(Encode([]byte(t))) + case []byte: + if len(t) == 1 && t[0] <= 0x7f { + buff.Write(t) + } else if len(t) < 56 { + buff.WriteByte(byte(len(t) + 0x80)) + buff.Write(t) + } else { + b := big.NewInt(int64(len(t))) + buff.WriteByte(byte(len(b.Bytes()) + 0xb7)) + buff.Write(b.Bytes()) + buff.Write(t) + } + case string: + buff.Write(Encode([]byte(t))) + case []interface{}: + // Inline function for writing the slice header + WriteSliceHeader := func(length int) { + if length < 56 { + buff.WriteByte(byte(length + 0xc0)) + } else { + b := big.NewInt(int64(length)) + buff.WriteByte(byte(len(b.Bytes()) + 0xf7)) + buff.Write(b.Bytes()) + } + } + + var b bytes.Buffer + for _, val := range t { + b.Write(Encode(val)) + } + WriteSliceHeader(len(b.Bytes())) + buff.Write(b.Bytes()) + } + } else { + // Empty list for nil + buff.WriteByte(0xc0) + } + + 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 new file mode 100644 index 000000000..90057ab42 --- /dev/null +++ b/ethutil/rlp_test.go @@ -0,0 +1,146 @@ +package ethutil + +import ( + "bytes" + "math/big" + "reflect" + "testing" +) + +func TestRlpValueEncoding(t *testing.T) { + val := EmptyValue() + val.AppendList().Append(1).Append(2).Append(3) + val.Append("4").AppendList().Append(5) + + res := val.Encode() + exp := Encode([]interface{}{[]interface{}{1, 2, 3}, "4", []interface{}{5}}) + if bytes.Compare(res, exp) != 0 { + t.Errorf("expected %q, got %q", res, exp) + } +} + +func TestValueSlice(t *testing.T) { + val := []interface{}{ + "value1", + "valeu2", + "value3", + } + + value := NewValue(val) + splitVal := value.SliceFrom(1) + + if splitVal.Len() != 2 { + t.Error("SliceFrom: Expected len", 2, "got", splitVal.Len()) + } + + splitVal = value.SliceTo(2) + if splitVal.Len() != 2 { + t.Error("SliceTo: Expected len", 2, "got", splitVal.Len()) + } + + splitVal = value.SliceFromTo(1, 3) + if splitVal.Len() != 2 { + t.Error("SliceFromTo: Expected len", 2, "got", splitVal.Len()) + } +} + +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" { + t.Errorf("expected '%v', got '%v'", value.Get(0).Str(), "dog") + } + + if value.Get(3).Uint() != 1 { + t.Errorf("expected '%v', got '%v'", value.Get(3).Uint(), 1) + } +} + +func TestEncode(t *testing.T) { + strRes := "\x83dog" + bytes := Encode("dog") + + str := string(bytes) + if str != strRes { + t.Errorf("Expected %q, got %q", strRes, str) + } + + sliceRes := "\xcc\x83dog\x83god\x83cat" + strs := []interface{}{"dog", "god", "cat"} + bytes = Encode(strs) + slice := string(bytes) + if slice != sliceRes { + t.Error("Expected %q, got %q", sliceRes, slice) + } + + intRes := "\x82\x04\x00" + bytes = Encode(1024) + if string(bytes) != intRes { + t.Errorf("Expected %q, got %q", intRes, bytes) + } +} + +func TestDecode(t *testing.T) { + single := []byte("\x01") + b, _ := Decode(single, 0) + + if b.(uint8) != 1 { + t.Errorf("Expected 1, got %q", b) + } + + str := []byte("\x83dog") + b, _ = Decode(str, 0) + if bytes.Compare(b.([]byte), []byte("dog")) != 0 { + t.Errorf("Expected dog, got %q", b) + } + + slice := []byte("\xcc\x83dog\x83god\x83cat") + res := []interface{}{"dog", "god", "cat"} + b, _ = Decode(slice, 0) + if reflect.DeepEqual(b, res) { + t.Errorf("Expected %q, got %q", res, b) + } +} + +func TestEncodeDecodeBigInt(t *testing.T) { + bigInt := big.NewInt(1391787038) + encoded := Encode(bigInt) + + value := NewValueFromBytes(encoded) + if value.BigInt().Cmp(bigInt) != 0 { + t.Errorf("Expected %v, got %v", bigInt, value.BigInt()) + } +} + +func TestEncodeDecodeBytes(t *testing.T) { + b := NewValue([]interface{}{[]byte{1, 2, 3, 4, 5}, byte(6)}) + val := NewValueFromBytes(b.Encode()) + if !b.Cmp(val) { + t.Errorf("Expected %v, got %v", val, b) + } +} + +func TestEncodeZero(t *testing.T) { + b := NewValue(0).Encode() + exp := []byte{0xc0} + if bytes.Compare(b, exp) == 0 { + t.Error("Expected", exp, "got", b) + } +} + +func BenchmarkEncodeDecode(b *testing.B) { + for i := 0; i < b.N; i++ { + bytes := Encode([]interface{}{"dog", "god", "cat"}) + Decode(bytes, 0) + } +} diff --git a/ethutil/script_unix.go b/ethutil/script_unix.go new file mode 100644 index 000000000..bd087e7e0 --- /dev/null +++ b/ethutil/script_unix.go @@ -0,0 +1,47 @@ +package ethutil + +import ( + "fmt" + "strings" + + "github.com/obscuren/mutan" + "github.com/obscuren/mutan/backends" + "github.com/obscuren/serpent-go" +) + +// General compile function +func Compile(script string, silent bool) (ret []byte, err error) { + if len(script) > 2 { + line := strings.Split(script, "\n")[0] + + if len(line) > 1 && line[0:2] == "#!" { + switch line { + case "#!serpent": + byteCode, err := serpent.Compile(script) + if err != nil { + return nil, err + } + + return byteCode, nil + } + } else { + + compiler := mutan.NewCompiler(backend.NewEthereumBackend()) + compiler.Silent = silent + byteCode, errors := compiler.Compile(strings.NewReader(script)) + if len(errors) > 0 { + var errs string + for _, er := range errors { + if er != nil { + errs += er.Error() + } + } + return nil, fmt.Errorf("%v", errs) + } + + return byteCode, nil + } + } + + return nil, nil +} diff --git a/ethutil/script_windows.go b/ethutil/script_windows.go new file mode 100644 index 000000000..4f94c6448 --- /dev/null +++ b/ethutil/script_windows.go @@ -0,0 +1,31 @@ +package ethutil + +import ( + "fmt" + "strings" + + "github.com/obscuren/mutan" + "github.com/obscuren/mutan/backends" +) + +// General compile function +func Compile(script string, silent bool) (ret []byte, err error) { + if len(script) > 2 { + compiler := mutan.NewCompiler(backend.NewEthereumBackend()) + compiler.Silent = silent + byteCode, errors := compiler.Compile(strings.NewReader(script)) + if len(errors) > 0 { + var errs string + for _, er := range errors { + if er != nil { + errs += er.Error() + } + } + return nil, fmt.Errorf("%v", errs) + } + + return byteCode, nil + } + + return nil, nil +} diff --git a/ethutil/set.go b/ethutil/set.go new file mode 100644 index 000000000..7955edac0 --- /dev/null +++ b/ethutil/set.go @@ -0,0 +1,36 @@ +package ethutil + +type Settable interface { + AsSet() UniqueSet +} + +type Stringable interface { + String() string +} + +type UniqueSet map[string]struct{} + +func NewSet(v ...Stringable) UniqueSet { + set := make(UniqueSet) + for _, val := range v { + set.Insert(val) + } + + return set +} + +func (self UniqueSet) Insert(k Stringable) UniqueSet { + self[k.String()] = struct{}{} + + return self +} + +func (self UniqueSet) Include(k Stringable) bool { + _, ok := self[k.String()] + + return ok +} + +func Set(s Settable) UniqueSet { + return s.AsSet() +} diff --git a/ethutil/size.go b/ethutil/size.go new file mode 100644 index 000000000..b4426465e --- /dev/null +++ b/ethutil/size.go @@ -0,0 +1,15 @@ +package ethutil + +import "fmt" + +type StorageSize float64 + +func (self StorageSize) String() string { + if self > 1000000 { + return fmt.Sprintf("%.2f mB", self/1000000) + } else if self > 1000 { + return fmt.Sprintf("%.2f kB", self/1000) + } else { + return fmt.Sprintf("%.2f B", self) + } +} diff --git a/ethutil/size_test.go b/ethutil/size_test.go new file mode 100644 index 000000000..82aa1c653 --- /dev/null +++ b/ethutil/size_test.go @@ -0,0 +1,12 @@ +package ethutil + +import ( + "fmt" + "testing" +) + +func TestSize(t *testing.T) { + fmt.Println(StorageSize(2381273)) + fmt.Println(StorageSize(2192)) + fmt.Println(StorageSize(12)) +} diff --git a/ethutil/value.go b/ethutil/value.go new file mode 100644 index 000000000..dd777fa43 --- /dev/null +++ b/ethutil/value.go @@ -0,0 +1,397 @@ +package ethutil + +import ( + "bytes" + "fmt" + "math/big" + "reflect" + "strconv" +) + +// Data values are returned by the rlp decoder. The data values represents +// one item within the rlp data structure. It's responsible for all the casting +// It always returns something valid +type Value struct { + Val interface{} + kind reflect.Value +} + +func (val *Value) String() string { + return fmt.Sprintf("%x", val.Val) +} + +func NewValue(val interface{}) *Value { + t := val + if v, ok := val.(*Value); ok { + t = v.Val + } + + return &Value{Val: t} +} + +func (val *Value) Type() reflect.Kind { + return reflect.TypeOf(val.Val).Kind() +} + +func (val *Value) IsNil() bool { + return val.Val == nil +} + +func (val *Value) Len() int { + //return val.kind.Len() + if data, ok := val.Val.([]interface{}); ok { + return len(data) + } + + return len(val.Bytes()) +} + +func (val *Value) Raw() interface{} { + return val.Val +} + +func (val *Value) Interface() interface{} { + return val.Val +} + +func (val *Value) Uint() uint64 { + if Val, ok := val.Val.(uint8); ok { + return uint64(Val) + } else if Val, ok := val.Val.(uint16); ok { + return uint64(Val) + } else if Val, ok := val.Val.(uint32); ok { + return uint64(Val) + } else if Val, ok := val.Val.(uint64); ok { + return Val + } else if Val, ok := val.Val.(float32); ok { + return uint64(Val) + } else if Val, ok := val.Val.(float64); ok { + return uint64(Val) + } else if Val, ok := val.Val.(int); ok { + return uint64(Val) + } else if Val, ok := val.Val.(uint); ok { + return uint64(Val) + } else if Val, ok := val.Val.([]byte); ok { + return new(big.Int).SetBytes(Val).Uint64() + } else if Val, ok := val.Val.(*big.Int); ok { + return Val.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() + } else if Val, ok := val.Val.(string); ok { + n, _ := strconv.Atoi(Val) + return int64(n) + } + + return 0 +} + +func (val *Value) Byte() byte { + if Val, ok := val.Val.(byte); ok { + return Val + } + + return 0x0 +} + +func (val *Value) BigInt() *big.Int { + if a, ok := val.Val.([]byte); ok { + b := new(big.Int).SetBytes(a) + + return b + } else if a, ok := val.Val.(*big.Int); ok { + return a + } else if a, ok := val.Val.(string); ok { + return Big(a) + } else { + return big.NewInt(int64(val.Uint())) + } + + return big.NewInt(0) +} + +func (val *Value) Str() string { + if a, ok := val.Val.([]byte); ok { + return string(a) + } else if a, ok := val.Val.(string); ok { + return a + } else if a, ok := val.Val.(byte); ok { + return string(a) + } + + return "" +} + +func (val *Value) Bytes() []byte { + if a, ok := val.Val.([]byte); ok { + return a + } else if s, ok := val.Val.(byte); ok { + return []byte{s} + } else if s, ok := val.Val.(string); ok { + return []byte(s) + } else if s, ok := val.Val.(*big.Int); ok { + return s.Bytes() + } else { + return big.NewInt(val.Int()).Bytes() + } + + 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 + } + + return []interface{}{} +} + +func (val *Value) SliceFrom(from int) *Value { + slice := val.Slice() + + return NewValue(slice[from:]) +} + +func (val *Value) SliceTo(to int) *Value { + slice := val.Slice() + + return NewValue(slice[:to]) +} + +func (val *Value) SliceFromTo(from, to int) *Value { + slice := val.Slice() + + return NewValue(slice[from:to]) +} + +// TODO More type checking methods +func (val *Value) IsSlice() bool { + return val.Type() == reflect.Slice +} + +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. +func (val *Value) IsList() bool { + _, ok := val.Val.([]interface{}) + + return ok +} + +func (val *Value) IsEmpty() bool { + return val.Val == nil || ((val.IsSlice() || val.IsStr()) && val.Len() == 0) +} + +// Threat the value as a slice +func (val *Value) Get(idx int) *Value { + if d, ok := val.Val.([]interface{}); ok { + // Guard for oob + if len(d) <= idx { + return NewValue(nil) + } + + if idx < 0 { + return NewValue(nil) + } + + return NewValue(d[idx]) + } + + // If this wasn't a slice you probably shouldn't be using this function + return NewValue(nil) +} + +func (self *Value) Copy() *Value { + switch val := self.Val.(type) { + case *big.Int: + return NewValue(new(big.Int).Set(val)) + case []byte: + return NewValue(CopyBytes(val)) + default: + return NewValue(self.Val) + } + + return nil +} + +func (val *Value) Cmp(o *Value) bool { + return reflect.DeepEqual(val.Val, o.Val) +} + +func (self *Value) DeepCmp(o *Value) bool { + return bytes.Compare(self.Bytes(), o.Bytes()) == 0 +} + +func (val *Value) Encode() []byte { + return Encode(val.Val) +} + +// Assume that the data we have is encoded +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 { + value := NewValue(data) + value.Decode() + + return value + } + + return NewValue(nil) +} + +// Value setters +func NewSliceValue(s interface{}) *Value { + list := EmptyValue() + + if s != nil { + if slice, ok := s.([]interface{}); ok { + for _, val := range slice { + list.Append(val) + } + } else if slice, ok := s.([]string); ok { + for _, val := range slice { + list.Append(val) + } + } + } + + return list +} + +func EmptyValue() *Value { + return NewValue([]interface{}{}) +} + +func (val *Value) AppendList() *Value { + list := EmptyValue() + val.Val = append(val.Slice(), list) + + return list +} + +func (val *Value) Append(v interface{}) *Value { + val.Val = append(val.Slice(), v) + + 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 + idx int +} + +func (val *Value) NewIterator() *ValueIterator { + return &ValueIterator{value: val} +} + +func (it *ValueIterator) Next() bool { + if it.idx >= it.value.Len() { + return false + } + + it.currentValue = it.value.Get(it.idx) + it.idx++ + + return true +} + +func (it *ValueIterator) Value() *Value { + return it.currentValue +} + +func (it *ValueIterator) Idx() int { + return it.idx +} diff --git a/ethutil/value_test.go b/ethutil/value_test.go new file mode 100644 index 000000000..5452a0790 --- /dev/null +++ b/ethutil/value_test.go @@ -0,0 +1,86 @@ +package ethutil + +import ( + "bytes" + "fmt" + "math/big" + "testing" +) + +func TestValueCmp(t *testing.T) { + val1 := NewValue("hello") + val2 := NewValue("world") + if val1.Cmp(val2) { + t.Error("Expected values not to be equal") + } + + val3 := NewValue("hello") + val4 := NewValue("hello") + if !val3.Cmp(val4) { + t.Error("Expected values to be equal") + } +} + +func TestValueTypes(t *testing.T) { + str := NewValue("str") + num := NewValue(1) + inter := NewValue([]interface{}{1}) + byt := NewValue([]byte{1, 2, 3, 4}) + bigInt := NewValue(big.NewInt(10)) + + if str.Str() != "str" { + t.Errorf("expected Str to return 'str', got %s", str.Str()) + } + + if num.Uint() != 1 { + t.Errorf("expected Uint to return '1', got %d", num.Uint()) + } + + interExp := []interface{}{1} + if !NewValue(inter.Interface()).Cmp(NewValue(interExp)) { + t.Errorf("expected Interface to return '%v', got %v", interExp, num.Interface()) + } + + bytExp := []byte{1, 2, 3, 4} + if bytes.Compare(byt.Bytes(), bytExp) != 0 { + t.Errorf("expected Bytes to return '%v', got %v", bytExp, byt.Bytes()) + } + + bigExp := big.NewInt(10) + if bigInt.BigInt().Cmp(bigExp) != 0 { + t.Errorf("expected BigInt to return '%v', got %v", bigExp, bigInt.BigInt()) + } +} + +func TestIterator(t *testing.T) { + value := NewValue([]interface{}{1, 2, 3}) + it := value.NewIterator() + values := []uint64{1, 2, 3} + i := 0 + for it.Next() { + if values[i] != it.Value().Uint() { + t.Errorf("Expected %d, got %d", values[i], it.Value().Uint()) + } + 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) + } +} + +func TestString(t *testing.T) { + a := NewValue("10") + fmt.Println("VALUE WITH STRING:", a.Int()) +} |