Merge branch 'release/0.9.0'

This commit is contained in:
obscuren 2015-02-20 18:13:46 +01:00
commit bd7ebbcd5b
1246 changed files with 2656195 additions and 21752 deletions

10
.gitignore vendored
View File

@ -10,3 +10,13 @@
.DS_Store
*/**/.DS_Store
.ethtest
#*
.#*
*#
*~
.project
.settings
cmd/ethereum/ethereum
cmd/mist/mist

12
.mailmap Normal file
View File

@ -0,0 +1,12 @@
Jeffrey Wilcke <jeffrey@ethereum.org>
Jeffrey Wilcke <jeffrey@ethereum.org> <geffobscura@gmail.com>
Jeffrey Wilcke <jeffrey@ethereum.org> <obscuren@obscura.com>
Jeffrey Wilcke <jeffrey@ethereum.org> <obscuren@users.noreply.github.com>
Viktor Trón <viktor.tron@gmail.com>
Joseph Goulden <joegoulden@gmail.com>
Nick Savers <nicksavers@gmail.com>
Maran Hidskes <maran.hidskes@gmail.com>

View File

@ -1,25 +1,24 @@
language: go
go:
- 1.3
- 1.4.1
before_install:
- sudo add-apt-repository ppa:ubuntu-sdk-team/ppa -y
- sudo add-apt-repository ppa:beineri/opt-qt54 -y
- sudo apt-get update -qq
- sudo apt-get install -yqq libgmp3-dev qtbase5-private-dev qtdeclarative5-private-dev libqt5opengl5-dev libreadline6-dev
- sudo apt-get install -yqq libgmp3-dev libreadline6-dev qt54quickcontrols qt54webengine
install:
- go get code.google.com/p/go.tools/cmd/goimports
- go get github.com/golang/lint/golint
# - go get code.google.com/p/go.tools/cmd/vet
- go get code.google.com/p/go.tools/cmd/cover
# - go get golang.org/x/tools/cmd/vet
- if ! go get code.google.com/p/go.tools/cmd/cover; then go get golang.org/x/tools/cmd/cover; fi
- go get github.com/mattn/goveralls
- ./install_deps.sh
before_script:
- gofmt -l -w .
- goimports -l -w .
- golint .
# - go vet ./...
# - go test -race ./...
script:
- ./gocoverage.sh && goveralls -coverprofile=profile.cov -service=travis-ci -repotoken $COVERALLS_TOKEN
- ./gocoverage.sh
after_success:
- if [ "$COVERALLS_TOKEN" ]; then goveralls -coverprofile=profile.cov -service=travis-ci -repotoken $COVERALLS_TOKEN; fi
env:
- secure: "U2U1AmkU4NJBgKR/uUAebQY87cNL0+1JHjnLOmmXwxYYyj5ralWb1aSuSH3qSXiT93qLBmtaUkuv9fberHVqrbAeVlztVdUsKAq7JMQH+M99iFkC9UiRMqHmtjWJ0ok4COD1sRYixxi21wb/JrMe3M1iL4QJVS61iltjHhVdM64="
global:
- PKG_CONFIG_PATH=/opt/qt54/lib/pkgconfig
- LD_LIBRARY_PATH=/opt/qt54/lib
- secure: "U2U1AmkU4NJBgKR/uUAebQY87cNL0+1JHjnLOmmXwxYYyj5ralWb1aSuSH3qSXiT93qLBmtaUkuv9fberHVqrbAeVlztVdUsKAq7JMQH+M99iFkC9UiRMqHmtjWJ0ok4COD1sRYixxi21wb/JrMe3M1iL4QJVS61iltjHhVdM64="

View File

@ -1,41 +1,40 @@
FROM ubuntu:14.04
FROM ubuntu:14.04.1
## Environment setup
ENV HOME /root
ENV GOPATH /root/go
ENV PATH /go/bin:/root/go/bin:/usr/local/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games
ENV PATH /root/go/bin:/usr/local/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games
RUN mkdir -p /root/go
ENV DEBIAN_FRONTEND noninteractive
## Install base dependencies
RUN apt-get update && apt-get upgrade -y
RUN apt-get install -y git mercurial build-essential software-properties-common pkg-config libgmp3-dev libreadline6-dev libpcre3-dev libpcre++-dev
RUN apt-get install -y git mercurial build-essential software-properties-common wget pkg-config libgmp3-dev libreadline6-dev libpcre3-dev libpcre++-dev
## Build and install Go
RUN hg clone -u release https://code.google.com/p/go
RUN cd go && hg update go1.4
RUN cd go/src && ./all.bash && go version
## Install Qt5.4 (not required for CLI)
# RUN add-apt-repository ppa:beineri/opt-qt54-trusty -y
# RUN apt-get update -y
# RUN apt-get install -y qt54quickcontrols qt54webengine mesa-common-dev libglu1-mesa-dev
# ENV PKG_CONFIG_PATH /opt/qt54/lib/pkgconfig
## Install GUI dependencies
RUN add-apt-repository ppa:ubuntu-sdk-team/ppa -y
RUN apt-get update -y
RUN apt-get install -y qtbase5-private-dev qtdeclarative5-private-dev libqt5opengl5-dev
# Install Golang
RUN wget https://storage.googleapis.com/golang/go1.4.1.linux-amd64.tar.gz
RUN tar -C /usr/local -xzf go*.tar.gz && go version
## Fetch and install serpent-go
RUN go get -v -d github.com/ethereum/serpent-go
WORKDIR $GOPATH/src/github.com/ethereum/serpent-go
RUN git checkout master
RUN git submodule update --init
RUN go install -v
# this is a workaround, to make sure that docker's cache is invalidated whenever the git repo changes
ADD https://api.github.com/repos/ethereum/go-ethereum/git/refs/heads/develop file_does_not_exist
# Fetch and install go-ethereum
## Fetch and install go-ethereum
RUN go get -v github.com/tools/godep
RUN go get -v -d github.com/ethereum/go-ethereum/...
WORKDIR $GOPATH/src/github.com/ethereum/go-ethereum
RUN git checkout poc8
RUN ETH_DEPS=$(go list -f '{{.Imports}} {{.TestImports}} {{.XTestImports}}' github.com/ethereum/go-ethereum/... | sed -e 's/\[//g' | sed -e 's/\]//g' | sed -e 's/C //g'); if [ "$ETH_DEPS" ]; then go get $ETH_DEPS; fi
RUN git checkout develop
RUN godep restore
RUN go install -v ./cmd/ethereum
# Run JSON RPC
ENTRYPOINT ["ethereum", "-rpc=true", "-rpcport=8080"]
EXPOSE 8080
## Run & expose JSON RPC
ENTRYPOINT ["ethereum", "-rpc=true", "-rpcport=8545"]
EXPOSE 8545

118
Godeps/Godeps.json generated Normal file
View File

@ -0,0 +1,118 @@
{
"ImportPath": "github.com/ethereum/go-ethereum",
"GoVersion": "go1.4.1",
"Packages": [
"./..."
],
"Deps": [
{
"ImportPath": "bitbucket.org/kardianos/osext",
"Comment": "null-13",
"Rev": "5d3ddcf53a508cc2f7404eaebf546ef2cb5cdb6e"
},
{
"ImportPath": "code.google.com/p/go-uuid/uuid",
"Comment": "null-12",
"Rev": "7dda39b2e7d5e265014674c5af696ba4186679e9"
},
{
"ImportPath": "code.google.com/p/snappy-go/snappy",
"Comment": "null-15",
"Rev": "12e4b4183793ac4b061921e7980845e750679fd0"
},
{
"ImportPath": "github.com/ethereum/serpent-go",
"Rev": "5767a0dbd759d313df3f404dadb7f98d7ab51443"
},
{
"ImportPath": "github.com/howeyc/fsnotify",
"Comment": "v0.9.0-11-g6b1ef89",
"Rev": "6b1ef893dc11e0447abda6da20a5203481878dda"
},
{
"ImportPath": "github.com/huin/goupnp",
"Rev": "4191d8a85005844ea202fde52799681971b12dfe"
},
{
"ImportPath": "github.com/jackpal/go-nat-pmp",
"Rev": "a45aa3d54aef73b504e15eb71bea0e5565b5e6e1"
},
{
"ImportPath": "github.com/obscuren/otto",
"Rev": "cf13cc4228c5e5ce0fe27a7aea90bc10091c4f19"
},
{
"ImportPath": "github.com/obscuren/qml",
"Rev": "c288002b52e905973b131089a8a7c761d4a2c36a"
},
{
"ImportPath": "github.com/rakyll/globalconf",
"Rev": "415abc325023f1a00cd2d9fa512e0e71745791a2"
},
{
"ImportPath": "github.com/rakyll/goini",
"Rev": "907cca0f578a5316fb864ec6992dc3d9730ec58c"
},
{
"ImportPath": "github.com/robertkrimen/otto/ast",
"Rev": "dea31a3d392779af358ec41f77a07fcc7e9d04ba"
},
{
"ImportPath": "github.com/robertkrimen/otto/dbg",
"Rev": "dea31a3d392779af358ec41f77a07fcc7e9d04ba"
},
{
"ImportPath": "github.com/robertkrimen/otto/file",
"Rev": "dea31a3d392779af358ec41f77a07fcc7e9d04ba"
},
{
"ImportPath": "github.com/robertkrimen/otto/parser",
"Rev": "dea31a3d392779af358ec41f77a07fcc7e9d04ba"
},
{
"ImportPath": "github.com/robertkrimen/otto/registry",
"Rev": "dea31a3d392779af358ec41f77a07fcc7e9d04ba"
},
{
"ImportPath": "github.com/robertkrimen/otto/token",
"Rev": "dea31a3d392779af358ec41f77a07fcc7e9d04ba"
},
{
"ImportPath": "github.com/syndtr/goleveldb/leveldb",
"Rev": "832fa7ed4d28545eab80f19e1831fc004305cade"
},
{
"ImportPath": "golang.org/x/crypto/pbkdf2",
"Rev": "4ed45ec682102c643324fae5dff8dab085b6c300"
},
{
"ImportPath": "golang.org/x/crypto/ripemd160",
"Rev": "4ed45ec682102c643324fae5dff8dab085b6c300"
},
{
"ImportPath": "golang.org/x/crypto/scrypt",
"Rev": "4ed45ec682102c643324fae5dff8dab085b6c300"
},
{
"ImportPath": "golang.org/x/net/websocket",
"Rev": "59b0df9b1f7abda5aab0495ee54f408daf182ce7"
},
{
"ImportPath": "gopkg.in/check.v1",
"Rev": "64131543e7896d5bcc6bd5a76287eb75ea96c673"
},
{
"ImportPath": "gopkg.in/fatih/set.v0",
"Comment": "v0.1.0-3-g27c4092",
"Rev": "27c40922c40b43fe04554d8223a402af3ea333f3"
},
{
"ImportPath": "gopkg.in/qml.v1/cdata",
"Rev": "1116cb9cd8dee23f8d444ded354eb53122739f99"
},
{
"ImportPath": "gopkg.in/qml.v1/gl/glbase",
"Rev": "1116cb9cd8dee23f8d444ded354eb53122739f99"
}
]
}

5
Godeps/Readme generated Normal file
View File

@ -0,0 +1,5 @@
This directory tree is generated automatically by godep.
Please do not edit.
See https://github.com/tools/godep for more information.

2
Godeps/_workspace/.gitignore generated vendored Normal file
View File

@ -0,0 +1,2 @@
/pkg
/bin

View File

@ -0,0 +1,20 @@
Copyright (c) 2012 Daniel Theophanes
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source
distribution.

View File

@ -0,0 +1,32 @@
// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Extensions to the standard "os" package.
package osext
import "path/filepath"
// Executable returns an absolute path that can be used to
// re-invoke the current program.
// It may not be valid after the current program exits.
func Executable() (string, error) {
p, err := executable()
return filepath.Clean(p), err
}
// Returns same path as Executable, returns just the folder
// path. Excludes the executable name.
func ExecutableFolder() (string, error) {
p, err := Executable()
if err != nil {
return "", err
}
folder, _ := filepath.Split(p)
return folder, nil
}
// Depricated. Same as Executable().
func GetExePath() (exePath string, err error) {
return Executable()
}

View File

@ -0,0 +1,20 @@
// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package osext
import (
"syscall"
"os"
"strconv"
)
func executable() (string, error) {
f, err := os.Open("/proc/" + strconv.Itoa(os.Getpid()) + "/text")
if err != nil {
return "", err
}
defer f.Close()
return syscall.Fd2path(int(f.Fd()))
}

View File

@ -0,0 +1,25 @@
// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build linux netbsd openbsd
package osext
import (
"errors"
"os"
"runtime"
)
func executable() (string, error) {
switch runtime.GOOS {
case "linux":
return os.Readlink("/proc/self/exe")
case "netbsd":
return os.Readlink("/proc/curproc/exe")
case "openbsd":
return os.Readlink("/proc/curproc/file")
}
return "", errors.New("ExecPath not implemented for " + runtime.GOOS)
}

View File

@ -0,0 +1,79 @@
// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build darwin freebsd
package osext
import (
"os"
"path/filepath"
"runtime"
"syscall"
"unsafe"
)
var initCwd, initCwdErr = os.Getwd()
func executable() (string, error) {
var mib [4]int32
switch runtime.GOOS {
case "freebsd":
mib = [4]int32{1 /* CTL_KERN */, 14 /* KERN_PROC */, 12 /* KERN_PROC_PATHNAME */, -1}
case "darwin":
mib = [4]int32{1 /* CTL_KERN */, 38 /* KERN_PROCARGS */, int32(os.Getpid()), -1}
}
n := uintptr(0)
// Get length.
_, _, errNum := syscall.Syscall6(syscall.SYS___SYSCTL, uintptr(unsafe.Pointer(&mib[0])), 4, 0, uintptr(unsafe.Pointer(&n)), 0, 0)
if errNum != 0 {
return "", errNum
}
if n == 0 { // This shouldn't happen.
return "", nil
}
buf := make([]byte, n)
_, _, errNum = syscall.Syscall6(syscall.SYS___SYSCTL, uintptr(unsafe.Pointer(&mib[0])), 4, uintptr(unsafe.Pointer(&buf[0])), uintptr(unsafe.Pointer(&n)), 0, 0)
if errNum != 0 {
return "", errNum
}
if n == 0 { // This shouldn't happen.
return "", nil
}
for i, v := range buf {
if v == 0 {
buf = buf[:i]
break
}
}
var err error
execPath := string(buf)
// execPath will not be empty due to above checks.
// Try to get the absolute path if the execPath is not rooted.
if execPath[0] != '/' {
execPath, err = getAbs(execPath)
if err != nil {
return execPath, err
}
}
// For darwin KERN_PROCARGS may return the path to a symlink rather than the
// actual executable.
if runtime.GOOS == "darwin" {
if execPath, err = filepath.EvalSymlinks(execPath); err != nil {
return execPath, err
}
}
return execPath, nil
}
func getAbs(execPath string) (string, error) {
if initCwdErr != nil {
return execPath, initCwdErr
}
// The execPath may begin with a "../" or a "./" so clean it first.
// Join the two paths, trailing and starting slashes undetermined, so use
// the generic Join function.
return filepath.Join(initCwd, filepath.Clean(execPath)), nil
}

View File

@ -0,0 +1,79 @@
// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build darwin linux freebsd netbsd windows
package osext
import (
"fmt"
"os"
oexec "os/exec"
"path/filepath"
"runtime"
"testing"
)
const execPath_EnvVar = "OSTEST_OUTPUT_EXECPATH"
func TestExecPath(t *testing.T) {
ep, err := Executable()
if err != nil {
t.Fatalf("ExecPath failed: %v", err)
}
// we want fn to be of the form "dir/prog"
dir := filepath.Dir(filepath.Dir(ep))
fn, err := filepath.Rel(dir, ep)
if err != nil {
t.Fatalf("filepath.Rel: %v", err)
}
cmd := &oexec.Cmd{}
// make child start with a relative program path
cmd.Dir = dir
cmd.Path = fn
// forge argv[0] for child, so that we can verify we could correctly
// get real path of the executable without influenced by argv[0].
cmd.Args = []string{"-", "-test.run=XXXX"}
cmd.Env = []string{fmt.Sprintf("%s=1", execPath_EnvVar)}
out, err := cmd.CombinedOutput()
if err != nil {
t.Fatalf("exec(self) failed: %v", err)
}
outs := string(out)
if !filepath.IsAbs(outs) {
t.Fatalf("Child returned %q, want an absolute path", out)
}
if !sameFile(outs, ep) {
t.Fatalf("Child returned %q, not the same file as %q", out, ep)
}
}
func sameFile(fn1, fn2 string) bool {
fi1, err := os.Stat(fn1)
if err != nil {
return false
}
fi2, err := os.Stat(fn2)
if err != nil {
return false
}
return os.SameFile(fi1, fi2)
}
func init() {
if e := os.Getenv(execPath_EnvVar); e != "" {
// first chdir to another path
dir := "/"
if runtime.GOOS == "windows" {
dir = filepath.VolumeName(".")
}
os.Chdir(dir)
if ep, err := Executable(); err != nil {
fmt.Fprint(os.Stderr, "ERROR: ", err)
} else {
fmt.Fprint(os.Stderr, ep)
}
os.Exit(0)
}
}

View File

@ -0,0 +1,34 @@
// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package osext
import (
"syscall"
"unicode/utf16"
"unsafe"
)
var (
kernel = syscall.MustLoadDLL("kernel32.dll")
getModuleFileNameProc = kernel.MustFindProc("GetModuleFileNameW")
)
// GetModuleFileName() with hModule = NULL
func executable() (exePath string, err error) {
return getModuleFileName()
}
func getModuleFileName() (string, error) {
var n uint32
b := make([]uint16, syscall.MAX_PATH)
size := uint32(len(b))
r0, _, e1 := getModuleFileNameProc.Call(0, uintptr(unsafe.Pointer(&b[0])), uintptr(size))
n = uint32(r0)
if n == 0 {
return "", e1
}
return string(utf16.Decode(b[0:n])), nil
}

View File

@ -0,0 +1,27 @@
Copyright (c) 2009 Google Inc. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -0,0 +1,84 @@
// Copyright 2011 Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package uuid
import (
"encoding/binary"
"fmt"
"os"
)
// A Domain represents a Version 2 domain
type Domain byte
// Domain constants for DCE Security (Version 2) UUIDs.
const (
Person = Domain(0)
Group = Domain(1)
Org = Domain(2)
)
// NewDCESecurity returns a DCE Security (Version 2) UUID.
//
// The domain should be one of Person, Group or Org.
// On a POSIX system the id should be the users UID for the Person
// domain and the users GID for the Group. The meaning of id for
// the domain Org or on non-POSIX systems is site defined.
//
// For a given domain/id pair the same token may be returned for up to
// 7 minutes and 10 seconds.
func NewDCESecurity(domain Domain, id uint32) UUID {
uuid := NewUUID()
if uuid != nil {
uuid[6] = (uuid[6] & 0x0f) | 0x20 // Version 2
uuid[9] = byte(domain)
binary.BigEndian.PutUint32(uuid[0:], id)
}
return uuid
}
// NewDCEPerson returns a DCE Security (Version 2) UUID in the person
// domain with the id returned by os.Getuid.
//
// NewDCEPerson(Person, uint32(os.Getuid()))
func NewDCEPerson() UUID {
return NewDCESecurity(Person, uint32(os.Getuid()))
}
// NewDCEGroup returns a DCE Security (Version 2) UUID in the group
// domain with the id returned by os.Getgid.
//
// NewDCEGroup(Group, uint32(os.Getgid()))
func NewDCEGroup() UUID {
return NewDCESecurity(Group, uint32(os.Getgid()))
}
// Domain returns the domain for a Version 2 UUID or false.
func (uuid UUID) Domain() (Domain, bool) {
if v, _ := uuid.Version(); v != 2 {
return 0, false
}
return Domain(uuid[9]), true
}
// Id returns the id for a Version 2 UUID or false.
func (uuid UUID) Id() (uint32, bool) {
if v, _ := uuid.Version(); v != 2 {
return 0, false
}
return binary.BigEndian.Uint32(uuid[0:4]), true
}
func (d Domain) String() string {
switch d {
case Person:
return "Person"
case Group:
return "Group"
case Org:
return "Org"
}
return fmt.Sprintf("Domain%d", int(d))
}

View File

@ -0,0 +1,8 @@
// Copyright 2011 Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// The uuid package generates and inspects UUIDs.
//
// UUIDs are based on RFC 4122 and DCE 1.1: Authentication and Security Services.
package uuid

View File

@ -0,0 +1,53 @@
// Copyright 2011 Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package uuid
import (
"crypto/md5"
"crypto/sha1"
"hash"
)
// Well known Name Space IDs and UUIDs
var (
NameSpace_DNS = Parse("6ba7b810-9dad-11d1-80b4-00c04fd430c8")
NameSpace_URL = Parse("6ba7b811-9dad-11d1-80b4-00c04fd430c8")
NameSpace_OID = Parse("6ba7b812-9dad-11d1-80b4-00c04fd430c8")
NameSpace_X500 = Parse("6ba7b814-9dad-11d1-80b4-00c04fd430c8")
NIL = Parse("00000000-0000-0000-0000-000000000000")
)
// NewHash returns a new UUID dervied from the hash of space concatenated with
// data generated by h. The hash should be at least 16 byte in length. The
// first 16 bytes of the hash are used to form the UUID. The version of the
// UUID will be the lower 4 bits of version. NewHash is used to implement
// NewMD5 and NewSHA1.
func NewHash(h hash.Hash, space UUID, data []byte, version int) UUID {
h.Reset()
h.Write(space)
h.Write([]byte(data))
s := h.Sum(nil)
uuid := make([]byte, 16)
copy(uuid, s)
uuid[6] = (uuid[6] & 0x0f) | uint8((version&0xf)<<4)
uuid[8] = (uuid[8] & 0x3f) | 0x80 // RFC 4122 variant
return uuid
}
// NewMD5 returns a new MD5 (Version 3) UUID based on the
// supplied name space and data.
//
// NewHash(md5.New(), space, data, 3)
func NewMD5(space UUID, data []byte) UUID {
return NewHash(md5.New(), space, data, 3)
}
// NewSHA1 returns a new SHA1 (Version 5) UUID based on the
// supplied name space and data.
//
// NewHash(sha1.New(), space, data, 5)
func NewSHA1(space UUID, data []byte) UUID {
return NewHash(sha1.New(), space, data, 5)
}

View File

@ -0,0 +1,101 @@
// Copyright 2011 Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package uuid
import "net"
var (
interfaces []net.Interface // cached list of interfaces
ifname string // name of interface being used
nodeID []byte // hardware for version 1 UUIDs
)
// NodeInterface returns the name of the interface from which the NodeID was
// derived. The interface "user" is returned if the NodeID was set by
// SetNodeID.
func NodeInterface() string {
return ifname
}
// SetNodeInterface selects the hardware address to be used for Version 1 UUIDs.
// If name is "" then the first usable interface found will be used or a random
// Node ID will be generated. If a named interface cannot be found then false
// is returned.
//
// SetNodeInterface never fails when name is "".
func SetNodeInterface(name string) bool {
if interfaces == nil {
var err error
interfaces, err = net.Interfaces()
if err != nil && name != "" {
return false
}
}
for _, ifs := range interfaces {
if len(ifs.HardwareAddr) >= 6 && (name == "" || name == ifs.Name) {
if setNodeID(ifs.HardwareAddr) {
ifname = ifs.Name
return true
}
}
}
// We found no interfaces with a valid hardware address. If name
// does not specify a specific interface generate a random Node ID
// (section 4.1.6)
if name == "" {
if nodeID == nil {
nodeID = make([]byte, 6)
}
randomBits(nodeID)
return true
}
return false
}
// NodeID returns a slice of a copy of the current Node ID, setting the Node ID
// if not already set.
func NodeID() []byte {
if nodeID == nil {
SetNodeInterface("")
}
nid := make([]byte, 6)
copy(nid, nodeID)
return nid
}
// SetNodeID sets the Node ID to be used for Version 1 UUIDs. The first 6 bytes
// of id are used. If id is less than 6 bytes then false is returned and the
// Node ID is not set.
func SetNodeID(id []byte) bool {
if setNodeID(id) {
ifname = "user"
return true
}
return false
}
func setNodeID(id []byte) bool {
if len(id) < 6 {
return false
}
if nodeID == nil {
nodeID = make([]byte, 6)
}
copy(nodeID, id)
return true
}
// NodeID returns the 6 byte node id encoded in uuid. It returns nil if uuid is
// not valid. The NodeID is only well defined for version 1 and 2 UUIDs.
func (uuid UUID) NodeID() []byte {
if len(uuid) != 16 {
return nil
}
node := make([]byte, 6)
copy(node, uuid[10:])
return node
}

View File

@ -0,0 +1,132 @@
// Copyright 2014 Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package uuid
import (
"encoding/binary"
"sync"
"time"
)
// A Time represents a time as the number of 100's of nanoseconds since 15 Oct
// 1582.
type Time int64
const (
lillian = 2299160 // Julian day of 15 Oct 1582
unix = 2440587 // Julian day of 1 Jan 1970
epoch = unix - lillian // Days between epochs
g1582 = epoch * 86400 // seconds between epochs
g1582ns100 = g1582 * 10000000 // 100s of a nanoseconds between epochs
)
var (
mu sync.Mutex
lasttime uint64 // last time we returned
clock_seq uint16 // clock sequence for this run
timeNow = time.Now // for testing
)
// UnixTime converts t the number of seconds and nanoseconds using the Unix
// epoch of 1 Jan 1970.
func (t Time) UnixTime() (sec, nsec int64) {
sec = int64(t - g1582ns100)
nsec = (sec % 10000000) * 100
sec /= 10000000
return sec, nsec
}
// GetTime returns the current Time (100s of nanoseconds since 15 Oct 1582) and
// adjusts the clock sequence as needed. An error is returned if the current
// time cannot be determined.
func GetTime() (Time, error) {
defer mu.Unlock()
mu.Lock()
return getTime()
}
func getTime() (Time, error) {
t := timeNow()
// If we don't have a clock sequence already, set one.
if clock_seq == 0 {
setClockSequence(-1)
}
now := uint64(t.UnixNano()/100) + g1582ns100
// If time has gone backwards with this clock sequence then we
// increment the clock sequence
if now <= lasttime {
clock_seq = ((clock_seq + 1) & 0x3fff) | 0x8000
}
lasttime = now
return Time(now), nil
}
// ClockSequence returns the current clock sequence, generating one if not
// already set. The clock sequence is only used for Version 1 UUIDs.
//
// The uuid package does not use global static storage for the clock sequence or
// the last time a UUID was generated. Unless SetClockSequence a new random
// clock sequence is generated the first time a clock sequence is requested by
// ClockSequence, GetTime, or NewUUID. (section 4.2.1.1) sequence is generated
// for
func ClockSequence() int {
defer mu.Unlock()
mu.Lock()
return clockSequence()
}
func clockSequence() int {
if clock_seq == 0 {
setClockSequence(-1)
}
return int(clock_seq & 0x3fff)
}
// SetClockSeq sets the clock sequence to the lower 14 bits of seq. Setting to
// -1 causes a new sequence to be generated.
func SetClockSequence(seq int) {
defer mu.Unlock()
mu.Lock()
setClockSequence(seq)
}
func setClockSequence(seq int) {
if seq == -1 {
var b [2]byte
randomBits(b[:]) // clock sequence
seq = int(b[0])<<8 | int(b[1])
}
old_seq := clock_seq
clock_seq = uint16(seq&0x3fff) | 0x8000 // Set our variant
if old_seq != clock_seq {
lasttime = 0
}
}
// Time returns the time in 100s of nanoseconds since 15 Oct 1582 encoded in
// uuid. It returns false if uuid is not valid. The time is only well defined
// for version 1 and 2 UUIDs.
func (uuid UUID) Time() (Time, bool) {
if len(uuid) != 16 {
return 0, false
}
time := int64(binary.BigEndian.Uint32(uuid[0:4]))
time |= int64(binary.BigEndian.Uint16(uuid[4:6])) << 32
time |= int64(binary.BigEndian.Uint16(uuid[6:8])&0xfff) << 48
return Time(time), true
}
// ClockSequence returns the clock sequence encoded in uuid. It returns false
// if uuid is not valid. The clock sequence is only well defined for version 1
// and 2 UUIDs.
func (uuid UUID) ClockSequence() (int, bool) {
if len(uuid) != 16 {
return 0, false
}
return int(binary.BigEndian.Uint16(uuid[8:10])) & 0x3fff, true
}

View File

@ -0,0 +1,43 @@
// Copyright 2011 Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package uuid
import (
"io"
)
// randomBits completely fills slice b with random data.
func randomBits(b []byte) {
if _, err := io.ReadFull(rander, b); err != nil {
panic(err.Error()) // rand should never fail
}
}
// xvalues returns the value of a byte as a hexadecimal digit or 255.
var xvalues = []byte{
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 255, 255, 255, 255, 255, 255,
255, 10, 11, 12, 13, 14, 15, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 10, 11, 12, 13, 14, 15, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
}
// xtob converts the the first two hex bytes of x into a byte.
func xtob(x string) (byte, bool) {
b1 := xvalues[x[0]]
b2 := xvalues[x[1]]
return (b1 << 4) | b2, b1 != 255 && b2 != 255
}

View File

@ -0,0 +1,163 @@
// Copyright 2011 Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package uuid
import (
"bytes"
"crypto/rand"
"fmt"
"io"
"strings"
)
// A UUID is a 128 bit (16 byte) Universal Unique IDentifier as defined in RFC
// 4122.
type UUID []byte
// A Version represents a UUIDs version.
type Version byte
// A Variant represents a UUIDs variant.
type Variant byte
// Constants returned by Variant.
const (
Invalid = Variant(iota) // Invalid UUID
RFC4122 // The variant specified in RFC4122
Reserved // Reserved, NCS backward compatibility.
Microsoft // Reserved, Microsoft Corporation backward compatibility.
Future // Reserved for future definition.
)
var rander = rand.Reader // random function
// New returns a new random (version 4) UUID as a string. It is a convenience
// function for NewRandom().String().
func New() string {
return NewRandom().String()
}
// Parse decodes s into a UUID or returns nil. Both the UUID form of
// xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx and
// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx are decoded.
func Parse(s string) UUID {
if len(s) == 36+9 {
if strings.ToLower(s[:9]) != "urn:uuid:" {
return nil
}
s = s[9:]
} else if len(s) != 36 {
return nil
}
if s[8] != '-' || s[13] != '-' || s[18] != '-' || s[23] != '-' {
return nil
}
uuid := make([]byte, 16)
for i, x := range []int{
0, 2, 4, 6,
9, 11,
14, 16,
19, 21,
24, 26, 28, 30, 32, 34} {
if v, ok := xtob(s[x:]); !ok {
return nil
} else {
uuid[i] = v
}
}
return uuid
}
// Equal returns true if uuid1 and uuid2 are equal.
func Equal(uuid1, uuid2 UUID) bool {
return bytes.Equal(uuid1, uuid2)
}
// String returns the string form of uuid, xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
// , or "" if uuid is invalid.
func (uuid UUID) String() string {
if uuid == nil || len(uuid) != 16 {
return ""
}
b := []byte(uuid)
return fmt.Sprintf("%08x-%04x-%04x-%04x-%012x",
b[:4], b[4:6], b[6:8], b[8:10], b[10:])
}
// URN returns the RFC 2141 URN form of uuid,
// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx, or "" if uuid is invalid.
func (uuid UUID) URN() string {
if uuid == nil || len(uuid) != 16 {
return ""
}
b := []byte(uuid)
return fmt.Sprintf("urn:uuid:%08x-%04x-%04x-%04x-%012x",
b[:4], b[4:6], b[6:8], b[8:10], b[10:])
}
// Variant returns the variant encoded in uuid. It returns Invalid if
// uuid is invalid.
func (uuid UUID) Variant() Variant {
if len(uuid) != 16 {
return Invalid
}
switch {
case (uuid[8] & 0xc0) == 0x80:
return RFC4122
case (uuid[8] & 0xe0) == 0xc0:
return Microsoft
case (uuid[8] & 0xe0) == 0xe0:
return Future
default:
return Reserved
}
panic("unreachable")
}
// Version returns the verison of uuid. It returns false if uuid is not
// valid.
func (uuid UUID) Version() (Version, bool) {
if len(uuid) != 16 {
return 0, false
}
return Version(uuid[6] >> 4), true
}
func (v Version) String() string {
if v > 15 {
return fmt.Sprintf("BAD_VERSION_%d", v)
}
return fmt.Sprintf("VERSION_%d", v)
}
func (v Variant) String() string {
switch v {
case RFC4122:
return "RFC4122"
case Reserved:
return "Reserved"
case Microsoft:
return "Microsoft"
case Future:
return "Future"
case Invalid:
return "Invalid"
}
return fmt.Sprintf("BadVariant%d", int(v))
}
// SetRand sets the random number generator to r, which implents io.Reader.
// If r.Read returns an error when the package requests random data then
// a panic will be issued.
//
// Calling SetRand with nil sets the random number generator to the default
// generator.
func SetRand(r io.Reader) {
if r == nil {
rander = rand.Reader
return
}
rander = r
}

View File

@ -0,0 +1,390 @@
// Copyright 2011 Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package uuid
import (
"bytes"
"fmt"
"os"
"strings"
"testing"
"time"
)
type test struct {
in string
version Version
variant Variant
isuuid bool
}
var tests = []test{
{"f47ac10b-58cc-0372-8567-0e02b2c3d479", 0, RFC4122, true},
{"f47ac10b-58cc-1372-8567-0e02b2c3d479", 1, RFC4122, true},
{"f47ac10b-58cc-2372-8567-0e02b2c3d479", 2, RFC4122, true},
{"f47ac10b-58cc-3372-8567-0e02b2c3d479", 3, RFC4122, true},
{"f47ac10b-58cc-4372-8567-0e02b2c3d479", 4, RFC4122, true},
{"f47ac10b-58cc-5372-8567-0e02b2c3d479", 5, RFC4122, true},
{"f47ac10b-58cc-6372-8567-0e02b2c3d479", 6, RFC4122, true},
{"f47ac10b-58cc-7372-8567-0e02b2c3d479", 7, RFC4122, true},
{"f47ac10b-58cc-8372-8567-0e02b2c3d479", 8, RFC4122, true},
{"f47ac10b-58cc-9372-8567-0e02b2c3d479", 9, RFC4122, true},
{"f47ac10b-58cc-a372-8567-0e02b2c3d479", 10, RFC4122, true},
{"f47ac10b-58cc-b372-8567-0e02b2c3d479", 11, RFC4122, true},
{"f47ac10b-58cc-c372-8567-0e02b2c3d479", 12, RFC4122, true},
{"f47ac10b-58cc-d372-8567-0e02b2c3d479", 13, RFC4122, true},
{"f47ac10b-58cc-e372-8567-0e02b2c3d479", 14, RFC4122, true},
{"f47ac10b-58cc-f372-8567-0e02b2c3d479", 15, RFC4122, true},
{"urn:uuid:f47ac10b-58cc-4372-0567-0e02b2c3d479", 4, Reserved, true},
{"URN:UUID:f47ac10b-58cc-4372-0567-0e02b2c3d479", 4, Reserved, true},
{"f47ac10b-58cc-4372-0567-0e02b2c3d479", 4, Reserved, true},
{"f47ac10b-58cc-4372-1567-0e02b2c3d479", 4, Reserved, true},
{"f47ac10b-58cc-4372-2567-0e02b2c3d479", 4, Reserved, true},
{"f47ac10b-58cc-4372-3567-0e02b2c3d479", 4, Reserved, true},
{"f47ac10b-58cc-4372-4567-0e02b2c3d479", 4, Reserved, true},
{"f47ac10b-58cc-4372-5567-0e02b2c3d479", 4, Reserved, true},
{"f47ac10b-58cc-4372-6567-0e02b2c3d479", 4, Reserved, true},
{"f47ac10b-58cc-4372-7567-0e02b2c3d479", 4, Reserved, true},
{"f47ac10b-58cc-4372-8567-0e02b2c3d479", 4, RFC4122, true},
{"f47ac10b-58cc-4372-9567-0e02b2c3d479", 4, RFC4122, true},
{"f47ac10b-58cc-4372-a567-0e02b2c3d479", 4, RFC4122, true},
{"f47ac10b-58cc-4372-b567-0e02b2c3d479", 4, RFC4122, true},
{"f47ac10b-58cc-4372-c567-0e02b2c3d479", 4, Microsoft, true},
{"f47ac10b-58cc-4372-d567-0e02b2c3d479", 4, Microsoft, true},
{"f47ac10b-58cc-4372-e567-0e02b2c3d479", 4, Future, true},
{"f47ac10b-58cc-4372-f567-0e02b2c3d479", 4, Future, true},
{"f47ac10b158cc-5372-a567-0e02b2c3d479", 0, Invalid, false},
{"f47ac10b-58cc25372-a567-0e02b2c3d479", 0, Invalid, false},
{"f47ac10b-58cc-53723a567-0e02b2c3d479", 0, Invalid, false},
{"f47ac10b-58cc-5372-a56740e02b2c3d479", 0, Invalid, false},
{"f47ac10b-58cc-5372-a567-0e02-2c3d479", 0, Invalid, false},
{"g47ac10b-58cc-4372-a567-0e02b2c3d479", 0, Invalid, false},
}
var constants = []struct {
c interface{}
name string
}{
{Person, "Person"},
{Group, "Group"},
{Org, "Org"},
{Invalid, "Invalid"},
{RFC4122, "RFC4122"},
{Reserved, "Reserved"},
{Microsoft, "Microsoft"},
{Future, "Future"},
{Domain(17), "Domain17"},
{Variant(42), "BadVariant42"},
}
func testTest(t *testing.T, in string, tt test) {
uuid := Parse(in)
if ok := (uuid != nil); ok != tt.isuuid {
t.Errorf("Parse(%s) got %v expected %v\b", in, ok, tt.isuuid)
}
if uuid == nil {
return
}
if v := uuid.Variant(); v != tt.variant {
t.Errorf("Variant(%s) got %d expected %d\b", in, v, tt.variant)
}
if v, _ := uuid.Version(); v != tt.version {
t.Errorf("Version(%s) got %d expected %d\b", in, v, tt.version)
}
}
func TestUUID(t *testing.T) {
for _, tt := range tests {
testTest(t, tt.in, tt)
testTest(t, strings.ToUpper(tt.in), tt)
}
}
func TestConstants(t *testing.T) {
for x, tt := range constants {
v, ok := tt.c.(fmt.Stringer)
if !ok {
t.Errorf("%x: %v: not a stringer", x, v)
} else if s := v.String(); s != tt.name {
v, _ := tt.c.(int)
t.Errorf("%x: Constant %T:%d gives %q, expected %q\n", x, tt.c, v, s, tt.name)
}
}
}
func TestRandomUUID(t *testing.T) {
m := make(map[string]bool)
for x := 1; x < 32; x++ {
uuid := NewRandom()
s := uuid.String()
if m[s] {
t.Errorf("NewRandom returned duplicated UUID %s\n", s)
}
m[s] = true
if v, _ := uuid.Version(); v != 4 {
t.Errorf("Random UUID of version %s\n", v)
}
if uuid.Variant() != RFC4122 {
t.Errorf("Random UUID is variant %d\n", uuid.Variant())
}
}
}
func TestNew(t *testing.T) {
m := make(map[string]bool)
for x := 1; x < 32; x++ {
s := New()
if m[s] {
t.Errorf("New returned duplicated UUID %s\n", s)
}
m[s] = true
uuid := Parse(s)
if uuid == nil {
t.Errorf("New returned %q which does not decode\n", s)
continue
}
if v, _ := uuid.Version(); v != 4 {
t.Errorf("Random UUID of version %s\n", v)
}
if uuid.Variant() != RFC4122 {
t.Errorf("Random UUID is variant %d\n", uuid.Variant())
}
}
}
func clockSeq(t *testing.T, uuid UUID) int {
seq, ok := uuid.ClockSequence()
if !ok {
t.Fatalf("%s: invalid clock sequence\n", uuid)
}
return seq
}
func TestClockSeq(t *testing.T) {
// Fake time.Now for this test to return a monotonically advancing time; restore it at end.
defer func(orig func() time.Time) { timeNow = orig }(timeNow)
monTime := time.Now()
timeNow = func() time.Time {
monTime = monTime.Add(1 * time.Second)
return monTime
}
SetClockSequence(-1)
uuid1 := NewUUID()
uuid2 := NewUUID()
if clockSeq(t, uuid1) != clockSeq(t, uuid2) {
t.Errorf("clock sequence %d != %d\n", clockSeq(t, uuid1), clockSeq(t, uuid2))
}
SetClockSequence(-1)
uuid2 = NewUUID()
// Just on the very off chance we generated the same sequence
// two times we try again.
if clockSeq(t, uuid1) == clockSeq(t, uuid2) {
SetClockSequence(-1)
uuid2 = NewUUID()
}
if clockSeq(t, uuid1) == clockSeq(t, uuid2) {
t.Errorf("Duplicate clock sequence %d\n", clockSeq(t, uuid1))
}
SetClockSequence(0x1234)
uuid1 = NewUUID()
if seq := clockSeq(t, uuid1); seq != 0x1234 {
t.Errorf("%s: expected seq 0x1234 got 0x%04x\n", uuid1, seq)
}
}
func TestCoding(t *testing.T) {
text := "7d444840-9dc0-11d1-b245-5ffdce74fad2"
urn := "urn:uuid:7d444840-9dc0-11d1-b245-5ffdce74fad2"
data := UUID{
0x7d, 0x44, 0x48, 0x40,
0x9d, 0xc0,
0x11, 0xd1,
0xb2, 0x45,
0x5f, 0xfd, 0xce, 0x74, 0xfa, 0xd2,
}
if v := data.String(); v != text {
t.Errorf("%x: encoded to %s, expected %s\n", data, v, text)
}
if v := data.URN(); v != urn {
t.Errorf("%x: urn is %s, expected %s\n", data, v, urn)
}
uuid := Parse(text)
if !Equal(uuid, data) {
t.Errorf("%s: decoded to %s, expected %s\n", text, uuid, data)
}
}
func TestVersion1(t *testing.T) {
uuid1 := NewUUID()
uuid2 := NewUUID()
if Equal(uuid1, uuid2) {
t.Errorf("%s:duplicate uuid\n", uuid1)
}
if v, _ := uuid1.Version(); v != 1 {
t.Errorf("%s: version %s expected 1\n", uuid1, v)
}
if v, _ := uuid2.Version(); v != 1 {
t.Errorf("%s: version %s expected 1\n", uuid2, v)
}
n1 := uuid1.NodeID()
n2 := uuid2.NodeID()
if !bytes.Equal(n1, n2) {
t.Errorf("Different nodes %x != %x\n", n1, n2)
}
t1, ok := uuid1.Time()
if !ok {
t.Errorf("%s: invalid time\n", uuid1)
}
t2, ok := uuid2.Time()
if !ok {
t.Errorf("%s: invalid time\n", uuid2)
}
q1, ok := uuid1.ClockSequence()
if !ok {
t.Errorf("%s: invalid clock sequence\n", uuid1)
}
q2, ok := uuid2.ClockSequence()
if !ok {
t.Errorf("%s: invalid clock sequence", uuid2)
}
switch {
case t1 == t2 && q1 == q2:
t.Errorf("time stopped\n")
case t1 > t2 && q1 == q2:
t.Errorf("time reversed\n")
case t1 < t2 && q1 != q2:
t.Errorf("clock sequence chaned unexpectedly\n")
}
}
func TestNodeAndTime(t *testing.T) {
// Time is February 5, 1998 12:30:23.136364800 AM GMT
uuid := Parse("7d444840-9dc0-11d1-b245-5ffdce74fad2")
node := []byte{0x5f, 0xfd, 0xce, 0x74, 0xfa, 0xd2}
ts, ok := uuid.Time()
if ok {
c := time.Unix(ts.UnixTime())
want := time.Date(1998, 2, 5, 0, 30, 23, 136364800, time.UTC)
if !c.Equal(want) {
t.Errorf("Got time %v, want %v", c, want)
}
} else {
t.Errorf("%s: bad time\n", uuid)
}
if !bytes.Equal(node, uuid.NodeID()) {
t.Errorf("Expected node %v got %v\n", node, uuid.NodeID())
}
}
func TestMD5(t *testing.T) {
uuid := NewMD5(NameSpace_DNS, []byte("python.org")).String()
want := "6fa459ea-ee8a-3ca4-894e-db77e160355e"
if uuid != want {
t.Errorf("MD5: got %q expected %q\n", uuid, want)
}
}
func TestSHA1(t *testing.T) {
uuid := NewSHA1(NameSpace_DNS, []byte("python.org")).String()
want := "886313e1-3b8a-5372-9b90-0c9aee199e5d"
if uuid != want {
t.Errorf("SHA1: got %q expected %q\n", uuid, want)
}
}
func TestNodeID(t *testing.T) {
nid := []byte{1, 2, 3, 4, 5, 6}
SetNodeInterface("")
s := NodeInterface()
if s == "" || s == "user" {
t.Errorf("NodeInterface %q after SetInteface\n", s)
}
node1 := NodeID()
if node1 == nil {
t.Errorf("NodeID nil after SetNodeInterface\n", s)
}
SetNodeID(nid)
s = NodeInterface()
if s != "user" {
t.Errorf("Expected NodeInterface %q got %q\n", "user", s)
}
node2 := NodeID()
if node2 == nil {
t.Errorf("NodeID nil after SetNodeID\n", s)
}
if bytes.Equal(node1, node2) {
t.Errorf("NodeID not changed after SetNodeID\n", s)
} else if !bytes.Equal(nid, node2) {
t.Errorf("NodeID is %x, expected %x\n", node2, nid)
}
}
func testDCE(t *testing.T, name string, uuid UUID, domain Domain, id uint32) {
if uuid == nil {
t.Errorf("%s failed\n", name)
return
}
if v, _ := uuid.Version(); v != 2 {
t.Errorf("%s: %s: expected version 2, got %s\n", name, uuid, v)
return
}
if v, ok := uuid.Domain(); !ok || v != domain {
if !ok {
t.Errorf("%s: %d: Domain failed\n", name, uuid)
} else {
t.Errorf("%s: %s: expected domain %d, got %d\n", name, uuid, domain, v)
}
}
if v, ok := uuid.Id(); !ok || v != id {
if !ok {
t.Errorf("%s: %d: Id failed\n", name, uuid)
} else {
t.Errorf("%s: %s: expected id %d, got %d\n", name, uuid, id, v)
}
}
}
func TestDCE(t *testing.T) {
testDCE(t, "NewDCESecurity", NewDCESecurity(42, 12345678), 42, 12345678)
testDCE(t, "NewDCEPerson", NewDCEPerson(), Person, uint32(os.Getuid()))
testDCE(t, "NewDCEGroup", NewDCEGroup(), Group, uint32(os.Getgid()))
}
type badRand struct{}
func (r badRand) Read(buf []byte) (int, error) {
for i, _ := range buf {
buf[i] = byte(i)
}
return len(buf), nil
}
func TestBadRand(t *testing.T) {
SetRand(badRand{})
uuid1 := New()
uuid2 := New()
if uuid1 != uuid2 {
t.Errorf("execpted duplicates, got %q and %q\n", uuid1, uuid2)
}
SetRand(nil)
uuid1 = New()
uuid2 = New()
if uuid1 == uuid2 {
t.Errorf("unexecpted duplicates, got %q\n", uuid1)
}
}

View File

@ -0,0 +1,41 @@
// Copyright 2011 Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package uuid
import (
"encoding/binary"
)
// NewUUID returns a Version 1 UUID based on the current NodeID and clock
// sequence, and the current time. If the NodeID has not been set by SetNodeID
// or SetNodeInterface then it will be set automatically. If the NodeID cannot
// be set NewUUID returns nil. If clock sequence has not been set by
// SetClockSequence then it will be set automatically. If GetTime fails to
// return the current NewUUID returns nil.
func NewUUID() UUID {
if nodeID == nil {
SetNodeInterface("")
}
now, err := GetTime()
if err != nil {
return nil
}
uuid := make([]byte, 16)
time_low := uint32(now & 0xffffffff)
time_mid := uint16((now >> 32) & 0xffff)
time_hi := uint16((now >> 48) & 0x0fff)
time_hi |= 0x1000 // Version 1
binary.BigEndian.PutUint32(uuid[0:], time_low)
binary.BigEndian.PutUint16(uuid[4:], time_mid)
binary.BigEndian.PutUint16(uuid[6:], time_hi)
binary.BigEndian.PutUint16(uuid[8:], clock_seq)
copy(uuid[10:], nodeID)
return uuid
}

View File

@ -0,0 +1,25 @@
// Copyright 2011 Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package uuid
// Random returns a Random (Version 4) UUID or panics.
//
// The strength of the UUIDs is based on the strength of the crypto/rand
// package.
//
// A note about uniqueness derived from from the UUID Wikipedia entry:
//
// Randomly generated UUIDs have 122 random bits. One's annual risk of being
// hit by a meteorite is estimated to be one chance in 17 billion, that
// means the probability is about 0.00000000006 (6 × 1011),
// equivalent to the odds of creating a few tens of trillions of UUIDs in a
// year and having one duplicate.
func NewRandom() UUID {
uuid := make([]byte, 16)
randomBits([]byte(uuid))
uuid[6] = (uuid[6] & 0x0f) | 0x40 // Version 4
uuid[8] = (uuid[8] & 0x3f) | 0x80 // Variant is 10
return uuid
}

View File

@ -0,0 +1,124 @@
// Copyright 2011 The Snappy-Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package snappy
import (
"encoding/binary"
"errors"
)
// ErrCorrupt reports that the input is invalid.
var ErrCorrupt = errors.New("snappy: corrupt input")
// DecodedLen returns the length of the decoded block.
func DecodedLen(src []byte) (int, error) {
v, _, err := decodedLen(src)
return v, err
}
// decodedLen returns the length of the decoded block and the number of bytes
// that the length header occupied.
func decodedLen(src []byte) (blockLen, headerLen int, err error) {
v, n := binary.Uvarint(src)
if n == 0 {
return 0, 0, ErrCorrupt
}
if uint64(int(v)) != v {
return 0, 0, errors.New("snappy: decoded block is too large")
}
return int(v), n, nil
}
// Decode returns the decoded form of src. The returned slice may be a sub-
// slice of dst if dst was large enough to hold the entire decoded block.
// Otherwise, a newly allocated slice will be returned.
// It is valid to pass a nil dst.
func Decode(dst, src []byte) ([]byte, error) {
dLen, s, err := decodedLen(src)
if err != nil {
return nil, err
}
if len(dst) < dLen {
dst = make([]byte, dLen)
}
var d, offset, length int
for s < len(src) {
switch src[s] & 0x03 {
case tagLiteral:
x := uint(src[s] >> 2)
switch {
case x < 60:
s += 1
case x == 60:
s += 2
if s > len(src) {
return nil, ErrCorrupt
}
x = uint(src[s-1])
case x == 61:
s += 3
if s > len(src) {
return nil, ErrCorrupt
}
x = uint(src[s-2]) | uint(src[s-1])<<8
case x == 62:
s += 4
if s > len(src) {
return nil, ErrCorrupt
}
x = uint(src[s-3]) | uint(src[s-2])<<8 | uint(src[s-1])<<16
case x == 63:
s += 5
if s > len(src) {
return nil, ErrCorrupt
}
x = uint(src[s-4]) | uint(src[s-3])<<8 | uint(src[s-2])<<16 | uint(src[s-1])<<24
}
length = int(x + 1)
if length <= 0 {
return nil, errors.New("snappy: unsupported literal length")
}
if length > len(dst)-d || length > len(src)-s {
return nil, ErrCorrupt
}
copy(dst[d:], src[s:s+length])
d += length
s += length
continue
case tagCopy1:
s += 2
if s > len(src) {
return nil, ErrCorrupt
}
length = 4 + int(src[s-2])>>2&0x7
offset = int(src[s-2])&0xe0<<3 | int(src[s-1])
case tagCopy2:
s += 3
if s > len(src) {
return nil, ErrCorrupt
}
length = 1 + int(src[s-3])>>2
offset = int(src[s-2]) | int(src[s-1])<<8
case tagCopy4:
return nil, errors.New("snappy: unsupported COPY_4 tag")
}
end := d + length
if offset > d || end > len(dst) {
return nil, ErrCorrupt
}
for ; d < end; d++ {
dst[d] = dst[d-offset]
}
}
if d != dLen {
return nil, ErrCorrupt
}
return dst[:d], nil
}

View File

@ -0,0 +1,174 @@
// Copyright 2011 The Snappy-Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package snappy
import (
"encoding/binary"
)
// We limit how far copy back-references can go, the same as the C++ code.
const maxOffset = 1 << 15
// emitLiteral writes a literal chunk and returns the number of bytes written.
func emitLiteral(dst, lit []byte) int {
i, n := 0, uint(len(lit)-1)
switch {
case n < 60:
dst[0] = uint8(n)<<2 | tagLiteral
i = 1
case n < 1<<8:
dst[0] = 60<<2 | tagLiteral
dst[1] = uint8(n)
i = 2
case n < 1<<16:
dst[0] = 61<<2 | tagLiteral
dst[1] = uint8(n)
dst[2] = uint8(n >> 8)
i = 3
case n < 1<<24:
dst[0] = 62<<2 | tagLiteral
dst[1] = uint8(n)
dst[2] = uint8(n >> 8)
dst[3] = uint8(n >> 16)
i = 4
case int64(n) < 1<<32:
dst[0] = 63<<2 | tagLiteral
dst[1] = uint8(n)
dst[2] = uint8(n >> 8)
dst[3] = uint8(n >> 16)
dst[4] = uint8(n >> 24)
i = 5
default:
panic("snappy: source buffer is too long")
}
if copy(dst[i:], lit) != len(lit) {
panic("snappy: destination buffer is too short")
}
return i + len(lit)
}
// emitCopy writes a copy chunk and returns the number of bytes written.
func emitCopy(dst []byte, offset, length int) int {
i := 0
for length > 0 {
x := length - 4
if 0 <= x && x < 1<<3 && offset < 1<<11 {
dst[i+0] = uint8(offset>>8)&0x07<<5 | uint8(x)<<2 | tagCopy1
dst[i+1] = uint8(offset)
i += 2
break
}
x = length
if x > 1<<6 {
x = 1 << 6
}
dst[i+0] = uint8(x-1)<<2 | tagCopy2
dst[i+1] = uint8(offset)
dst[i+2] = uint8(offset >> 8)
i += 3
length -= x
}
return i
}
// Encode returns the encoded form of src. The returned slice may be a sub-
// slice of dst if dst was large enough to hold the entire encoded block.
// Otherwise, a newly allocated slice will be returned.
// It is valid to pass a nil dst.
func Encode(dst, src []byte) ([]byte, error) {
if n := MaxEncodedLen(len(src)); len(dst) < n {
dst = make([]byte, n)
}
// The block starts with the varint-encoded length of the decompressed bytes.
d := binary.PutUvarint(dst, uint64(len(src)))
// Return early if src is short.
if len(src) <= 4 {
if len(src) != 0 {
d += emitLiteral(dst[d:], src)
}
return dst[:d], nil
}
// Initialize the hash table. Its size ranges from 1<<8 to 1<<14 inclusive.
const maxTableSize = 1 << 14
shift, tableSize := uint(32-8), 1<<8
for tableSize < maxTableSize && tableSize < len(src) {
shift--
tableSize *= 2
}
var table [maxTableSize]int
// Iterate over the source bytes.
var (
s int // The iterator position.
t int // The last position with the same hash as s.
lit int // The start position of any pending literal bytes.
)
for s+3 < len(src) {
// Update the hash table.
b0, b1, b2, b3 := src[s], src[s+1], src[s+2], src[s+3]
h := uint32(b0) | uint32(b1)<<8 | uint32(b2)<<16 | uint32(b3)<<24
p := &table[(h*0x1e35a7bd)>>shift]
// We need to to store values in [-1, inf) in table. To save
// some initialization time, (re)use the table's zero value
// and shift the values against this zero: add 1 on writes,
// subtract 1 on reads.
t, *p = *p-1, s+1
// If t is invalid or src[s:s+4] differs from src[t:t+4], accumulate a literal byte.
if t < 0 || s-t >= maxOffset || b0 != src[t] || b1 != src[t+1] || b2 != src[t+2] || b3 != src[t+3] {
s++
continue
}
// Otherwise, we have a match. First, emit any pending literal bytes.
if lit != s {
d += emitLiteral(dst[d:], src[lit:s])
}
// Extend the match to be as long as possible.
s0 := s
s, t = s+4, t+4
for s < len(src) && src[s] == src[t] {
s++
t++
}
// Emit the copied bytes.
d += emitCopy(dst[d:], s-t, s-s0)
lit = s
}
// Emit any final pending literal bytes and return.
if lit != len(src) {
d += emitLiteral(dst[d:], src[lit:])
}
return dst[:d], nil
}
// MaxEncodedLen returns the maximum length of a snappy block, given its
// uncompressed length.
func MaxEncodedLen(srcLen int) int {
// Compressed data can be defined as:
// compressed := item* literal*
// item := literal* copy
//
// The trailing literal sequence has a space blowup of at most 62/60
// since a literal of length 60 needs one tag byte + one extra byte
// for length information.
//
// Item blowup is trickier to measure. Suppose the "copy" op copies
// 4 bytes of data. Because of a special check in the encoding code,
// we produce a 4-byte copy only if the offset is < 65536. Therefore
// the copy op takes 3 bytes to encode, and this type of item leads
// to at most the 62/60 blowup for representing literals.
//
// Suppose the "copy" op copies 5 bytes of data. If the offset is big
// enough, it will take 5 bytes to encode the copy op. Therefore the
// worst case here is a one-byte literal followed by a five-byte copy.
// That is, 6 bytes of input turn into 7 bytes of "compressed" data.
//
// This last factor dominates the blowup, so the final estimate is:
return 32 + srcLen + srcLen/6
}

View File

@ -0,0 +1,38 @@
// Copyright 2011 The Snappy-Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package snappy implements the snappy block-based compression format.
// It aims for very high speeds and reasonable compression.
//
// The C++ snappy implementation is at http://code.google.com/p/snappy/
package snappy
/*
Each encoded block begins with the varint-encoded length of the decoded data,
followed by a sequence of chunks. Chunks begin and end on byte boundaries. The
first byte of each chunk is broken into its 2 least and 6 most significant bits
called l and m: l ranges in [0, 4) and m ranges in [0, 64). l is the chunk tag.
Zero means a literal tag. All other values mean a copy tag.
For literal tags:
- If m < 60, the next 1 + m bytes are literal bytes.
- Otherwise, let n be the little-endian unsigned integer denoted by the next
m - 59 bytes. The next 1 + n bytes after that are literal bytes.
For copy tags, length bytes are copied from offset bytes ago, in the style of
Lempel-Ziv compression algorithms. In particular:
- For l == 1, the offset ranges in [0, 1<<11) and the length in [4, 12).
The length is 4 + the low 3 bits of m. The high 3 bits of m form bits 8-10
of the offset. The next byte is bits 0-7 of the offset.
- For l == 2, the offset ranges in [0, 1<<16) and the length in [1, 65).
The length is 1 + m. The offset is the little-endian unsigned integer
denoted by the next 2 bytes.
- For l == 3, this tag is a legacy format that is no longer supported.
*/
const (
tagLiteral = 0x00
tagCopy1 = 0x01
tagCopy2 = 0x02
tagCopy4 = 0x03
)

View File

@ -0,0 +1,261 @@
// Copyright 2011 The Snappy-Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package snappy
import (
"bytes"
"flag"
"fmt"
"io"
"io/ioutil"
"math/rand"
"net/http"
"os"
"path/filepath"
"strings"
"testing"
)
var download = flag.Bool("download", false, "If true, download any missing files before running benchmarks")
func roundtrip(b, ebuf, dbuf []byte) error {
e, err := Encode(ebuf, b)
if err != nil {
return fmt.Errorf("encoding error: %v", err)
}
d, err := Decode(dbuf, e)
if err != nil {
return fmt.Errorf("decoding error: %v", err)
}
if !bytes.Equal(b, d) {
return fmt.Errorf("roundtrip mismatch:\n\twant %v\n\tgot %v", b, d)
}
return nil
}
func TestEmpty(t *testing.T) {
if err := roundtrip(nil, nil, nil); err != nil {
t.Fatal(err)
}
}
func TestSmallCopy(t *testing.T) {
for _, ebuf := range [][]byte{nil, make([]byte, 20), make([]byte, 64)} {
for _, dbuf := range [][]byte{nil, make([]byte, 20), make([]byte, 64)} {
for i := 0; i < 32; i++ {
s := "aaaa" + strings.Repeat("b", i) + "aaaabbbb"
if err := roundtrip([]byte(s), ebuf, dbuf); err != nil {
t.Errorf("len(ebuf)=%d, len(dbuf)=%d, i=%d: %v", len(ebuf), len(dbuf), i, err)
}
}
}
}
}
func TestSmallRand(t *testing.T) {
rand.Seed(27354294)
for n := 1; n < 20000; n += 23 {
b := make([]byte, n)
for i, _ := range b {
b[i] = uint8(rand.Uint32())
}
if err := roundtrip(b, nil, nil); err != nil {
t.Fatal(err)
}
}
}
func TestSmallRegular(t *testing.T) {
for n := 1; n < 20000; n += 23 {
b := make([]byte, n)
for i, _ := range b {
b[i] = uint8(i%10 + 'a')
}
if err := roundtrip(b, nil, nil); err != nil {
t.Fatal(err)
}
}
}
func benchDecode(b *testing.B, src []byte) {
encoded, err := Encode(nil, src)
if err != nil {
b.Fatal(err)
}
// Bandwidth is in amount of uncompressed data.
b.SetBytes(int64(len(src)))
b.ResetTimer()
for i := 0; i < b.N; i++ {
Decode(src, encoded)
}
}
func benchEncode(b *testing.B, src []byte) {
// Bandwidth is in amount of uncompressed data.
b.SetBytes(int64(len(src)))
dst := make([]byte, MaxEncodedLen(len(src)))
b.ResetTimer()
for i := 0; i < b.N; i++ {
Encode(dst, src)
}
}
func readFile(b *testing.B, filename string) []byte {
src, err := ioutil.ReadFile(filename)
if err != nil {
b.Fatalf("failed reading %s: %s", filename, err)
}
if len(src) == 0 {
b.Fatalf("%s has zero length", filename)
}
return src
}
// expand returns a slice of length n containing repeated copies of src.
func expand(src []byte, n int) []byte {
dst := make([]byte, n)
for x := dst; len(x) > 0; {
i := copy(x, src)
x = x[i:]
}
return dst
}
func benchWords(b *testing.B, n int, decode bool) {
// Note: the file is OS-language dependent so the resulting values are not
// directly comparable for non-US-English OS installations.
data := expand(readFile(b, "/usr/share/dict/words"), n)
if decode {
benchDecode(b, data)
} else {
benchEncode(b, data)
}
}
func BenchmarkWordsDecode1e3(b *testing.B) { benchWords(b, 1e3, true) }
func BenchmarkWordsDecode1e4(b *testing.B) { benchWords(b, 1e4, true) }
func BenchmarkWordsDecode1e5(b *testing.B) { benchWords(b, 1e5, true) }
func BenchmarkWordsDecode1e6(b *testing.B) { benchWords(b, 1e6, true) }
func BenchmarkWordsEncode1e3(b *testing.B) { benchWords(b, 1e3, false) }
func BenchmarkWordsEncode1e4(b *testing.B) { benchWords(b, 1e4, false) }
func BenchmarkWordsEncode1e5(b *testing.B) { benchWords(b, 1e5, false) }
func BenchmarkWordsEncode1e6(b *testing.B) { benchWords(b, 1e6, false) }
// testFiles' values are copied directly from
// https://code.google.com/p/snappy/source/browse/trunk/snappy_unittest.cc.
// The label field is unused in snappy-go.
var testFiles = []struct {
label string
filename string
}{
{"html", "html"},
{"urls", "urls.10K"},
{"jpg", "house.jpg"},
{"pdf", "mapreduce-osdi-1.pdf"},
{"html4", "html_x_4"},
{"cp", "cp.html"},
{"c", "fields.c"},
{"lsp", "grammar.lsp"},
{"xls", "kennedy.xls"},
{"txt1", "alice29.txt"},
{"txt2", "asyoulik.txt"},
{"txt3", "lcet10.txt"},
{"txt4", "plrabn12.txt"},
{"bin", "ptt5"},
{"sum", "sum"},
{"man", "xargs.1"},
{"pb", "geo.protodata"},
{"gaviota", "kppkn.gtb"},
}
// The test data files are present at this canonical URL.
const baseURL = "https://snappy.googlecode.com/svn/trunk/testdata/"
func downloadTestdata(basename string) (errRet error) {
filename := filepath.Join("testdata", basename)
f, err := os.Create(filename)
if err != nil {
return fmt.Errorf("failed to create %s: %s", filename, err)
}
defer f.Close()
defer func() {
if errRet != nil {
os.Remove(filename)
}
}()
resp, err := http.Get(baseURL + basename)
if err != nil {
return fmt.Errorf("failed to download %s: %s", baseURL+basename, err)
}
defer resp.Body.Close()
_, err = io.Copy(f, resp.Body)
if err != nil {
return fmt.Errorf("failed to write %s: %s", filename, err)
}
return nil
}
func benchFile(b *testing.B, n int, decode bool) {
filename := filepath.Join("testdata", testFiles[n].filename)
if stat, err := os.Stat(filename); err != nil || stat.Size() == 0 {
if !*download {
b.Fatal("test data not found; skipping benchmark without the -download flag")
}
// Download the official snappy C++ implementation reference test data
// files for benchmarking.
if err := os.Mkdir("testdata", 0777); err != nil && !os.IsExist(err) {
b.Fatalf("failed to create testdata: %s", err)
}
for _, tf := range testFiles {
if err := downloadTestdata(tf.filename); err != nil {
b.Fatalf("failed to download testdata: %s", err)
}
}
}
data := readFile(b, filename)
if decode {
benchDecode(b, data)
} else {
benchEncode(b, data)
}
}
// Naming convention is kept similar to what snappy's C++ implementation uses.
func Benchmark_UFlat0(b *testing.B) { benchFile(b, 0, true) }
func Benchmark_UFlat1(b *testing.B) { benchFile(b, 1, true) }
func Benchmark_UFlat2(b *testing.B) { benchFile(b, 2, true) }
func Benchmark_UFlat3(b *testing.B) { benchFile(b, 3, true) }
func Benchmark_UFlat4(b *testing.B) { benchFile(b, 4, true) }
func Benchmark_UFlat5(b *testing.B) { benchFile(b, 5, true) }
func Benchmark_UFlat6(b *testing.B) { benchFile(b, 6, true) }
func Benchmark_UFlat7(b *testing.B) { benchFile(b, 7, true) }
func Benchmark_UFlat8(b *testing.B) { benchFile(b, 8, true) }
func Benchmark_UFlat9(b *testing.B) { benchFile(b, 9, true) }
func Benchmark_UFlat10(b *testing.B) { benchFile(b, 10, true) }
func Benchmark_UFlat11(b *testing.B) { benchFile(b, 11, true) }
func Benchmark_UFlat12(b *testing.B) { benchFile(b, 12, true) }
func Benchmark_UFlat13(b *testing.B) { benchFile(b, 13, true) }
func Benchmark_UFlat14(b *testing.B) { benchFile(b, 14, true) }
func Benchmark_UFlat15(b *testing.B) { benchFile(b, 15, true) }
func Benchmark_UFlat16(b *testing.B) { benchFile(b, 16, true) }
func Benchmark_UFlat17(b *testing.B) { benchFile(b, 17, true) }
func Benchmark_ZFlat0(b *testing.B) { benchFile(b, 0, false) }
func Benchmark_ZFlat1(b *testing.B) { benchFile(b, 1, false) }
func Benchmark_ZFlat2(b *testing.B) { benchFile(b, 2, false) }
func Benchmark_ZFlat3(b *testing.B) { benchFile(b, 3, false) }
func Benchmark_ZFlat4(b *testing.B) { benchFile(b, 4, false) }
func Benchmark_ZFlat5(b *testing.B) { benchFile(b, 5, false) }
func Benchmark_ZFlat6(b *testing.B) { benchFile(b, 6, false) }
func Benchmark_ZFlat7(b *testing.B) { benchFile(b, 7, false) }
func Benchmark_ZFlat8(b *testing.B) { benchFile(b, 8, false) }
func Benchmark_ZFlat9(b *testing.B) { benchFile(b, 9, false) }
func Benchmark_ZFlat10(b *testing.B) { benchFile(b, 10, false) }
func Benchmark_ZFlat11(b *testing.B) { benchFile(b, 11, false) }
func Benchmark_ZFlat12(b *testing.B) { benchFile(b, 12, false) }
func Benchmark_ZFlat13(b *testing.B) { benchFile(b, 13, false) }
func Benchmark_ZFlat14(b *testing.B) { benchFile(b, 14, false) }
func Benchmark_ZFlat15(b *testing.B) { benchFile(b, 15, false) }
func Benchmark_ZFlat16(b *testing.B) { benchFile(b, 16, false) }
func Benchmark_ZFlat17(b *testing.B) { benchFile(b, 17, false) }

View File

@ -0,0 +1,5 @@
/tmp
*/**/*un~
*un~
.DS_Store
*/**/.DS_Store

View File

@ -0,0 +1,3 @@
[submodule "serp"]
path = serpent
url = https://github.com/ethereum/serpent.git

View File

@ -0,0 +1,12 @@
[serpent](https://github.com/ethereum/serpent) go bindings.
## Build instructions
```
go get -d github.com/ethereum/serpent-go
cd $GOPATH/src/github.com/ethereum/serpent-go
git submodule init
git submodule update
```
You're now ready to go :-)

View File

@ -0,0 +1,16 @@
#include "serpent/bignum.cpp"
#include "serpent/util.cpp"
#include "serpent/tokenize.cpp"
#include "serpent/parser.cpp"
#include "serpent/compiler.cpp"
#include "serpent/funcs.cpp"
#include "serpent/lllparser.cpp"
#include "serpent/rewriter.cpp"
#include "serpent/opcodes.cpp"
#include "serpent/optimize.cpp"
#include "serpent/functions.cpp"
#include "serpent/preprocess.cpp"
#include "serpent/rewriteutils.cpp"
#include "cpp/api.cpp"

View File

@ -0,0 +1,26 @@
#include <string>
#include "serpent/lllparser.h"
#include "serpent/bignum.h"
#include "serpent/util.h"
#include "serpent/tokenize.h"
#include "serpent/parser.h"
#include "serpent/compiler.h"
#include "cpp/api.h"
const char *compileGo(char *code, int *err)
{
try {
std::string c = binToHex(compile(std::string(code)));
return c.c_str();
}
catch(std::string &error) {
*err = 1;
return error.c_str();
}
catch(...) {
return "Unknown error";
}
}

View File

@ -0,0 +1,14 @@
#ifndef CPP_API_H
#define CPP_API_H
#ifdef __cplusplus
extern "C" {
#endif
const char *compileGo(char *code, int *err);
#ifdef __cplusplus
}
#endif
#endif

View File

@ -0,0 +1,27 @@
package serpent
// #cgo CXXFLAGS: -I. -Ilangs/ -std=c++0x -Wall -fno-strict-aliasing
// #cgo LDFLAGS: -lstdc++
//
// #include "cpp/api.h"
//
import "C"
import (
"encoding/hex"
"errors"
"unsafe"
)
func Compile(str string) ([]byte, error) {
var err C.int
out := C.GoString(C.compileGo(C.CString(str), (*C.int)(unsafe.Pointer(&err))))
if err == C.int(1) {
return nil, errors.New(out)
}
bytes, _ := hex.DecodeString(out)
return bytes, nil
}

View File

@ -0,0 +1,12 @@
[._]*.s[a-w][a-z]
[._]s[a-w][a-z]
*.un~
Session.vim
.netrwhist
*~
*.o
serpent
libserpent.a
pyserpent.so
dist
*.egg-info

View File

@ -0,0 +1,5 @@
include *.cpp
include *.h
include *py
include README.md
include Makefile

View File

@ -0,0 +1,55 @@
PLATFORM_OPTS =
PYTHON = /usr/include/python2.7
CXXFLAGS = -fPIC
# -g3 -O0
BOOST_INC = /usr/include
BOOST_LIB = /usr/lib
TARGET = pyserpent
COMMON_OBJS = bignum.o util.o tokenize.o lllparser.o parser.o opcodes.o optimize.o functions.o rewriteutils.o preprocess.o rewriter.o compiler.o funcs.o
HEADERS = bignum.h util.h tokenize.h lllparser.h parser.h opcodes.h functions.h optimize.h rewriteutils.h preprocess.h rewriter.h compiler.h funcs.h
PYTHON_VERSION = 2.7
serpent : serpentc lib
lib:
ar rvs libserpent.a $(COMMON_OBJS)
g++ $(CXXFLAGS) -shared $(COMMON_OBJS) -o libserpent.so
serpentc: $(COMMON_OBJS) cmdline.o
rm -rf serpent
g++ -Wall $(COMMON_OBJS) cmdline.o -o serpent
bignum.o : bignum.cpp bignum.h
opcodes.o : opcodes.cpp opcodes.h
util.o : util.cpp util.h bignum.o
tokenize.o : tokenize.cpp tokenize.h util.o
lllparser.o : lllparser.cpp lllparser.h tokenize.o util.o
parser.o : parser.cpp parser.h tokenize.o util.o
rewriter.o : rewriter.cpp rewriter.h lllparser.o util.o rewriteutils.o preprocess.o opcodes.o functions.o
preprocessor.o: rewriteutils.o functions.o
compiler.o : compiler.cpp compiler.h util.o
funcs.o : funcs.cpp funcs.h
cmdline.o: cmdline.cpp
pyext.o: pyext.cpp
clean:
rm -f serpent *\.o libserpent.a libserpent.so
install:
cp serpent /usr/local/bin
cp libserpent.a /usr/local/lib
cp libserpent.so /usr/local/lib
rm -rf /usr/local/include/libserpent
mkdir -p /usr/local/include/libserpent
cp $(HEADERS) /usr/local/include/libserpent

View File

@ -0,0 +1,3 @@
Installation:
```make && sudo make install```

View File

@ -0,0 +1,112 @@
#include <stdio.h>
#include <iostream>
#include <vector>
#include <map>
#include "bignum.h"
//Integer to string conversion
std::string unsignedToDecimal(unsigned branch) {
if (branch < 10) return nums.substr(branch, 1);
else return unsignedToDecimal(branch / 10) + nums.substr(branch % 10,1);
}
//Add two strings representing decimal values
std::string decimalAdd(std::string a, std::string b) {
std::string o = a;
while (b.length() < a.length()) b = "0" + b;
while (o.length() < b.length()) o = "0" + o;
bool carry = false;
for (int i = o.length() - 1; i >= 0; i--) {
o[i] = o[i] + b[i] - '0';
if (carry) o[i]++;
if (o[i] > '9') {
o[i] -= 10;
carry = true;
}
else carry = false;
}
if (carry) o = "1" + o;
return o;
}
//Helper function for decimalMul
std::string decimalDigitMul(std::string a, int dig) {
if (dig == 0) return "0";
else return decimalAdd(a, decimalDigitMul(a, dig - 1));
}
//Multiply two strings representing decimal values
std::string decimalMul(std::string a, std::string b) {
std::string o = "0";
for (unsigned i = 0; i < b.length(); i++) {
std::string n = decimalDigitMul(a, b[i] - '0');
if (n != "0") {
for (unsigned j = i + 1; j < b.length(); j++) n += "0";
}
o = decimalAdd(o, n);
}
return o;
}
//Modexp
std::string decimalModExp(std::string b, std::string e, std::string m) {
if (e == "0") return "1";
else if (e == "1") return b;
else if (decimalMod(e, "2") == "0") {
std::string o = decimalModExp(b, decimalDiv(e, "2"), m);
return decimalMod(decimalMul(o, o), m);
}
else {
std::string o = decimalModExp(b, decimalDiv(e, "2"), m);
return decimalMod(decimalMul(decimalMul(o, o), b), m);
}
}
//Is a greater than b? Flag allows equality
bool decimalGt(std::string a, std::string b, bool eqAllowed) {
if (a == b) return eqAllowed;
return (a.length() > b.length()) || (a.length() >= b.length() && a > b);
}
//Subtract the two strings representing decimal values
std::string decimalSub(std::string a, std::string b) {
if (b == "0") return a;
if (b == a) return "0";
while (b.length() < a.length()) b = "0" + b;
std::string c = b;
for (unsigned i = 0; i < c.length(); i++) c[i] = '0' + ('9' - c[i]);
std::string o = decimalAdd(decimalAdd(a, c).substr(1), "1");
while (o.size() > 1 && o[0] == '0') o = o.substr(1);
return o;
}
//Divide the two strings representing decimal values
std::string decimalDiv(std::string a, std::string b) {
std::string c = b;
if (decimalGt(c, a)) return "0";
int zeroes = -1;
while (decimalGt(a, c, true)) {
zeroes += 1;
c = c + "0";
}
c = c.substr(0, c.size() - 1);
std::string quot = "0";
while (decimalGt(a, c, true)) {
a = decimalSub(a, c);
quot = decimalAdd(quot, "1");
}
for (int i = 0; i < zeroes; i++) quot += "0";
return decimalAdd(quot, decimalDiv(a, b));
}
//Modulo the two strings representing decimal values
std::string decimalMod(std::string a, std::string b) {
return decimalSub(a, decimalMul(decimalDiv(a, b), b));
}
//String to int conversion
unsigned decimalToUnsigned(std::string a) {
if (a.size() == 0) return 0;
else return (a[a.size() - 1] - '0')
+ decimalToUnsigned(a.substr(0,a.size()-1)) * 10;
}

View File

@ -0,0 +1,41 @@
#ifndef ETHSERP_BIGNUM
#define ETHSERP_BIGNUM
const std::string nums = "0123456789";
const std::string tt256 =
"115792089237316195423570985008687907853269984665640564039457584007913129639936"
;
const std::string tt256m1 =
"115792089237316195423570985008687907853269984665640564039457584007913129639935"
;
const std::string tt255 =
"57896044618658097711785492504343953926634992332820282019728792003956564819968";
const std::string tt176 =
"95780971304118053647396689196894323976171195136475136";
std::string unsignedToDecimal(unsigned branch);
std::string decimalAdd(std::string a, std::string b);
std::string decimalMul(std::string a, std::string b);
std::string decimalSub(std::string a, std::string b);
std::string decimalDiv(std::string a, std::string b);
std::string decimalMod(std::string a, std::string b);
std::string decimalModExp(std::string b, std::string e, std::string m);
bool decimalGt(std::string a, std::string b, bool eqAllowed=false);
unsigned decimalToUnsigned(std::string a);
#define utd unsignedToDecimal
#define dtu decimalToUnsigned
#endif

View File

@ -0,0 +1,132 @@
#include <stdio.h>
#include <string>
#include <iostream>
#include <vector>
#include <map>
#include "funcs.h"
int main(int argv, char** argc) {
if (argv == 1) {
std::cerr << "Must provide a command and arguments! Try parse, rewrite, compile, assemble\n";
return 0;
}
if (argv == 2 && std::string(argc[1]) == "--help" || std::string(argc[1]) == "-h" ) {
std::cout << argc[1] << "\n";
std::cout << "serpent command input\n";
std::cout << "where input -s for from stdin, a file, or interpreted as serpent code if does not exist as file.";
std::cout << "where command: \n";
std::cout << " parse: Just parses and returns s-expression code.\n";
std::cout << " rewrite: Parse, use rewrite rules print s-expressions of result.\n";
std::cout << " compile: Return resulting compiled EVM code in hex.\n";
std::cout << " assemble: Return result from step before compilation.\n";
return 0;
}
std::string flag = "";
std::string command = argc[1];
std::string input;
std::string secondInput;
if (std::string(argc[1]) == "-s") {
flag = command.substr(1);
command = argc[2];
input = "";
std::string line;
while (std::getline(std::cin, line)) {
input += line + "\n";
}
secondInput = argv == 3 ? "" : argc[3];
}
else {
if (argv == 2) {
std::cerr << "Not enough arguments for serpent cmdline\n";
throw(0);
}
input = argc[2];
secondInput = argv == 3 ? "" : argc[3];
}
bool haveSec = secondInput.length() > 0;
if (command == "parse" || command == "parse_serpent") {
std::cout << printAST(parseSerpent(input), haveSec) << "\n";
}
else if (command == "rewrite") {
std::cout << printAST(rewrite(parseLLL(input, true)), haveSec) << "\n";
}
else if (command == "compile_to_lll") {
std::cout << printAST(compileToLLL(input), haveSec) << "\n";
}
else if (command == "rewrite_chunk") {
std::cout << printAST(rewriteChunk(parseLLL(input, true)), haveSec) << "\n";
}
else if (command == "compile_chunk_to_lll") {
std::cout << printAST(compileChunkToLLL(input), haveSec) << "\n";
}
else if (command == "build_fragtree") {
std::cout << printAST(buildFragmentTree(parseLLL(input, true))) << "\n";
}
else if (command == "compile_lll") {
std::cout << binToHex(compileLLL(parseLLL(input, true))) << "\n";
}
else if (command == "dereference") {
std::cout << printAST(dereference(parseLLL(input, true)), haveSec) <<"\n";
}
else if (command == "pretty_assemble") {
std::cout << printTokens(prettyAssemble(parseLLL(input, true))) <<"\n";
}
else if (command == "pretty_compile_lll") {
std::cout << printTokens(prettyCompileLLL(parseLLL(input, true))) << "\n";
}
else if (command == "pretty_compile") {
std::cout << printTokens(prettyCompile(input)) << "\n";
}
else if (command == "pretty_compile_chunk") {
std::cout << printTokens(prettyCompileChunk(input)) << "\n";
}
else if (command == "assemble") {
std::cout << assemble(parseLLL(input, true)) << "\n";
}
else if (command == "serialize") {
std::cout << binToHex(serialize(tokenize(input, Metadata(), false))) << "\n";
}
else if (command == "flatten") {
std::cout << printTokens(flatten(parseLLL(input, true))) << "\n";
}
else if (command == "deserialize") {
std::cout << printTokens(deserialize(hexToBin(input))) << "\n";
}
else if (command == "compile") {
std::cout << binToHex(compile(input)) << "\n";
}
else if (command == "compile_chunk") {
std::cout << binToHex(compileChunk(input)) << "\n";
}
else if (command == "encode_datalist") {
std::vector<Node> tokens = tokenize(input);
std::vector<std::string> o;
for (int i = 0; i < (int)tokens.size(); i++) {
o.push_back(tokens[i].val);
}
std::cout << binToHex(encodeDatalist(o)) << "\n";
}
else if (command == "decode_datalist") {
std::vector<std::string> o = decodeDatalist(hexToBin(input));
std::vector<Node> tokens;
for (int i = 0; i < (int)o.size(); i++)
tokens.push_back(token(o[i]));
std::cout << printTokens(tokens) << "\n";
}
else if (command == "tokenize") {
std::cout << printTokens(tokenize(input));
}
else if (command == "biject") {
if (argv == 3)
std::cerr << "Not enough arguments for biject\n";
int pos = decimalToUnsigned(secondInput);
std::vector<Node> n = prettyCompile(input);
if (pos >= (int)n.size())
std::cerr << "Code position too high\n";
Metadata m = n[pos].metadata;
std::cout << "Opcode: " << n[pos].val << ", file: " << m.file <<
", line: " << m.ln << ", char: " << m.ch << "\n";
}
}

View File

@ -0,0 +1,554 @@
#include <stdio.h>
#include <iostream>
#include <vector>
#include <map>
#include "util.h"
#include "bignum.h"
#include "opcodes.h"
struct programAux {
std::map<std::string, std::string> vars;
int nextVarMem;
bool allocUsed;
bool calldataUsed;
int step;
int labelLength;
};
struct programVerticalAux {
int height;
std::string innerScopeName;
std::map<std::string, int> dupvars;
std::map<std::string, int> funvars;
std::vector<mss> scopes;
};
struct programData {
programAux aux;
Node code;
int outs;
};
programAux Aux() {
programAux o;
o.allocUsed = false;
o.calldataUsed = false;
o.step = 0;
o.nextVarMem = 32;
return o;
}
programVerticalAux verticalAux() {
programVerticalAux o;
o.height = 0;
o.dupvars = std::map<std::string, int>();
o.funvars = std::map<std::string, int>();
o.scopes = std::vector<mss>();
return o;
}
programData pd(programAux aux = Aux(), Node code=token("_"), int outs=0) {
programData o;
o.aux = aux;
o.code = code;
o.outs = outs;
return o;
}
Node multiToken(Node nodes[], int len, Metadata met) {
std::vector<Node> out;
for (int i = 0; i < len; i++) {
out.push_back(nodes[i]);
}
return astnode("_", out, met);
}
Node finalize(programData c);
Node popwrap(Node node) {
Node nodelist[] = {
node,
token("POP", node.metadata)
};
return multiToken(nodelist, 2, node.metadata);
}
// Grabs variables
mss getVariables(Node node, mss cur=mss()) {
Metadata m = node.metadata;
// Tokens don't contain any variables
if (node.type == TOKEN)
return cur;
// Don't descend into call fragments
else if (node.val == "lll")
return getVariables(node.args[1], cur);
// At global scope get/set/ref also declare
else if (node.val == "get" || node.val == "set" || node.val == "ref") {
if (node.args[0].type != TOKEN)
err("Variable name must be simple token,"
" not complex expression!", m);
if (!cur.count(node.args[0].val)) {
cur[node.args[0].val] = utd(cur.size() * 32 + 32);
//std::cerr << node.args[0].val << " " << cur[node.args[0].val] << "\n";
}
}
// Recursively process children
for (unsigned i = 0; i < node.args.size(); i++) {
cur = getVariables(node.args[i], cur);
}
return cur;
}
// Turns LLL tree into tree of code fragments
programData opcodeify(Node node,
programAux aux=Aux(),
programVerticalAux vaux=verticalAux()) {
std::string symb = "_"+mkUniqueToken();
Metadata m = node.metadata;
// Get variables
if (!aux.vars.size()) {
aux.vars = getVariables(node);
aux.nextVarMem = aux.vars.size() * 32 + 32;
}
// Numbers
if (node.type == TOKEN) {
return pd(aux, nodeToNumeric(node), 1);
}
else if (node.val == "ref" || node.val == "get" || node.val == "set") {
std::string varname = node.args[0].val;
// Determine reference to variable
Node varNode = tkn(aux.vars[varname], m);
//std::cerr << varname << " " << printSimple(varNode) << "\n";
// Set variable
if (node.val == "set") {
programData sub = opcodeify(node.args[1], aux, vaux);
if (!sub.outs)
err("Value to set variable must have nonzero arity!", m);
// What if we are setting a stack variable?
if (vaux.dupvars.count(node.args[0].val)) {
int h = vaux.height - vaux.dupvars[node.args[0].val];
if (h > 16) err("Too deep for stack variable (max 16)", m);
Node nodelist[] = {
sub.code,
token("SWAP"+unsignedToDecimal(h), m),
token("POP", m)
};
return pd(sub.aux, multiToken(nodelist, 3, m), 0);
}
// Setting a memory variable
else {
Node nodelist[] = {
sub.code,
varNode,
token("MSTORE", m),
};
return pd(sub.aux, multiToken(nodelist, 3, m), 0);
}
}
// Get variable
else if (node.val == "get") {
// Getting a stack variable
if (vaux.dupvars.count(node.args[0].val)) {
int h = vaux.height - vaux.dupvars[node.args[0].val];
if (h > 16) err("Too deep for stack variable (max 16)", m);
return pd(aux, token("DUP"+unsignedToDecimal(h)), 1);
}
// Getting a memory variable
else {
Node nodelist[] =
{ varNode, token("MLOAD", m) };
return pd(aux, multiToken(nodelist, 2, m), 1);
}
}
// Refer variable
else if (node.val == "ref") {
if (vaux.dupvars.count(node.args[0].val))
err("Cannot ref stack variable!", m);
return pd(aux, varNode, 1);
}
}
// Comments do nothing
else if (node.val == "comment") {
Node nodelist[] = { };
return pd(aux, multiToken(nodelist, 0, m), 0);
}
// Custom operation sequence
// eg. (ops bytez id msize swap1 msize add 0 swap1 mstore) == alloc
if (node.val == "ops") {
std::vector<Node> subs2;
int depth = 0;
for (unsigned i = 0; i < node.args.size(); i++) {
std::string op = upperCase(node.args[i].val);
if (node.args[i].type == ASTNODE || opinputs(op) == -1) {
programVerticalAux vaux2 = vaux;
vaux2.height = vaux.height - i - 1 + node.args.size();
programData sub = opcodeify(node.args[i], aux, vaux2);
aux = sub.aux;
depth += sub.outs;
subs2.push_back(sub.code);
}
else {
subs2.push_back(token(op, m));
depth += opoutputs(op) - opinputs(op);
}
}
if (depth < 0 || depth > 1) err("Stack depth mismatch", m);
return pd(aux, astnode("_", subs2, m), 0);
}
// Code blocks
if (node.val == "lll" && node.args.size() == 2) {
if (node.args[1].val != "0") aux.allocUsed = true;
std::vector<Node> o;
o.push_back(finalize(opcodeify(node.args[0])));
programData sub = opcodeify(node.args[1], aux, vaux);
Node code = astnode("____CODE", o, m);
Node nodelist[] = {
token("$begincode"+symb+".endcode"+symb, m), token("DUP1", m),
token("$begincode"+symb, m), sub.code, token("CODECOPY", m),
token("$endcode"+symb, m), token("JUMP", m),
token("~begincode"+symb, m), code,
token("~endcode"+symb, m), token("JUMPDEST", m)
};
return pd(sub.aux, multiToken(nodelist, 11, m), 1);
}
// Stack variables
if (node.val == "with") {
programData initial = opcodeify(node.args[1], aux, vaux);
programVerticalAux vaux2 = vaux;
vaux2.dupvars[node.args[0].val] = vaux.height;
vaux2.height += 1;
if (!initial.outs)
err("Initial variable value must have nonzero arity!", m);
programData sub = opcodeify(node.args[2], initial.aux, vaux2);
Node nodelist[] = {
initial.code,
sub.code
};
programData o = pd(sub.aux, multiToken(nodelist, 2, m), sub.outs);
if (sub.outs)
o.code.args.push_back(token("SWAP1", m));
o.code.args.push_back(token("POP", m));
return o;
}
// Seq of multiple statements
if (node.val == "seq") {
std::vector<Node> children;
int lastOut = 0;
for (unsigned i = 0; i < node.args.size(); i++) {
programData sub = opcodeify(node.args[i], aux, vaux);
aux = sub.aux;
if (sub.outs == 1) {
if (i < node.args.size() - 1) sub.code = popwrap(sub.code);
else lastOut = 1;
}
children.push_back(sub.code);
}
return pd(aux, astnode("_", children, m), lastOut);
}
// 2-part conditional (if gets rewritten to unless in rewrites)
else if (node.val == "unless" && node.args.size() == 2) {
programData cond = opcodeify(node.args[0], aux, vaux);
programData action = opcodeify(node.args[1], cond.aux, vaux);
aux = action.aux;
if (!cond.outs) err("Condition of if/unless statement has arity 0", m);
if (action.outs) action.code = popwrap(action.code);
Node nodelist[] = {
cond.code,
token("$endif"+symb, m), token("JUMPI", m),
action.code,
token("~endif"+symb, m), token("JUMPDEST", m)
};
return pd(aux, multiToken(nodelist, 6, m), 0);
}
// 3-part conditional
else if (node.val == "if" && node.args.size() == 3) {
programData ifd = opcodeify(node.args[0], aux, vaux);
programData thend = opcodeify(node.args[1], ifd.aux, vaux);
programData elsed = opcodeify(node.args[2], thend.aux, vaux);
aux = elsed.aux;
if (!ifd.outs)
err("Condition of if/unless statement has arity 0", m);
// Handle cases where one conditional outputs something
// and the other does not
int outs = (thend.outs && elsed.outs) ? 1 : 0;
if (thend.outs > outs) thend.code = popwrap(thend.code);
if (elsed.outs > outs) elsed.code = popwrap(elsed.code);
Node nodelist[] = {
ifd.code,
token("ISZERO", m),
token("$else"+symb, m), token("JUMPI", m),
thend.code,
token("$endif"+symb, m), token("JUMP", m),
token("~else"+symb, m), token("JUMPDEST", m),
elsed.code,
token("~endif"+symb, m), token("JUMPDEST", m)
};
return pd(aux, multiToken(nodelist, 12, m), outs);
}
// While (rewritten to this in rewrites)
else if (node.val == "until") {
programData cond = opcodeify(node.args[0], aux, vaux);
programData action = opcodeify(node.args[1], cond.aux, vaux);
aux = action.aux;
if (!cond.outs)
err("Condition of while/until loop has arity 0", m);
if (action.outs) action.code = popwrap(action.code);
Node nodelist[] = {
token("~beg"+symb, m), token("JUMPDEST", m),
cond.code,
token("$end"+symb, m), token("JUMPI", m),
action.code,
token("$beg"+symb, m), token("JUMP", m),
token("~end"+symb, m), token("JUMPDEST", m),
};
return pd(aux, multiToken(nodelist, 10, m));
}
// Memory allocations
else if (node.val == "alloc") {
programData bytez = opcodeify(node.args[0], aux, vaux);
aux = bytez.aux;
if (!bytez.outs)
err("Alloc input has arity 0", m);
aux.allocUsed = true;
Node nodelist[] = {
bytez.code,
token("MSIZE", m), token("SWAP1", m), token("MSIZE", m),
token("ADD", m),
token("0", m), token("SWAP1", m), token("MSTORE", m)
};
return pd(aux, multiToken(nodelist, 8, m), 1);
}
// All other functions/operators
else {
std::vector<Node> subs2;
int depth = opinputs(upperCase(node.val));
if (depth == -1)
err("Not a function or opcode: "+node.val, m);
if ((int)node.args.size() != depth)
err("Invalid arity for "+node.val, m);
for (int i = node.args.size() - 1; i >= 0; i--) {
programVerticalAux vaux2 = vaux;
vaux2.height = vaux.height - i - 1 + node.args.size();
programData sub = opcodeify(node.args[i], aux, vaux2);
aux = sub.aux;
if (!sub.outs)
err("Input "+unsignedToDecimal(i)+" has arity 0", sub.code.metadata);
subs2.push_back(sub.code);
}
subs2.push_back(token(upperCase(node.val), m));
int outdepth = opoutputs(upperCase(node.val));
return pd(aux, astnode("_", subs2, m), outdepth);
}
}
// Adds necessary wrappers to a program
Node finalize(programData c) {
std::vector<Node> bottom;
Metadata m = c.code.metadata;
// If we are using both alloc and variables, we need to pre-zfill
// some memory
if ((c.aux.allocUsed || c.aux.calldataUsed) && c.aux.vars.size() > 0) {
Node nodelist[] = {
token("0", m),
token(unsignedToDecimal(c.aux.nextVarMem - 1)),
token("MSTORE8", m)
};
bottom.push_back(multiToken(nodelist, 3, m));
}
// The actual code
bottom.push_back(c.code);
return astnode("_", bottom, m);
}
//LLL -> code fragment tree
Node buildFragmentTree(Node node) {
return finalize(opcodeify(node));
}
// Builds a dictionary mapping labels to variable names
programAux buildDict(Node program, programAux aux, int labelLength) {
Metadata m = program.metadata;
// Token
if (program.type == TOKEN) {
if (isNumberLike(program)) {
aux.step += 1 + toByteArr(program.val, m).size();
}
else if (program.val[0] == '~') {
aux.vars[program.val.substr(1)] = unsignedToDecimal(aux.step);
}
else if (program.val[0] == '$') {
aux.step += labelLength + 1;
}
else aux.step += 1;
}
// A sub-program (ie. LLL)
else if (program.val == "____CODE") {
programAux auks = Aux();
for (unsigned i = 0; i < program.args.size(); i++) {
auks = buildDict(program.args[i], auks, labelLength);
}
for (std::map<std::string,std::string>::iterator it=auks.vars.begin();
it != auks.vars.end();
it++) {
aux.vars[(*it).first] = (*it).second;
}
aux.step += auks.step;
}
// Normal sub-block
else {
for (unsigned i = 0; i < program.args.size(); i++) {
aux = buildDict(program.args[i], aux, labelLength);
}
}
return aux;
}
// Applies that dictionary
Node substDict(Node program, programAux aux, int labelLength) {
Metadata m = program.metadata;
std::vector<Node> out;
std::vector<Node> inner;
if (program.type == TOKEN) {
if (program.val[0] == '$') {
std::string tokStr = "PUSH"+unsignedToDecimal(labelLength);
out.push_back(token(tokStr, m));
int dotLoc = program.val.find('.');
if (dotLoc == -1) {
std::string val = aux.vars[program.val.substr(1)];
inner = toByteArr(val, m, labelLength);
}
else {
std::string start = aux.vars[program.val.substr(1, dotLoc-1)],
end = aux.vars[program.val.substr(dotLoc + 1)],
dist = decimalSub(end, start);
inner = toByteArr(dist, m, labelLength);
}
out.push_back(astnode("_", inner, m));
}
else if (program.val[0] == '~') { }
else if (isNumberLike(program)) {
inner = toByteArr(program.val, m);
out.push_back(token("PUSH"+unsignedToDecimal(inner.size())));
out.push_back(astnode("_", inner, m));
}
else return program;
}
else {
for (unsigned i = 0; i < program.args.size(); i++) {
Node n = substDict(program.args[i], aux, labelLength);
if (n.type == TOKEN || n.args.size()) out.push_back(n);
}
}
return astnode("_", out, m);
}
// Compiled fragtree -> compiled fragtree without labels
Node dereference(Node program) {
int sz = treeSize(program) * 4;
int labelLength = 1;
while (sz >= 256) { labelLength += 1; sz /= 256; }
programAux aux = buildDict(program, Aux(), labelLength);
return substDict(program, aux, labelLength);
}
// Dereferenced fragtree -> opcodes
std::vector<Node> flatten(Node derefed) {
std::vector<Node> o;
if (derefed.type == TOKEN) {
o.push_back(derefed);
}
else {
for (unsigned i = 0; i < derefed.args.size(); i++) {
std::vector<Node> oprime = flatten(derefed.args[i]);
for (unsigned j = 0; j < oprime.size(); j++) o.push_back(oprime[j]);
}
}
return o;
}
// Opcodes -> bin
std::string serialize(std::vector<Node> codons) {
std::string o;
for (unsigned i = 0; i < codons.size(); i++) {
int v;
if (isNumberLike(codons[i])) {
v = decimalToUnsigned(codons[i].val);
}
else if (codons[i].val.substr(0,4) == "PUSH") {
v = 95 + decimalToUnsigned(codons[i].val.substr(4));
}
else {
v = opcode(codons[i].val);
}
o += (char)v;
}
return o;
}
// Bin -> opcodes
std::vector<Node> deserialize(std::string ser) {
std::vector<Node> o;
int backCount = 0;
for (unsigned i = 0; i < ser.length(); i++) {
unsigned char v = (unsigned char)ser[i];
std::string oper = op((int)v);
if (oper != "" && backCount <= 0) o.push_back(token(oper));
else if (v >= 96 && v < 128 && backCount <= 0) {
o.push_back(token("PUSH"+unsignedToDecimal(v - 95)));
}
else o.push_back(token(unsignedToDecimal(v)));
if (v >= 96 && v < 128 && backCount <= 0) {
backCount = v - 95;
}
else backCount--;
}
return o;
}
// Fragtree -> bin
std::string assemble(Node fragTree) {
return serialize(flatten(dereference(fragTree)));
}
// Fragtree -> tokens
std::vector<Node> prettyAssemble(Node fragTree) {
return flatten(dereference(fragTree));
}
// LLL -> bin
std::string compileLLL(Node program) {
return assemble(buildFragmentTree(program));
}
// LLL -> tokens
std::vector<Node> prettyCompileLLL(Node program) {
return prettyAssemble(buildFragmentTree(program));
}
// Converts a list of integer values to binary transaction data
std::string encodeDatalist(std::vector<std::string> vals) {
std::string o;
for (unsigned i = 0; i < vals.size(); i++) {
std::vector<Node> n = toByteArr(strToNumeric(vals[i]), Metadata(), 32);
for (unsigned j = 0; j < n.size(); j++) {
int v = decimalToUnsigned(n[j].val);
o += (char)v;
}
}
return o;
}
// Converts binary transaction data into a list of integer values
std::vector<std::string> decodeDatalist(std::string ser) {
std::vector<std::string> out;
for (unsigned i = 0; i < ser.length(); i+= 32) {
std::string o = "0";
for (unsigned j = i; j < i + 32; j++) {
int vj = (int)(unsigned char)ser[j];
o = decimalAdd(decimalMul(o, "256"), unsignedToDecimal(vj));
}
out.push_back(o);
}
return out;
}

View File

@ -0,0 +1,43 @@
#ifndef ETHSERP_COMPILER
#define ETHSERP_COMPILER
#include <stdio.h>
#include <iostream>
#include <vector>
#include <map>
#include "util.h"
// Compiled fragtree -> compiled fragtree without labels
Node dereference(Node program);
// LLL -> fragtree
Node buildFragmentTree(Node program);
// Dereferenced fragtree -> opcodes
std::vector<Node> flatten(Node derefed);
// opcodes -> bin
std::string serialize(std::vector<Node> codons);
// Fragtree -> bin
std::string assemble(Node fragTree);
// Fragtree -> opcodes
std::vector<Node> prettyAssemble(Node fragTree);
// LLL -> bin
std::string compileLLL(Node program);
// LLL -> opcodes
std::vector<Node> prettyCompileLLL(Node program);
// bin -> opcodes
std::vector<Node> deserialize(std::string ser);
// Converts a list of integer values to binary transaction data
std::string encodeDatalist(std::vector<std::string> vals);
// Converts binary transaction data into a list of integer values
std::vector<std::string> decodeDatalist(std::string ser);
#endif

View File

@ -0,0 +1,11 @@
#include <libserpent/funcs.h>
#include <libserpent/bignum.h>
#include <iostream>
using namespace std;
int main() {
cout << printAST(compileToLLL(get_file_contents("examples/namecoin.se"))) << "\n";
cout << decimalSub("10234", "10234") << "\n";
cout << decimalSub("10234", "10233") << "\n";
}

View File

@ -0,0 +1,11 @@
x = msg.data[0]
steps = 0
while x > 1:
steps += 1
if (x % 2) == 0:
x /= 2
else:
x = 3 * x + 1
return(steps)

View File

@ -0,0 +1,274 @@
# Ethereum forks Counterparty in 340 lines of serpent
# Not yet tested
# assets[i] = a registered asset, assets[i].holders[j] = former or current i-holder
data assets[2^50](creator, name, calldate, callprice, dividend_paid, holders[2^50], holdersCount)
data nextAssetId
# holdersMap: holdersMap[addr][asset] = 1 if addr holds asset
data holdersMap[2^160][2^50]
# balances[x][y] = how much of y x holds
data balances[2^160][2^50]
# orders[a][b] = heap of indices to (c, d, e)
# = c offers to sell d units of a at a price of e units of b per 10^18 units
# of a
data orderbooks[2^50][2^50]
# store of general order data
data orders[2^50](seller, asset_sold, quantity, price)
data ordersCount
# data feeds
data feeds[2^50](owner, value)
data feedCount
# heap
data heap
extern heap: [register, push, pop, top, size]
data cfds[2^50](maker, acceptor, feed, asset, strike, leverage, min, max, maturity)
data cfdCount
data bets[2^50](maker, acceptor, feed, asset, makerstake, acceptorstake, eqtest, maturity)
data betCount
def init():
heap = create('heap.se')
# Add units (internal method)
def add(to, asset, value):
assert msg.sender == self
self.balances[to][asset] += value
# Add the holder to the holders list
if not self.holdersMap[to][asset]:
self.holdersMap[to][asset] = 1
c = self.assets[asset].holdersCount
self.assets[asset].holders[c] = to
self.assets[asset].holdersCount = c + 1
# Register a new asset
def register_asset(q, name, calldate, callprice):
newid = self.nextAssetId
self.assets[newid].creator = msg.sender
self.assets[newid].name = name
self.assets[newid].calldate = calldate
self.assets[newid].callprice = callprice
self.assets[newid].holders[0] = msg.sender
self.assets[newid].holdersCount = 1
self.balances[msg.sender][newid] = q
self.holdersMap[msg.sender][newid] = 1
# Send
def send(to, asset, value):
fromval = self.balances[msg.sender][asset]
if fromval >= value:
self.balances[msg.sender][asset] -= value
self.add(to, asset, value)
# Order
def mkorder(selling, buying, quantity, price):
# Make sure you have enough to pay for the order
assert self.balances[msg.sender][selling] >= quantity:
# Try to match existing orders
o = orderbooks[buying][selling]
if not o:
o = self.heap.register()
orderbooks[selling][buying] = o
sz = self.heap.size(o)
invprice = 10^36 / price
while quantity > 0 and sz > 0:
orderid = self.heap.pop()
p = self.orders[orderid].price
if p > invprice:
sz = 0
else:
q = self.orders[orderid].quantity
oq = min(q, quantity)
b = self.orders[orderid].seller
self.balances[msg.sender][selling] -= oq * p / 10^18
self.add(msg.sender, buying, oq)
self.add(b, selling, oq * p / 10^18)
self.orders[orderid].quantity = q - oq
if oq == q:
self.orders[orderid].seller = 0
self.orders[orderid].price = 0
self.orders[orderid].asset_sold = 0
quantity -= oq
sz -= 1
assert quantity > 0
# Make the order
c = self.ordersCount
self.orders[c].seller = msg.sender
self.orders[c].asset_sold = selling
self.orders[c].quantity = quantity
self.orders[c].price = price
self.ordersCount += 1
# Add it to the heap
o = orderbooks[selling][buying]
if not o:
o = self.heap.register()
orderbooks[selling][buying] = o
self.balances[msg.sender][selling] -= quantity
self.heap.push(o, price, c)
return(c)
def cancel_order(id):
if self.orders[id].seller == msg.sender:
self.orders[id].seller = 0
self.orders[id].price = 0
self.balances[msg.sender][self.orders[id].asset_sold] += self.orders[id].quantity
self.orders[id].quantity = 0
self.orders[id].asset_sold = 0
def register_feed():
c = self.feedCount
self.feeds[c].owner = msg.sender
self.feedCount = c + 1
return(c)
def set_feed(id, v):
if self.feeds[id].owner == msg.sender:
self.feeds[id].value = v
def mk_cfd_offer(feed, asset, strike, leverage, min, max, maturity):
b = self.balances[msg.sender][asset]
req = max((strike - min) * leverage, (strike - max) * leverage)
assert b >= req
self.balances[msg.sender][asset] = b - req
c = self.cfdCount
self.cfds[c].maker = msg.sender
self.cfds[c].feed = feed
self.cfds[c].asset = asset
self.cfds[c].strike = strike
self.cfds[c].leverage = leverage
self.cfds[c].min = min
self.cfds[c].max = max
self.cfds[c].maturity = maturity
self.cfdCount = c + 1
return(c)
def accept_cfd_offer(c):
assert not self.cfds[c].acceptor and self.cfds[c].maker
asset = self.cfds[c].asset
strike = self.cfds[c].strike
min = self.cfds[c].min
max = self.cfds[c].max
leverage = self.cfds[c].leverage
b = self.balances[msg.sender][asset]
req = max((min - strike) * leverage, (max - strike) * leverage)
assert b >= req
self.balances[msg.sender][asset] = b - req
self.cfds[c].acceptor = msg.sender
self.cfds[c].maturity += block.timestamp
def claim_cfd_offer(c):
asset = self.cfds[c].asset
strike = self.cfds[c].strike
min = self.cfds[c].min
max = self.cfds[c].max
leverage = self.cfds[c].leverage
v = self.feeds[self.cfds[c].feed].value
assert v <= min or v >= max or block.timestamp >= self.cfds[c].maturity
maker_req = max((strike - min) * leverage, (strike - max) * leverage)
acceptor_req = max((min - strike) * leverage, (max - strike) * leverage)
paydelta = (strike - v) * leverage
self.add(self.cfds[c].maker, asset, maker_req + paydelta)
self.add(self.cfds[c].acceptor, asset, acceptor_req - paydelta)
self.cfds[c].maker = 0
self.cfds[c].acceptor = 0
self.cfds[c].feed = 0
self.cfds[c].asset = 0
self.cfds[c].strike = 0
self.cfds[c].leverage = 0
self.cfds[c].min = 0
self.cfds[c].max = 0
self.cfds[c].maturity = 0
def withdraw_cfd_offer(c):
if self.cfds[c].maker == msg.sender and not self.cfds[c].acceptor:
asset = self.cfds[c].asset
strike = self.cfds[c].strike
min = self.cfds[c].min
max = self.cfds[c].max
leverage = self.cfds[c].leverage
maker_req = max((strike - min) * leverage, (strike - max) * leverage)
self.balances[self.cfds[c].maker][asset] += maker_req
self.cfds[c].maker = 0
self.cfds[c].acceptor = 0
self.cfds[c].feed = 0
self.cfds[c].asset = 0
self.cfds[c].strike = 0
self.cfds[c].leverage = 0
self.cfds[c].min = 0
self.cfds[c].max = 0
self.cfds[c].maturity = 0
def mk_bet_offer(feed, asset, makerstake, acceptorstake, eqtest, maturity):
assert self.balances[msg.sender][asset] >= makerstake
c = self.betCount
self.bets[c].maker = msg.sender
self.bets[c].feed = feed
self.bets[c].asset = asset
self.bets[c].makerstake = makerstake
self.bets[c].acceptorstake = acceptorstake
self.bets[c].eqtest = eqtest
self.bets[c].maturity = maturity
self.balances[msg.sender][asset] -= makerstake
self.betCount = c + 1
return(c)
def accept_bet_offer(c):
assert self.bets[c].maker and not self.bets[c].acceptor
asset = self.bets[c].asset
acceptorstake = self.bets[c].acceptorstake
assert self.balances[msg.sender][asset] >= acceptorstake
self.balances[msg.sender][asset] -= acceptorstake
self.bets[c].acceptor = msg.sender
def claim_bet_offer(c):
assert block.timestamp >= self.bets[c].maturity
v = self.feeds[self.bets[c].feed].value
totalstake = self.bets[c].makerstake + self.bets[c].acceptorstake
if v == self.bets[c].eqtest:
self.add(self.bets[c].maker, self.bets[c].asset, totalstake)
else:
self.add(self.bets[c].acceptor, self.bets[c].asset, totalstake)
self.bets[c].maker = 0
self.bets[c].feed = 0
self.bets[c].asset = 0
self.bets[c].makerstake = 0
self.bets[c].acceptorstake = 0
self.bets[c].eqtest = 0
self.bets[c].maturity = 0
def cancel_bet(c):
assert not self.bets[c].acceptor and msg.sender == self.bets[c].maker
self.balances[msg.sender][self.bets[c].asset] += self.bets[c].makerstake
self.bets[c].maker = 0
self.bets[c].feed = 0
self.bets[c].asset = 0
self.bets[c].makerstake = 0
self.bets[c].acceptorstake = 0
self.bets[c].eqtest = 0
self.bets[c].maturity = 0
def dividend(holder_asset, divvying_asset, ratio):
i = 0
sz = self.assets[holder_asset].holdersCount
t = 0
holders = array(sz)
payments = array(sz)
while i < sz:
holders[i] = self.assets[holder_asset].holders[i]
payments[i] = self.balances[holders[i]][holder_asset] * ratio / 10^18
t += payments[i]
i += 1
if self.balances[msg.sender][divvying_asset] >= t:
i = 0
while i < sz:
self.add(holders[i], divvying_asset, payments[i])
i += 1
self.balances[msg.sender][divvying_asset] -= t

View File

@ -0,0 +1,69 @@
data heaps[2^50](owner, size, nodes[2^50](key, value))
data heapIndex
def register():
i = self.heapIndex
self.heaps[i].owner = msg.sender
self.heapIndex = i + 1
return(i)
def push(heap, key, value):
assert msg.sender == self.heaps[heap].owner
sz = self.heaps[heap].size
self.heaps[heap].nodes[sz].key = key
self.heaps[heap].nodes[sz].value = value
k = sz + 1
while k > 1:
bottom = self.heaps[heap].nodes[k].key
top = self.heaps[heap].nodes[k/2].key
if bottom < top:
tvalue = self.heaps[heap].nodes[k/2].value
bvalue = self.heaps[heap].nodes[k].value
self.heaps[heap].nodes[k].key = top
self.heaps[heap].nodes[k].value = tvalue
self.heaps[heap].nodes[k/2].key = bottom
self.heaps[heap].nodes[k/2].value = bvalue
k /= 2
else:
k = 0
self.heaps[heap].size = sz + 1
def pop(heap):
sz = self.heaps[heap].size
assert sz
prevtop = self.heaps[heap].nodes[1].value
self.heaps[heap].nodes[1].key = self.heaps[heap].nodes[sz].key
self.heaps[heap].nodes[1].value = self.heaps[heap].nodes[sz].value
self.heaps[heap].nodes[sz].key = 0
self.heaps[heap].nodes[sz].value = 0
top = self.heaps[heap].nodes[1].key
k = 1
while k * 2 < sz:
bottom1 = self.heaps[heap].nodes[k * 2].key
bottom2 = self.heaps[heap].nodes[k * 2 + 1].key
if bottom1 < top and (bottom1 < bottom2 or k * 2 + 1 >= sz):
tvalue = self.heaps[heap].nodes[1].value
bvalue = self.heaps[heap].nodes[k * 2].value
self.heaps[heap].nodes[k].key = bottom1
self.heaps[heap].nodes[k].value = bvalue
self.heaps[heap].nodes[k * 2].key = top
self.heaps[heap].nodes[k * 2].value = tvalue
k = k * 2
elif bottom2 < top and bottom2 < bottom1 and k * 2 + 1 < sz:
tvalue = self.heaps[heap].nodes[1].value
bvalue = self.heaps[heap].nodes[k * 2 + 1].value
self.heaps[heap].nodes[k].key = bottom2
self.heaps[heap].nodes[k].value = bvalue
self.heaps[heap].nodes[k * 2 + 1].key = top
self.heaps[heap].nodes[k * 2 + 1].value = tvalue
k = k * 2 + 1
else:
k = sz
self.heaps[heap].size = sz - 1
return(prevtop)
def top(heap):
return(self.heaps[heap].nodes[1].value)
def size(heap):
return(self.heaps[heap].size)

View File

@ -0,0 +1,53 @@
data campaigns[2^80](recipient, goal, deadline, contrib_total, contrib_count, contribs[2^50](sender, value))
def create_campaign(id, recipient, goal, timelimit):
if self.campaigns[id].recipient:
return(0)
self.campaigns[id].recipient = recipient
self.campaigns[id].goal = goal
self.campaigns[id].deadline = block.timestamp + timelimit
def contribute(id):
# Update contribution total
total_contributed = self.campaigns[id].contrib_total + msg.value
self.campaigns[id].contrib_total = total_contributed
# Record new contribution
sub_index = self.campaigns[id].contrib_count
self.campaigns[id].contribs[sub_index].sender = msg.sender
self.campaigns[id].contribs[sub_index].value = msg.value
self.campaigns[id].contrib_count = sub_index + 1
# Enough funding?
if total_contributed >= self.campaigns[id].goal:
send(self.campaigns[id].recipient, total_contributed)
self.clear(id)
return(1)
# Expired?
if block.timestamp > self.campaigns[id].deadline:
i = 0
c = self.campaigns[id].contrib_count
while i < c:
send(self.campaigns[id].contribs[i].sender, self.campaigns[id].contribs[i].value)
i += 1
self.clear(id)
return(2)
def progress_report(id):
return(self.campaigns[id].contrib_total)
# Clearing function for internal use
def clear(id):
if self == msg.sender:
self.campaigns[id].recipient = 0
self.campaigns[id].goal = 0
self.campaigns[id].deadline = 0
c = self.campaigns[id].contrib_count
self.campaigns[id].contrib_count = 0
self.campaigns[id].contrib_total = 0
i = 0
while i < c:
self.campaigns[id].contribs[i].sender = 0
self.campaigns[id].contribs[i].value = 0
i += 1

View File

@ -0,0 +1,136 @@
# 0: current epoch
# 1: number of proposals
# 2: master currency
# 3: last winning market
# 4: last txid
# 5: long-term ema currency units purchased
# 6: last block when currency units purchased
# 7: ether allocated to last round
# 8: last block when currency units claimed
# 9: ether allocated to current round
# 1000+: [proposal address, market ID, totprice, totvolume]
init:
# We technically have two levels of epoch here. We have
# one epoch of 1000, to synchronize with the 1000 epoch
# of the market, and then 100 of those epochs make a
# meta-epoch (I'll nominate the term "seculum") over
# which the futarchy protocol will take place
contract.storage[0] = block.number / 1000
# The master currency of the futarchy. The futarchy will
# assign currency units to whoever the prediction market
# thinks will best increase the currency's value
master_currency = create('subcurrency.se')
contract.storage[2] = master_currency
code:
curepoch = block.number / 1000
prevepoch = contract.storage[0]
if curepoch > prevepoch:
if (curepoch % 100) > 50:
# Collect price data
# We take an average over 50 subepochs to determine
# the price of each asset, weighting by volume to
# prevent abuse
contract.storage[0] = curepoch
i = 0
numprop = contract.storage[1]
while i < numprop:
market = contract.storage[1001 + i * 4]
price = call(market, 2)
volume = call(market, 3)
contract.storage[1002 + i * 4] += price
contract.storage[1003 + i * 4] += volume * price
i += 1
if (curepoch / 100) > (prevepoch / 100):
# If we are entering a new seculum, we determine the
# market with the highest total average price
best = 0
bestmarket = 0
besti = 0
i = 0
while i < numprop:
curtotprice = contract.storage[1002 + i * 4]
curvolume = contract.storage[1002 + i * 4]
curavgprice = curtotprice / curvolume
if curavgprice > best:
best = curavgprice
besti = i
bestmarket = contract.storage[1003 + i * 4]
i += 1
# Reset the number of proposals to 0
contract.storage[1] = 0
# Reward the highest proposal
call(contract.storage[2], [best, 10^9, 0], 3)
# Record the winning market so we can later appropriately
# compensate the participants
contract.storage[2] = bestmarket
# The amount of ether allocated to the last round
contract.storage[7] = contract.storage[9]
# The amount of ether allocated to the next round
contract.storage[9] = contract.balance / 2
# Make a proposal [0, address]
if msg.data[0] == 0 and curepoch % 100 < 50:
pid = contract.storage[1]
market = create('market.se')
c1 = create('subcurrency.se')
c2 = create('subcurrency.se')
call(market, [c1, c2], 2)
contract.storage[1000 + pid * 4] = msg.data[1]
contract.storage[1001 + pid * 4] = market
contract.storage[1] += 1
# Claim ether [1, address]
# One unit of the first currency in the last round's winning
# market entitles you to a quantity of ether that was decided
# at the start of that epoch
elif msg.data[0] == 1:
first_subcurrency = call(contract.storage[2], 3)
# We ask the first subcurrency contract what the last transaction was. The
# way to make a claim is to send the amount of first currency units that
# you wish to claim with, and then immediately call this contract. For security
# it makes sense to set up a tx which sends both messages in sequence atomically
data = call(first_subcurrency, [], 0, 4)
from = data[0]
to = data[1]
value = data[2]
txid = data[3]
if txid > contract.storage[4] and to == contract.address:
send(to, contract.storage[7] * value / 10^9)
contract.storage[4] = txid
# Claim second currency [2, address]
# One unit of the second currency in the last round's winning
# market entitles you to one unit of the futarchy's master
# currency
elif msg.data[0] == 2:
second_subcurrency = call(contract.storage[2], 3)
data = call(first_subcurrency, [], 0, 4)
from = data[0]
to = data[1]
value = data[2]
txid = data[3]
if txid > contract.storage[4] and to == contract.address:
call(contract.storage[2], [to, value], 2)
contract.storage[4] = txid
# Purchase currency for ether (target releasing 10^9 units per seculum)
# Price starts off 1 eth for 10^9 units but increases hyperbolically to
# limit issuance
elif msg.data[0] == 3:
pre_ema = contract.storage[5]
post_ema = pre_ema + msg.value
pre_reserve = 10^18 / (10^9 + pre_ema / 10^9)
post_reserve = 10^18 / (10^9 + post_ema / 10^9)
call(contract.storage[2], [msg.sender, pre_reserve - post_reserve], 2)
last_sold = contract.storage[6]
contract.storage[5] = pre_ema * (100000 + last_sold - block.number) + msg.value
contract.storage[6] = block.number
# Claim all currencies as the ether miner of the current block
elif msg.data[0] == 2 and msg.sender == block.coinbase and block.number > contract.storage[8]:
i = 0
numproposals = contract.storage[1]
while i < numproposals:
market = contract.storage[1001 + i * 3]
fc = call(market, 4)
sc = call(market, 5)
call(fc, [msg.sender, 1000], 2)
call(sc, [msg.sender, 1000], 2)
i += 1
contract.storage[8] = block.number

View File

@ -0,0 +1,55 @@
# 0: size
# 1-n: elements
init:
contract.storage[1000] = msg.sender
code:
# Only owner of the heap is allowed to modify it
if contract.storage[1000] != msg.sender:
stop
# push
if msg.data[0] == 0:
sz = contract.storage[0]
contract.storage[sz + 1] = msg.data[1]
k = sz + 1
while k > 1:
bottom = contract.storage[k]
top = contract.storage[k/2]
if bottom < top:
contract.storage[k] = top
contract.storage[k/2] = bottom
k /= 2
else:
k = 0
contract.storage[0] = sz + 1
# pop
elif msg.data[0] == 1:
sz = contract.storage[0]
if !sz:
return(0)
prevtop = contract.storage[1]
contract.storage[1] = contract.storage[sz]
contract.storage[sz] = 0
top = contract.storage[1]
k = 1
while k * 2 < sz:
bottom1 = contract.storage[k * 2]
bottom2 = contract.storage[k * 2 + 1]
if bottom1 < top and (bottom1 < bottom2 or k * 2 + 1 >= sz):
contract.storage[k] = bottom1
contract.storage[k * 2] = top
k = k * 2
elif bottom2 < top and bottom2 < bottom1 and k * 2 + 1 < sz:
contract.storage[k] = bottom2
contract.storage[k * 2 + 1] = top
k = k * 2 + 1
else:
k = sz
contract.storage[0] = sz - 1
return(prevtop)
# top
elif msg.data[0] == 2:
return(contract.storage[1])
# size
elif msg.data[0] == 3:
return(contract.storage[0])

View File

@ -0,0 +1,117 @@
# Creates a decentralized market between any two subcurrencies
# Here, the first subcurrency is the base asset and the second
# subcurrency is the asset priced against the base asset. Hence,
# "buying" refers to trading the first for the second, and
# "selling" refers to trading the second for the first
# storage 0: buy orders
# storage 1: sell orders
# storage 1000: first subcurrency
# storage 1001: last first subcurrency txid
# storage 2000: second subcurrency
# storage 2001: last second subcurrency txid
# storage 3000: current epoch
# storage 4000: price
# storage 4001: volume
init:
# Heap for buy orders
contract.storage[0] = create('heap.se')
# Heap for sell orders
contract.storage[1] = create('heap.se')
code:
# Initialize with [ first_subcurrency, second_subcurrency ]
if !contract.storage[1000]:
contract.storage[1000] = msg.data[0] # First subcurrency
contract.storage[1001] = -1
contract.storage[2000] = msg.data[1] # Second subcurrency
contract.storage[2001] = -1
contract.storage[3000] = block.number / 1000
stop
first_subcurrency = contract.storage[1000]
second_subcurrency = contract.storage[2000]
buy_heap = contract.storage[0]
sell_heap = contract.storage[1]
# This contract operates in "epochs" of 100 blocks
# At the end of each epoch, we process all orders
# simultaneously, independent of order. This algorithm
# prevents front-running, and generates a profit from
# the spread. The profit is permanently kept in the
# market (ie. destroyed), making both subcurrencies
# more valuable
# Epoch transition code
if contract.storage[3000] < block.number / 100:
done = 0
volume = 0
while !done:
# Grab the top buy and sell order from each heap
topbuy = call(buy_heap, 1)
topsell = call(sell_heap, 1)
# An order is recorded in the heap as:
# Buys: (2^48 - 1 - price) * 2^208 + units of first currency * 2^160 + from
# Sells: price * 2^208 + units of second currency * 2^160 + from
buyprice = -(topbuy / 2^208)
buyfcvalue = (topbuy / 2^160) % 2^48
buyer = topbuy % 2^160
sellprice = topsell / 2^208
sellscvalue = (topsell / 2^160) % 2^48
seller = topsell % 2^160
# Heap empty, or no more matching orders
if not topbuy or not topsell or buyprice < sellprice:
done = 1
else:
# Add to volume counter
volume += buyfcvalue
# Calculate how much of the second currency the buyer gets, and
# how much of the first currency the seller gets
sellfcvalue = sellscvalue / buyprice
buyscvalue = buyfcvalue * sellprice
# Send the currency units along
call(second_subcurrency, [buyer, buyscvalue], 2)
call(first_subcurrency, [seller, sellfcvalue], 2)
if volume:
contract.storage[4000] = (buyprice + sellprice) / 2
contract.storage[4001] = volume
contract.storage[3000] = block.number / 100
# Make buy order [0, price]
if msg.data[0] == 0:
# We ask the first subcurrency contract what the last transaction was. The
# way to make a buy order is to send the amount of first currency units that
# you wish to buy with, and then immediately call this contract. For security
# it makes sense to set up a tx which sends both messages in sequence atomically
data = call(first_subcurrency, [], 0, 4)
from = data[0]
to = data[1]
value = data[2]
txid = data[3]
price = msg.data[1]
if txid > contract.storage[1001] and to == contract.address:
contract.storage[1001] = txid
# Adds the order to the heap
call(buy_heap, [0, -price * 2^208 + (value % 2^48) * 2^160 + from], 2)
# Make sell order [1, price]
elif msg.data[0] == 1:
# Same mechanics as buying
data = call(second_subcurrency, [], 0, 4)
from = data[0]
to = data[1]
value = data[2]
txid = data[3]
price = msg.data[1]
if txid > contract.storage[2001] and to == contract.address:
contract.storage[2001] = txid
call(sell_heap, [0, price * 2^208 + (value % 2^48) * 2^160 + from], 2)
# Ask for price
elif msg.data[0] == 2:
return(contract.storage[4000])
# Ask for volume
elif msg.data[0] == 3:
return(contract.storage[1000])
# Ask for first currency
elif msg.data[0] == 4:
return(contract.storage[2000])
# Ask for second currency
elif msg.data[0] == 5:
return(contract.storage[4001])

View File

@ -0,0 +1,35 @@
# Initialization
# Admin can issue and delete at will
init:
contract.storage[0] = msg.sender
code:
# If a message with one item is sent, that's a balance query
if msg.datasize == 1:
addr = msg.data[0]
return(contract.storage[addr])
# If a message with two items [to, value] are sent, that's a transfer request
elif msg.datasize == 2:
from = msg.sender
fromvalue = contract.storage[from]
to = msg.data[0]
value = msg.data[1]
if fromvalue >= value and value > 0 and to > 4:
contract.storage[from] = fromvalue - value
contract.storage[to] += value
contract.storage[2] = from
contract.storage[3] = to
contract.storage[4] = value
contract.storage[5] += 1
return(1)
return(0)
elif msg.datasize == 3 and msg.sender == contract.storage[0]:
# Admin can issue at will by sending a [to, value, 0] message
if msg.data[2] == 0:
contract.storage[msg.data[0]] += msg.data[1]
# Change admin [ newadmin, 0, 1 ]
# Set admin to 0 to disable administration
elif msg.data[2] == 1:
contract.storage[0] = msg.data[0]
# Fetch last transaction
else:
return([contract.storage[2], contract.storage[3], contract.storage[4], contract.storage[5]], 4)

View File

@ -0,0 +1,39 @@
from __future__ import print_function
import pyethereum
t = pyethereum.tester
s = t.state()
# Create currencies
c1 = s.contract('subcurrency.se')
print("First currency: %s" % c1)
c2 = s.contract('subcurrency.se')
print("First currency: %s" % c2)
# Allocate units
s.send(t.k0, c1, 0, [t.a0, 1000, 0])
s.send(t.k0, c1, 0, [t.a1, 1000, 0])
s.send(t.k0, c2, 0, [t.a2, 1000000, 0])
s.send(t.k0, c2, 0, [t.a3, 1000000, 0])
print("Allocated units")
# Market
m = s.contract('market.se')
s.send(t.k0, m, 0, [c1, c2])
# Place orders
s.send(t.k0, c1, 0, [m, 1000])
s.send(t.k0, m, 0, [0, 1200])
s.send(t.k1, c1, 0, [m, 1000])
s.send(t.k1, m, 0, [0, 1400])
s.send(t.k2, c2, 0, [m, 1000000])
s.send(t.k2, m, 0, [1, 800])
s.send(t.k3, c2, 0, [m, 1000000])
s.send(t.k3, m, 0, [1, 600])
print("Orders placed")
# Next epoch and ping
s.mine(100)
print("Mined 100")
s.send(t.k0, m, 0, [])
print("Updating")
# Check
assert s.send(t.k0, c2, 0, [t.a0]) == [800000]
assert s.send(t.k0, c2, 0, [t.a1]) == [600000]
assert s.send(t.k0, c1, 0, [t.a2]) == [833]
assert s.send(t.k0, c1, 0, [t.a3]) == [714]
print("Balance checks passed")

View File

@ -0,0 +1,12 @@
# Database updateable only by the original creator
data creator
def init():
self.creator = msg.sender
def update(k, v):
if msg.sender == self.creator:
self.storage[k] = v
def query(k):
return(self.storage[k])

View File

@ -0,0 +1,40 @@
# So I looked up on Wikipedia what Jacobian form actually is, and noticed that it's
# actually a rather different and more clever construction than the naive version
# that I created. It may possible to achieve a further 20-50% savings by applying
# that version.
extern all: [call]
data JORDANMUL
data JORDANADD
data EXP
def init():
self.JORDANMUL = create('jacobian_mul.se')
self.JORDANADD = create('jacobian_add.se')
self.EXP = create('modexp.se')
def call(h, v, r, s):
N = -432420386565659656852420866394968145599
P = -4294968273
h = mod(h, N)
r = mod(r, P)
s = mod(s, N)
Gx = 55066263022277343669578718895168534326250603453777594175500187360389116729240
Gy = 32670510020758816978083085130507043184471273380659243275938904335757337482424
x = r
xcubed = mulmod(mulmod(x, x, P), x, P)
beta = self.EXP.call(addmod(xcubed, 7, P), div(P + 1, 4), P)
# Static-gascost ghetto conditional
y_is_positive = mod(v, 2) xor mod(beta, 2)
y = beta * y_is_positive + (P - beta) * (1 - y_is_positive)
GZ = self.JORDANMUL.call(Gx, 1, Gy, 1, N - h, outsz=4)
XY = self.JORDANMUL.call(x, 1, y, 1, s, outsz=4)
COMB = self.JORDANADD.call(GZ[0], GZ[1], GZ[2], GZ[3], XY[0], XY[1], XY[2], XY[3], 1, outsz=5)
COMB[4] = self.EXP.call(r, N - 2, N)
Q = self.JORDANMUL.call(data=COMB, datasz=5, outsz=4)
ox = mulmod(Q[0], self.EXP.call(Q[1], P - 2, P), P)
oy = mulmod(Q[2], self.EXP.call(Q[3], P - 2, P), P)
return([ox, oy], 2)

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,32 @@
extern all: [call]
data DOUBLE
def init():
self.DOUBLE = create('jacobian_double.se')
def call(axn, axd, ayn, ayd, bxn, bxd, byn, byd):
if !axn and !ayn:
o = [bxn, bxd, byn, byd]
if !bxn and !byn:
o = [axn, axd, ayn, ayd]
if o:
return(o, 4)
with P = -4294968273:
if addmod(mulmod(axn, bxd, P), P - mulmod(axd, bxn, P), P) == 0:
if addmod(mulmod(ayn, byd, P), P - mulmod(ayd, byn, P), P) == 0:
return(self.DOUBLE.call(axn, axd, ayn, ayd, outsz=4), 4)
else:
return([0, 1, 0, 1], 4)
with mn = mulmod(addmod(mulmod(byn, ayd, P), P - mulmod(ayn, byd, P), P), mulmod(bxd, axd, P), P):
with md = mulmod(mulmod(byd, ayd, P), addmod(mulmod(bxn, axd, P), P - mulmod(axn, bxd, P), P), P):
with msqn = mulmod(mn, mn, P):
with msqd = mulmod(md, md, P):
with msqman = addmod(mulmod(msqn, axd, P), P - mulmod(msqd, axn, P), P):
with msqmad = mulmod(msqd, axd, P):
with xn = addmod(mulmod(msqman, bxd, P), P - mulmod(msqmad, bxn, P), P):
with xd = mulmod(msqmad, bxd, P):
with mamxn = mulmod(mn, addmod(mulmod(axn, xd, P), P - mulmod(xn, axd, P), P), P):
with mamxd = mulmod(md, mulmod(axd, xd, P), P):
with yn = addmod(mulmod(mamxn, ayd, P), P - mulmod(mamxd, ayn, P), P):
with yd = mulmod(mamxd, ayd, P):
return([xn, xd, yn, yd], 4)

View File

@ -0,0 +1,16 @@
def call(axn, axd, ayn, ayd):
if !axn and !ayn:
return([0, 1, 0, 1], 4)
with P = -4294968273:
# No need to add (A, 1) because A = 0 for bitcoin
with mn = mulmod(mulmod(mulmod(axn, axn, P), 3, P), ayd, P):
with md = mulmod(mulmod(axd, axd, P), mulmod(ayn, 2, P), P):
with msqn = mulmod(mn, mn, P):
with msqd = mulmod(md, md, P):
with xn = addmod(mulmod(msqn, axd, P), P - mulmod(msqd, mulmod(axn, 2, P), P), P):
with xd = mulmod(msqd, axd, P):
with mamxn = mulmod(addmod(mulmod(axn, xd, P), P - mulmod(axd, xn, P), P), mn, P):
with mamxd = mulmod(mulmod(axd, xd, P), md, P):
with yn = addmod(mulmod(mamxn, ayd, P), P - mulmod(mamxd, ayn, P), P):
with yd = mulmod(mamxd, ayd, P):
return([xn, xd, yn, yd], 4)

View File

@ -0,0 +1,37 @@
# Expected gas cost
#
# def expect(n, point_at_infinity=False):
# n = n % (2**256 - 432420386565659656852420866394968145599)
# if point_at_infinity:
# return 79
# if n == 0:
# return 34479
# L = int(1 + math.log(n) / math.log(2))
# H = len([x for x in b.encode(n, 2) if x == '1'])
# return 34221 + 94 * L + 343 * H
data DOUBLE
data ADD
def init():
self.DOUBLE = create('jacobian_double.se')
self.ADD = create('jacobian_add.se')
def call(axn, axd, ayn, ayd, n):
n = mod(n, -432420386565659656852420866394968145599)
if !axn * !ayn + !n: # Constant-gas version of !axn and !ayn or !n
return([0, 1, 0, 1], 4)
with o = [0, 0, 1, 0, 1, 0, 0, 0, 0]:
with b = 2 ^ 255:
while gt(b, 0):
if n & b:
~call(20000, self.DOUBLE, 0, o + 31, 129, o + 32, 128)
o[5] = axn
o[6] = axd
o[7] = ayn
o[8] = ayd
~call(20000, self.ADD, 0, o + 31, 257, o + 32, 128)
else:
~call(20000, self.DOUBLE, 0, o + 31, 129, o + 32, 128)
b = div(b, 2)
return(o + 32, 4)

View File

@ -0,0 +1,11 @@
def call(b, e, m):
with o = 1:
with bit = 2 ^ 255:
while gt(bit, 0):
# A touch of loop unrolling for 20% efficiency gain
o = mulmod(mulmod(o, o, m), b ^ !(!(e & bit)), m)
o = mulmod(mulmod(o, o, m), b ^ !(!(e & div(bit, 2))), m)
o = mulmod(mulmod(o, o, m), b ^ !(!(e & div(bit, 4))), m)
o = mulmod(mulmod(o, o, m), b ^ !(!(e & div(bit, 8))), m)
bit = div(bit, 16)
return(o)

View File

@ -0,0 +1,78 @@
import bitcoin as b
import math
import sys
def signed(o):
return map(lambda x: x - 2**256 if x >= 2**255 else x, o)
def hamming_weight(n):
return len([x for x in b.encode(n, 2) if x == '1'])
def binary_length(n):
return len(b.encode(n, 2))
def jacobian_mul_substitute(A, B, C, D, N):
if A == 0 and C == 0 or (N % b.N) == 0:
return {"gas": 86, "output": [0, 1, 0, 1]}
else:
output = b.jordan_multiply(((A, B), (C, D)), N)
return {
"gas": 35262 + 95 * binary_length(N % b.N) + 355 * hamming_weight(N % b.N),
"output": signed(list(output[0]) + list(output[1]))
}
def jacobian_add_substitute(A, B, C, D, E, F, G, H):
if A == 0 or E == 0:
gas = 149
elif (A * F - B * E) % b.P == 0:
if (C * H - D * G) % b.P == 0:
gas = 442
else:
gas = 177
else:
gas = 301
output = b.jordan_add(((A, B), (C, D)), ((E, F), (G, H)))
return {
"gas": gas,
"output": signed(list(output[0]) + list(output[1]))
}
def modexp_substitute(base, exp, mod):
return {
"gas": 5150,
"output": signed([pow(base, exp, mod) if mod > 0 else 0])
}
def ecrecover_substitute(z, v, r, s):
P, A, B, N, Gx, Gy = b.P, b.A, b.B, b.N, b.Gx, b.Gy
x = r
beta = pow(x*x*x+A*x+B, (P + 1) / 4, P)
BETA_PREMIUM = modexp_substitute(x, (P + 1) / 4, P)["gas"]
y = beta if v % 2 ^ beta % 2 else (P - beta)
Gz = b.jordan_multiply(((Gx, 1), (Gy, 1)), (N - z) % N)
GZ_PREMIUM = jacobian_mul_substitute(Gx, 1, Gy, 1, (N - z) % N)["gas"]
XY = b.jordan_multiply(((x, 1), (y, 1)), s)
XY_PREMIUM = jacobian_mul_substitute(x, 1, y, 1, s % N)["gas"]
Qr = b.jordan_add(Gz, XY)
QR_PREMIUM = jacobian_add_substitute(Gz[0][0], Gz[0][1], Gz[1][0], Gz[1][1],
XY[0][0], XY[0][1], XY[1][0], XY[1][1]
)["gas"]
Q = b.jordan_multiply(Qr, pow(r, N - 2, N))
Q_PREMIUM = jacobian_mul_substitute(Qr[0][0], Qr[0][1], Qr[1][0], Qr[1][1],
pow(r, N - 2, N))["gas"]
R_PREMIUM = modexp_substitute(r, N - 2, N)["gas"]
OX_PREMIUM = modexp_substitute(Q[0][1], P - 2, P)["gas"]
OY_PREMIUM = modexp_substitute(Q[1][1], P - 2, P)["gas"]
Q = b.from_jordan(Q)
return {
"gas": 991 + BETA_PREMIUM + GZ_PREMIUM + XY_PREMIUM + QR_PREMIUM +
Q_PREMIUM + R_PREMIUM + OX_PREMIUM + OY_PREMIUM,
"output": signed(Q)
}

View File

@ -0,0 +1,129 @@
import bitcoin as b
import random
import sys
import math
from pyethereum import tester as t
import substitutes
import time
vals = [random.randrange(2**256) for i in range(12)]
test_points = [list(p[0]) + list(p[1]) for p in
[b.jordan_multiply(((b.Gx, 1), (b.Gy, 1)), r) for r in vals]]
G = [b.Gx, 1, b.Gy, 1]
Z = [0, 1, 0, 1]
def neg_point(p):
return [p[0], b.P - p[1], p[2], b.P - p[3]]
s = t.state()
s.block.gas_limit = 10000000
t.gas_limit = 1000000
c = s.contract('modexp.se')
print "Starting modexp tests"
for i in range(0, len(vals) - 2, 3):
o1 = substitutes.modexp_substitute(vals[i], vals[i+1], vals[i+2])
o2 = s.profile(t.k0, c, 0, funid=0, abi=vals[i:i+3])
#assert o1["gas"] == o2["gas"], (o1, o2)
assert o1["output"] == o2["output"], (o1, o2)
c = s.contract('jacobian_add.se')
print "Starting addition tests"
for i in range(2):
P = test_points[i * 2]
Q = test_points[i * 2 + 1]
NP = neg_point(P)
o1 = substitutes.jacobian_add_substitute(*(P + Q))
o2 = s.profile(t.k0, c, 0, funid=0, abi=P + Q)
#assert o1["gas"] == o2["gas"], (o1, o2)
assert o1["output"] == o2["output"], (o1, o2)
o1 = substitutes.jacobian_add_substitute(*(P + NP))
o2 = s.profile(t.k0, c, 0, funid=0, abi=P + NP)
#assert o1["gas"] == o2["gas"], (o1, o2)
assert o1["output"] == o2["output"], (o1, o2)
o1 = substitutes.jacobian_add_substitute(*(P + P))
o2 = s.profile(t.k0, c, 0, funid=0, abi=P + P)
#assert o1["gas"] == o2["gas"], (o1, o2)
assert o1["output"] == o2["output"], (o1, o2)
o1 = substitutes.jacobian_add_substitute(*(P + Z))
o2 = s.profile(t.k0, c, 0, funid=0, abi=P + Z)
#assert o1["gas"] == o2["gas"], (o1, o2)
assert o1["output"] == o2["output"], (o1, o2)
o1 = substitutes.jacobian_add_substitute(*(Z + P))
o2 = s.profile(t.k0, c, 0, funid=0, abi=Z + P)
#assert o1["gas"] == o2["gas"], (o1, o2)
assert o1["output"] == o2["output"], (o1, o2)
c = s.contract('jacobian_mul.se')
print "Starting multiplication tests"
mul_tests = [
Z + [0],
Z + [vals[0]],
test_points[0] + [0],
test_points[1] + [b.N],
test_points[2] + [1],
test_points[2] + [2],
test_points[2] + [3],
test_points[2] + [4],
test_points[3] + [5],
test_points[3] + [6],
test_points[4] + [7],
test_points[4] + [2**254],
test_points[4] + [vals[1]],
test_points[4] + [vals[2]],
test_points[4] + [vals[3]],
test_points[5] + [2**256 - 1],
]
for i, test in enumerate(mul_tests):
print 'trying mul_test %i' % i, test
o1 = substitutes.jacobian_mul_substitute(*test)
o2 = s.profile(t.k0, c, 0, funid=0, abi=test)
# assert o1["gas"] == o2["gas"], (o1, o2, test)
assert o1["output"] == o2["output"], (o1, o2, test)
c = s.contract('ecrecover.se')
print "Starting ecrecover tests"
for i in range(5):
print 'trying ecrecover_test', vals[i*2], vals[i*2+1]
k = vals[i*2]
h = vals[i*2+1]
V, R, S = b.ecdsa_raw_sign(b.encode(h, 256, 32), k)
aa = time.time()
o1 = substitutes.ecrecover_substitute(h, V, R, S)
print 'sub', time.time() - aa
a = time.time()
o2 = s.profile(t.k0, c, 0, funid=0, abi=[h, V, R, S])
print time.time() - a
# assert o1["gas"] == o2["gas"], (o1, o2, h, V, R, S)
assert o1["output"] == o2["output"], (o1, o2, h, V, R, S)
# Explicit tests
data = [[
0xf007a9c78a4b2213220adaaf50c89a49d533fbefe09d52bbf9b0da55b0b90b60,
0x1b,
0x5228fc9e2fabfe470c32f459f4dc17ef6a0a81026e57e4d61abc3bc268fc92b5,
0x697d4221cd7bc5943b482173de95d3114b9f54c5f37cc7f02c6910c6dd8bd107
]]
for datum in data:
o1 = substitutes.ecrecover_substitute(*datum)
o2 = s.profile(t.k0, c, 0, funid=0, abi=datum)
#assert o1["gas"] == o2["gas"], (o1, o2, datum)
assert o1["output"] == o2["output"], (o1, o2, datum)

View File

@ -0,0 +1,45 @@
if msg.data[0] == 0:
new_id = contract.storage[-1]
# store [from, to, value, maxvalue, timeout] in contract storage
contract.storage[new_id] = msg.sender
contract.storage[new_id + 1] = msg.data[1]
contract.storage[new_id + 2] = 0
contract.storage[new_id + 3] = msg.value
contract.storage[new_id + 4] = 2^254
# increment next id
contract.storage[-1] = new_id + 10
# return id of this channel
return(new_id)
# Increase payment on channel: [1, id, value, v, r, s]
elif msg.data[0] == 1:
# Ecrecover native extension; will be a different address in testnet and live
ecrecover = 0x46a8d0b21b1336d83b06829f568d7450df36883f
# Message data parameters
id = msg.data[1] % 2^160
value = msg.data[2]
# Determine sender from signature
h = sha3([id, value], 2)
sender = call(ecrecover, [h, msg.data[3], msg.data[4], msg.data[5]], 4)
# Check sender matches and new value is greater than old
if sender == contract.storage[id]:
if value > contract.storage[id + 2] and value <= contract.storage[id + 3]:
# Update channel, increasing value and setting timeout
contract.storage[id + 2] = value
contract.storage[id + 4] = block.number + 1000
# Cash out channel: [2, id]
elif msg.data[0] == 2:
id = msg.data[1] % 2^160
# Check if timeout has run out
if block.number >= contract.storage[id + 3]:
# Send funds
send(contract.storage[id + 1], contract.storage[id + 2])
# Send refund
send(contract.storage[id], contract.storage[id + 3] - contract.storage[id + 2])
# Clear storage
contract.storage[id] = 0
contract.storage[id + 1] = 0
contract.storage[id + 2] = 0
contract.storage[id + 3] = 0
contract.storage[id + 4] = 0

View File

@ -0,0 +1,19 @@
# An implementation of a contract for storing a key/value binding
init:
# Set owner
contract.storage[0] = msg.sender
code:
# Check ownership
if msg.sender == contract.storage[0]:
# Get: returns (found, val)
if msg.data[0] == 0:
s = sha3(msg.data[1])
return([contract.storage[s], contract.storage[s+1]], 2)
# Set: sets map[k] = v
elif msg.data[0] == 1:
s = sha3(msg.data[1])
contract.storage[s] = 1
contract.storage[s + 1] = msg.data[2]
# Suicide
elif msg.data[2] == 1:
suicide(0)

View File

@ -0,0 +1,14 @@
init:
contract.storage[0] = msg.sender
code:
if msg.sender != contract.storage[0]:
stop
i = 0
while i < ~calldatasize():
to = ~calldataload(i)
value = ~calldataload(i+20) / 256^12
datasize = ~calldataload(i+32) / 256^30
data = alloc(datasize)
~calldatacopy(data, i+34, datasize)
~call(tx.gas - 25, to, value, data, datasize, 0, 0)
i += 34 + datasize

View File

@ -0,0 +1,166 @@
# Exists in state:
# (i) last committed block
# (ii) chain of uncommitted blocks (linear only)
# (iii) transactions, each tx with an associated block number
#
# Uncommitted block =
# [ numtxs, numkvs, tx1 (N words), tx2 (N words) ..., [k1, v1], [k2, v2], [k3, v3] ... ]
#
# Block checking process
#
# Suppose last committed state is m
# Last uncommitted state is n
# Contested block is b
#
# 1. Temporarily apply all state transitions from
# m to b
# 2. Run code, get list of changes
# 3. Check is list of changes matches deltas
# * if yes, do nothing
# * if no, set last uncommitted state to pre-b
#
# Storage variables:
#
# Last committed block: 0
# Last uncommitted block: 1
# Contract holding code: 2
# Uncommitted map: 3
# Transaction length (parameter): 4
# Block b: 2^160 + b * 2^40:
# + 1: submission blknum
# + 2: submitter
# + 3: data in uncommitted block format above
# Last committed storage:
# sha3(k): index k
# Initialize: [0, c, txlength], set address of the code-holding contract and the transaction
# length
if not contract.storage[2]:
contract.storage[2] = msg.data[1]
contract.storage[4] = msg.data[2]
stop
# Sequentially commit all uncommitted blocks that are more than 1000 mainchain-blocks old
last_committed_block = contract.storage[0]
last_uncommitted_block = contract.storage[1]
lcb_storage_index = 2^160 + last_committed_block * 2^40
while contract.storage[lcb_storage_index + 1] < block.number - 1000 and last_committed_block < last_uncommitted_block:
kvpairs = contract.storage[lcb_storage_index]
i = 0
while i < kvpairs:
k = contract.storage[lcb_storage_index + 3 + i * 2]
v = contract.storage[lcb_storage_index + 4 + i * 2]
contract.storage[sha3(k)] = v
i += 1
last_committed_block += 1
lcb_storage_index += 2^40
contract.storage[0] = last_committed_block
# Propose block: [ 0, block number, data in block format above ... ]
if msg.data[0] == 0:
blknumber = msg.data[1]
# Block number must be correct
if blknumber != contract.storage[1]:
stop
# Deposit requirement
if msg.value < 10^19:
stop
# Store the proposal in storage as
# [ 0, main-chain block number, sender, block data...]
start_index = 2^160 + blknumber * 2^40
numkvs = (msg.datasize - 2) / 2
contract.storage[start_index + 1] = block.number
1ontract.storage[start_index + 2] = msg.sender
i = 0
while i < msg.datasize - 2:
contract.storage[start_index + 3 + i] = msg.data[2 + i]
i += 1
contract.storage[1] = blknumber + 1
# Challenge block: [ 1, b ]
elif msg.data[0] == 1:
blknumber = msg.data[1]
txwidth = contract.storage[4]
last_uncommitted_block = contract.storage[1]
last_committed_block = contract.storage[0]
# Cannot challenge nonexistent or committed blocks
if blknumber <= last_uncommitted_block or blknumber > last_committed_block:
stop
# Create a contract to serve as a map that maintains keys and values
# temporarily
tempstore = create('map.se')
contract.storage[3] = tempstore
# Unquestioningly apply the state transitions from the last committed block
# up to b
b = last_committed_block
cur_storage_index = 2^160 + last_committed_block * 2^40
while b < blknumber:
numtxs = contract.storage[cur_storage_index + 3]
numkvs = contract.storage[cur_storage_index + 4]
kv0index = cur_storage_index + 5 + numtxs * txwidth
i = 0
while i < numkvs:
k = contract.storage[kv0index + i * 2]
v = contract.storage[kx0index + i * 2 + 1]
call(tempstore, [1, k, v], 3)
i += 1
b += 1
cur_storage_index += 2^40
# Run the actual code, and see what state transitions it outputs
# The way that the code is expected to work is to:
#
# (1) take as input the list of transactions (the contract should
# use msg.datasize to determine how many txs there are, and it should
# be aware of the value of txwidth)
# (2) call this contract with [2, k] to read current state data
# (3) call this contract with [3, k, v] to write current state data
# (4) return as output a list of all state transitions that it made
# in the form [kvcount, k1, v1, k2, v2 ... ]
#
# The reason for separating (2) from (3) is that sometimes the state
# transition may end up changing a given key many times, and we don't
# need to inefficiently store that in storage
numkvs = contract.storage[cur_storage_index + 3]
numtxs = contract.storage[cur_storage_index + 4]
# Populate input array
inpwidth = numtxs * txwidth
inp = array(inpwidth)
i = 0
while i < inpwidth:
inp[i] = contract.storage[cur_storage_index + 5 + i]
i += 1
out = call(contract.storage[2], inp, inpwidth, numkvs * 2 + 1)
# Check that the number of state transitions is the same
if out[0] != kvcount:
send(msg.sender, 10^19)
contract.storage[0] = last_committed_block
stop
kv0index = cur_storage_index + 5 + numtxs * txwidth
i = 0
while i < kvcount:
# Check that each individual state transition matches
k = contract.storage[kv0index + i * 2 + 1]
v = contract.storage[kv0index + i * 2 + 2]
if k != out[i * 2 + 1] or v != out[i * 2 + 2]:
send(msg.sender, 10^19)
contract.storage[0] = last_committed_block
stop
i += 1
# Suicide tempstore
call(tempstore, 2)
# Read data [2, k]
elif msg.data[0] == 2:
tempstore = contract.storage[3]
o = call(tempstore, [0, msg.data[1]], 2, 2)
if o[0]:
return(o[1])
else:
return contract.storage[sha3(msg.data[1])]
# Write data [3, k, v]
elif msg.data[0] == 3:
tempstore = contract.storage[3]
call(tempstore, [1, msg.data[1], msg.data[2]], 3, 2)

View File

@ -0,0 +1,31 @@
type f: [a, b, c, d, e]
macro f($a) + f($b):
f(add($a, $b))
macro f($a) - f($b):
f(sub($a, $b))
macro f($a) * f($b):
f(mul($a, $b) / 10000)
macro f($a) / f($b):
f(sdiv($a * 10000, $b))
macro f($a) % f($b):
f(smod($a, $b))
macro f($v) = f($w):
$v = $w
macro unfify(f($a)):
$a / 10000
macro fify($a):
f($a * 10000)
a = fify(5)
b = fify(2)
c = a / b
e = c + (a / b)
return(unfify(e))

View File

@ -0,0 +1,116 @@
macro smin($a, $b):
with $1 = $a:
with $2 = $b:
if(slt($1, $2), $1, $2)
macro smax($a, $b):
with $1 = $a:
with $2 = $b:
if(slt($1, $2), $2, $1)
def omul(x, y):
o = expose(mklong(x) * mklong(y))
return(slice(o, 1), o[0]+1)
def oadd(x, y):
o = expose(mklong(x) + mklong(y))
return(slice(o, 1), o[0]+1)
def osub(x, y):
o = expose(mklong(x) - mklong(y))
return(slice(o, 1), o[0]+1)
def odiv(x, y):
o = expose(mklong(x) / mklong(y))
return(slice(o, 1), o[0]+1)
def comb(a:a, b:a, sign):
sz = smax(a[0], b[0])
msz = smin(a[0], b[0])
c = array(sz + 2)
c[0] = sz
i = 0
carry = 0
while i < msz:
m = a[i + 1] + sign * b[i + 1] + carry
c[i + 1] = mod(m + 2^127, 2^128) - 2^127
carry = (div(m + 2^127, 2^128) + 2^127) % 2^128 - 2^127
i += 1
u = if(a[0] > msz, a, b)
s = if(a[0] > msz, 1, sign)
while i < sz:
m = s * u[i + 1] + carry
c[i + 1] = mod(m + 2^127, 2^128) - 2^127
carry = (div(m + 2^127, 2^128) + 2^127) % 2^128 - 2^127
i += 1
if carry:
c[0] += 1
c[sz + 1] = carry
return(c, c[0]+1)
def mul(a:a, b:a):
c = array(a[0] + b[0] + 2)
c[0] = a[0] + b[0]
i = 0
while i < a[0]:
j = 0
carry = 0
while j < b[0]:
m = c[i + j + 1] + a[i + 1] * b[j + 1] + carry
c[i + j + 1] = mod(m + 2^127, 2^128) - 2^127
carry = (div(m + 2^127, 2^128) + 2^127) % 2^128 - 2^127
j += 1
if carry:
c[0] = a[0] + b[0] + 1
c[i + j + 1] += carry
i += 1
return(c, c[0]+1)
macro long($a) + long($b):
long(self.comb($a:$a[0]+1, $b:$b[0]+1, 1, outsz=$a[0]+$b[0]+2))
macro long($a) - long($b):
long(self.comb($a:$a[0]+1, $b:$b[0]+1, -1, outsz=$a[0]+$b[0]+2))
macro long($a) * long($b):
long(self.mul($a:$a[0]+1, $b:$b[0]+1, outsz=$a[0]+$b[0]+2))
macro long($a) / long($b):
long(self.div($a:$a[0]+1, $b:$b[0]+1, outsz=$a[0]+$b[0]+2))
macro mulexpand(long($a), $k, $m):
long:
with $c = array($a[0]+k+2):
$c[0] = $a[0]+$k
with i = 0:
while i < $a[0]:
v = $a[i+1] * $m + $c[i+$k+1]
$c[i+$k+1] = mod(v + 2^127, 2^128) - 2^127
$c[i+$k+2] = div(v + 2^127, 2^128)
i += 1
$c
def div(a:a, b:a):
asz = a[0]
bsz = b[0]
while b[bsz] == 0 and bsz > 0:
bsz -= 1
c = array(asz+2)
c[0] = asz+1
while 1:
while a[asz] == 0 and asz > 0:
asz -= 1
if asz < bsz:
return(c, c[0]+1)
sub = expose(mulexpand(long(b), asz - bsz, a[asz] / b[bsz]))
c[asz - bsz+1] = a[asz] / b[bsz]
a = expose(long(a) - long(sub))
a[asz-1] += 2^128 * a[asz]
a[asz] = 0
macro mklong($i):
long([2, mod($i + 2^127, 2^128) - 2^127, div($i + 2^127, 2^128)])
macro expose(long($i)):
$i

View File

@ -0,0 +1,2 @@
def double(v):
return(v*2)

View File

@ -0,0 +1,187 @@
# mutuala - subcurrency
# We want to issue a currency that reduces in value as you store it through negative interest.
# That negative interest would be stored in a commons account. It's like the p2p version of a
# capital tax
# the same things goes for transactions - you pay as you use the currency. However, the more
# you pay, the more you get to say about what the tax is used for
# each participant can propose a recipient for a payout to be made out of the commons account,
# others can vote on it by awarding it tax_credits.
# TODO should proposal have expiration timestamp?, after which the tax_credits are refunded
# TODO multiple proposals can take more credits that available in the Commons, how to handle this
# TODO how to handle lost accounts, after which no longer possible to get 2/3 majority
shared:
COMMONS = 42
ADMIN = 666
CAPITAL_TAX_PER_DAY = 7305 # 5% per year
PAYMENT_TAX = 20 # 5%
ACCOUNT_LIST_OFFSET = 2^160
ACCOUNT_MAP_OFFSET = 2^161
PROPOSAL_LIST_OFFSET = 2^162
PROPOSAL_MAP_OFFSET = 2^163
init:
contract.storage[ADMIN] = msg.sender
contract.storage[ACCOUNT_LIST_OFFSET - 1] = 1
contract.storage[ACCOUNT_LIST_OFFSET] = msg.sender
contract.storage[ACCOUNT_MAP_OFFSET + msg.sender] = 10^12
contract.storage[ACCOUNT_MAP_OFFSET + msg.sender + 1] = block.timestamp
# contract.storage[COMMONS] = balance commons
# contract.storage[ACCOUNT_LIST_OFFSET - 1] = number of accounts
# contract.storage[ACCOUNT_LIST_OFFSET + n] = account n
# contract.storage[PROPOSAL_LIST_OFFSET - 1] contains the number of proposals
# contract.storage[PROPOSAL_LIST_OFFSET + n] = proposal n
# per account:
# contract.storage[ACCOUNT_MAP_OFFSET + account] = balance
# contract.storage[ACCOUNT_MAP_OFFSET + account+1] = timestamp_last_transaction
# contract.storage[ACCOUNT_MAP_OFFSET + account+2] = tax_credits
# per proposal:
# contract.storage[PROPOSAL_MAP_OFFSET + proposal_id] = recipient
# contract.storage[PROPOSAL_MAP_OFFSET + proposal_id+1] = amount
# contract.storage[PROPOSAL_MAP_OFFSET + proposal_id+2] = total vote credits
code:
if msg.data[0] == "suicide" and msg.sender == contract.storage[ADMIN]:
suicide(msg.sender)
elif msg.data[0] == "balance":
addr = msg.data[1]
return(contract.storage[ACCOUNT_MAP_OFFSET + addr])
elif msg.data[0] == "pay":
from = msg.sender
fromvalue = contract.storage[ACCOUNT_MAP_OFFSET + from]
to = msg.data[1]
if to == 0 or to >= 2^160:
return([0, "invalid address"], 2)
value = msg.data[2]
tax = value / PAYMENT_TAX
if fromvalue >= value + tax:
contract.storage[ACCOUNT_MAP_OFFSET + from] = fromvalue - (value + tax)
contract.storage[ACCOUNT_MAP_OFFSET + to] += value
# tax
contract.storage[COMMONS] += tax
contract.storage[ACCOUNT_MAP_OFFSET + from + 2] += tax
# check timestamp field to see if target account exists
if contract.storage[ACCOUNT_MAP_OFFSET + to + 1] == 0:
# register new account
nr_accounts = contract.storage[ACCOUNT_LIST_OFFSET - 1]
contract.storage[ACCOUNT_LIST_OFFSET + nr_accounts] = to
contract.storage[ACCOUNT_LIST_OFFSET - 1] += 1
contract.storage[ACCOUNT_MAP_OFFSET + to + 1] = block.timestamp
return(1)
else:
return([0, "insufficient balance"], 2)
elif msg.data[0] == "hash":
proposal_id = sha3(msg.data[1])
return(proposal_id)
elif msg.data[0] == "propose":
from = msg.sender
# check if sender has an account and has tax credits
if contract.storage[ACCOUNT_MAP_OFFSET + from + 2] == 0:
return([0, "sender has no tax credits"], 2)
proposal_id = sha3(msg.data[1])
# check if proposal doesn't already exist
if contract.storage[PROPOSAL_MAP_OFFSET + proposal_id]:
return([0, "proposal already exists"])
to = msg.data[2]
# check if recipient is a valid address and has an account (with timestamp)
if to == 0 or to >= 2^160:
return([0, "invalid address"], 2)
if contract.storage[ACCOUNT_MAP_OFFSET + to + 1] == 0:
return([0, "invalid to account"], 2)
value = msg.data[3]
# check if there is enough money in the commons account
if value > contract.storage[COMMONS]:
return([0, "not enough credits in commons"], 2)
# record proposal in list
nr_proposals = contract.storage[PROPOSAL_LIST_OFFSET - 1]
contract.storage[PROPOSAL_LIST_OFFSET + nr_proposals] = proposal_id
contract.storage[PROPOSAL_LIST_OFFSET - 1] += 1
# record proposal in map
contract.storage[PROPOSAL_MAP_OFFSET + proposal_id] = to
contract.storage[PROPOSAL_MAP_OFFSET + proposal_id + 1] = value
return(proposal_id)
elif msg.data[0] == "vote":
from = msg.sender
proposal_id = sha3(msg.data[1])
value = msg.data[2]
# check if sender has an account and has tax credits
if value < contract.storage[ACCOUNT_MAP_OFFSET + from + 2]:
return([0, "sender doesn't have enough tax credits"], 2)
# check if proposal exist
if contract.storage[PROPOSAL_MAP_OFFSET + proposal_id] == 0:
return([0, "proposal doesn't exist"], 2)
# increase votes
contract.storage[PROPOSAL_MAP_OFFSET + proposal_id + 2] += value
# withdraw tax credits
contract.storage[ACCOUNT_MAP_OFFSET + from + 2] -= value
# did we reach 2/3 threshold?
if contract.storage[PROPOSAL_MAP_OFFSET + proposal_id + 2] >= contract.storage[COMMONS] * 2 / 3:
# got majority
to = contract.storage[PROPOSAL_MAP_OFFSET + proposal_id]
amount = contract.storage[PROPOSAL_MAP_OFFSET + proposal_id + 1]
# adjust balances
contract.storage[ACCOUNT_MAP_OFFSET + to] += amount
contract.storage[COMMONS] -= amount
# reset proposal
contract.storage[PROPOSAL_MAP_OFFSET + proposal_id] = 0
contract.storage[PROPOSAL_MAP_OFFSET + proposal_id + 1] = 0
contract.storage[PROPOSAL_MAP_OFFSET + proposal_id + 2] = 0
return(1)
return(proposal_id)
elif msg.data[0] == "tick":
nr_accounts = contract.storage[ACCOUNT_LIST_OFFSET - 1]
account_idx = 0
tax_paid = 0
# process all accounts and see if they have to pay their daily capital tax
while account_idx < nr_accounts:
cur_account = contract.storage[ACCOUNT_LIST_OFFSET + account_idx]
last_timestamp = contract.storage[ACCOUNT_MAP_OFFSET + cur_account + 1]
time_diff = block.timestamp - last_timestamp
if time_diff >= 86400:
tax_days = time_diff / 86400
balance = contract.storage[ACCOUNT_MAP_OFFSET + cur_account]
tax = tax_days * (balance / CAPITAL_TAX_PER_DAY)
if tax > 0:
# charge capital tax, but give tax credits in return
contract.storage[ACCOUNT_MAP_OFFSET + cur_account] -= tax
contract.storage[ACCOUNT_MAP_OFFSET + cur_account + 1] += tax_days * 86400
contract.storage[ACCOUNT_MAP_OFFSET + cur_account + 2] += tax
contract.storage[COMMONS] += tax
tax_paid += 1
account_idx += 1
return(tax_paid) # how many accounts did we charge tax on
else:
return([0, "unknown command"], 2)

View File

@ -0,0 +1,7 @@
def register(k, v):
if !self.storage[k]: # Is the key not yet taken?
# Then take it!
self.storage[k] = v
return(1)
else:
return(0) // Otherwise do nothing

View File

@ -0,0 +1,43 @@
macro padd($x, psuc($y)):
psuc(padd($x, $y))
macro padd($x, z()):
$x
macro dec(psuc($x)):
dec($x) + 1
macro dec(z()):
0
macro pmul($x, z()):
z()
macro pmul($x, psuc($y)):
padd(pmul($x, $y), $x)
macro pexp($x, z()):
one()
macro pexp($x, psuc($y)):
pmul($x, pexp($x, $y))
macro fac(z()):
one()
macro fac(psuc($x)):
pmul(psuc($x), fac($x))
macro one():
psuc(z())
macro two():
psuc(psuc(z()))
macro three():
psuc(psuc(psuc(z())))
macro five():
padd(three(), two())
return([dec(pmul(three(), pmul(three(), three()))), dec(fac(five()))], 2)

View File

@ -0,0 +1,4 @@
extern mul2: [double]
x = create("mul2.se")
return(x.double(5))

View File

@ -0,0 +1,33 @@
def kall():
argcount = ~calldatasize() / 32
if argcount == 1:
return(~calldataload(1))
args = array(argcount)
~calldatacopy(args, 1, argcount * 32)
low = array(argcount)
lsz = 0
high = array(argcount)
hsz = 0
i = 1
while i < argcount:
if args[i] < args[0]:
low[lsz] = args[i]
lsz += 1
else:
high[hsz] = args[i]
hsz += 1
i += 1
low = self.kall(data=low, datasz=lsz, outsz=lsz)
high = self.kall(data=high, datasz=hsz, outsz=hsz)
o = array(argcount)
i = 0
while i < lsz:
o[i] = low[i]
i += 1
o[lsz] = args[0]
j = 0
while j < hsz:
o[lsz + 1 + j] = high[j]
j += 1
return(o, argcount)

View File

@ -0,0 +1,46 @@
# Quicksort pairs
# eg. input of the form [ 30, 1, 90, 2, 70, 3, 50, 4]
# outputs [ 30, 1, 50, 4, 70, 3, 90, 2 ]
#
# Note: this can be used as a generalized sorting algorithm:
# map every object to [ key, ref ] where `ref` is the index
# in memory to all of the properties and `key` is the key to
# sort by
def kall():
argcount = ~calldatasize() / 64
if argcount == 1:
return([~calldataload(1), ~calldataload(33)], 2)
args = array(argcount * 2)
~calldatacopy(args, 1, argcount * 64)
low = array(argcount * 2)
lsz = 0
high = array(argcount * 2)
hsz = 0
i = 2
while i < argcount * 2:
if args[i] < args[0]:
low[lsz] = args[i]
low[lsz + 1] = args[i + 1]
lsz += 2
else:
high[hsz] = args[i]
high[hsz + 1] = args[i + 1]
hsz += 2
i = i + 2
low = self.kall(data=low, datasz=lsz, outsz=lsz)
high = self.kall(data=high, datasz=hsz, outsz=hsz)
o = array(argcount * 2)
i = 0
while i < lsz:
o[i] = low[i]
i += 1
o[lsz] = args[0]
o[lsz + 1] = args[1]
j = 0
while j < hsz:
o[lsz + 2 + j] = high[j]
j += 1
return(o, argcount * 2)

View File

@ -0,0 +1,94 @@
# SchellingCoin implementation
#
# Epoch length: 100 blocks
# Target savings depletion rate: 0.1% per epoch
data epoch
data hashes_submitted
data output
data quicksort_pairs
data accounts[2^160]
data submissions[2^80](hash, deposit, address, value)
extern any: [call]
def init():
self.epoch = block.number / 100
self.quicksort_pairs = create('quicksort_pairs.se')
def any():
if block.number / 100 > epoch:
# Sort all values submitted
N = self.hashes_submitted
o = array(N * 2)
i = 0
j = 0
while i < N:
v = self.submissions[i].value
if v:
o[j] = v
o[j + 1] = i
j += 2
i += 1
values = self.quicksort_pairs.call(data=o, datasz=j, outsz=j)
# Calculate total deposit, refund non-submitters and
# cleanup
deposits = array(j / 2)
addresses = array(j / 2)
i = 0
total_deposit = 0
while i < j / 2:
base_index = HASHES + values[i * 2 + 1] * 3
deposits[i] = self.submissions[i].deposit
addresses[i] = self.submissions[i].address
if self.submissions[values[i * 2 + 1]].value:
total_deposit += deposits[i]
else:
send(addresses[i], deposits[i] * 999 / 1000)
i += 1
inverse_profit_ratio = total_deposit / (contract.balance / 1000) + 1
# Reward everyone
i = 0
running_deposit_sum = 0
halfway_passed = 0
while i < j / 2:
new_deposit_sum = running_deposit_sum + deposits[i]
if new_deposit_sum > total_deposit / 4 and running_deposit_sum < total_deposit * 3 / 4:
send(addresses[i], deposits[i] + deposits[i] / inverse_profit_ratio * 2)
else:
send(addresses[i], deposits[i] - deposits[i] / inverse_profit_ratio)
if not halfway_passed and new_deposit_sum > total_deposit / 2:
self.output = self.submissions[i].value
halfway_passed = 1
self.submissions[i].value = 0
running_deposit_sum = new_deposit_sum
i += 1
self.epoch = block.number / 100
self.hashes_submitted = 0
def submit_hash(h):
if block.number % 100 < 50:
cur = self.hashes_submitted
pos = HASHES + cur * 3
self.submissions[cur].hash = h
self.submissions[cur].deposit = msg.value
self.submissions[cur].address = msg.sender
self.hashes_submitted = cur + 1
return(cur)
def submit_value(index, v):
if sha3([msg.sender, v], 2) == self.submissions[index].hash:
self.submissions[index].value = v
return(1)
def request_balance():
return(contract.balance)
def request_output():
return(self.output)

View File

@ -0,0 +1,171 @@
# Hedged zero-supply dollar implementation
# Uses SchellingCoin as price-determining backend
#
# Stored variables:
#
# 0: Schelling coin contract
# 1: Last epoch
# 2: Genesis block of contract
# 3: USD exposure
# 4: ETH exposure
# 5: Cached price
# 6: Last interest rate
# 2^160 + k: interest rate accumulator at k epochs
# 2^161 + ADDR * 3: eth-balance of a particular address
# 2^161 + ADDR * 3 + 1: usd-balance of a particular address
# 2^161 + ADDR * 3 + 1: last accessed epoch of a particular address
#
# Transaction types:
#
# [1, to, val]: send ETH
# [2, to, val]: send USD
# [3, wei_amount]: convert ETH to USD
# [4, usd_amount]: converts USD to ETH
# [5]: deposit
# [6, amount]: withdraw
# [7]: my balance query
# [7, acct]: balance query for any acct
# [8]: global state query
# [9]: liquidation test any account
#
# The purpose of the contract is to serve as a sort of cryptographic
# bank account where users can store both ETH and USD. ETH must be
# stored in zero or positive quantities, but USD balances can be
# positive or negative. If the USD balance is negative, the invariant
# usdbal * 10 >= ethbal * 9 must be satisfied; if any account falls
# below this value, then that account's balances are zeroed. Note
# that there is a 2% bounty to ping the app if an account does go
# below zero; one weakness is that if no one does ping then it is
# quite possible for accounts to go negative-net-worth, then zero
# themselves out, draining the reserves of the "bank" and potentially
# bankrupting it. A 0.1% fee on ETH <-> USD trade is charged to
# minimize this risk. Additionally, the bank itself will inevitably
# end up with positive or negative USD exposure; to mitigate this,
# it automatically updates interest rates on USD to keep exposure
# near zero.
data schelling_coin
data last_epoch
data starting_block
data usd_exposure
data eth_exposure
data price
data last_interest_rate
data interest_rate_accum[2^50]
data accounts[2^160](eth, usd, last_epoch)
extern sc: [submit_hash, submit_value, request_balance, request_output]
def init():
self.schelling_coin = create('schellingcoin.se')
self.price = self.schelling_coin.request_output()
self.interest_rate_accum[0] = 10^18
self.starting_block = block.number
def any():
sender = msg.sender
epoch = (block.number - self.starting_block) / 100
last_epoch = self.last_epoch
usdprice = self.price
# Update contract epochs
if epoch > last_epoch:
delta = epoch - last_epoch
last_interest_rate = self.last_interest_rate
usd_exposure - self.usd_exposure
last_accum = self.interest_rate_accum[last_epoch]
if usd_exposure < 0:
self.last_interest_rate = last_interest_rate - 10000 * delta
elif usd_exposure > 0:
self.last_interest_rate = last_interest_rate + 10000 * delta
self.interest_rate_accum[epoch] = last_accum + last_accum * last_interest_rate * delta / 10^9
# Proceeds go to support the SchellingCoin feeding it price data, ultimately providing the depositors
# of the SchellingCoin an interest rate
bal = max(self.balance - self.eth_exposure, 0) / 10000
usdprice = self.schelling_coin.request_output()
self.price = usdprice
self.last_epoch = epoch
ethbal = self.accounts[msg.sender].eth
usdbal = self.accounts[msg.sender].usd
# Apply interest rates to sender and liquidation-test self
if msg.sender != self:
self.ping(self)
def send_eth(to, value):
if value > 0 and value <= ethbal and usdbal * usdprice * 2 + (ethbal - value) >= 0:
self.accounts[msg.sender].eth = ethbal - value
self.ping(to)
self.accounts[to].eth += value
return(1)
def send_usd(to, value):
if value > 0 and value <= usdbal and (usdbal - value) * usdprice * 2 + ethbal >= 0:
self.accounts[msg.sender].usd = usdbal - value
self.ping(to)
self.accounts[to].usd += value
return(1)
def convert_to_eth(usdvalue):
ethplus = usdvalue * usdprice * 999 / 1000
if usdvalue > 0 and (usdbal - usdvalue) * usdprice * 2 + (ethbal + ethplus) >= 0:
self.accounts[msg.sender].eth = ethbal + ethplus
self.accounts[msg.sender].usd = usdbal - usdvalue
self.eth_exposure += ethplus
self.usd_exposure -= usdvalue
return([ethbal + ethplus, usdbal - usdvalue], 2)
def convert_to_usd(ethvalue):
usdplus = ethvalue / usdprice * 999 / 1000
if ethvalue > 0 and (usdbal + usdplus) * usdprice * 2 + (ethbal - ethvalue) >= 0:
self.accounts[msg.sender].eth = ethbal - ethvalue
self.accounts[msg.sender].usd = usdbal + usdplus
self.eth_exposure -= ethvalue
self.usd_exposure += usdplus
return([ethbal - ethvalue, usdbal + usdplus], 2)
def deposit():
self.accounts[msg.sender].eth = ethbal + msg.value
self.eth_exposure += msg.value
return(ethbal + msg.value)
def withdraw(value):
if value > 0 and value <= ethbal and usdbal * usdprice * 2 + (ethbal - value) >= 0:
self.accounts[msg.sender].eth -= value
self.eth_exposure -= value
return(ethbal - value)
def balance(acct):
self.ping(acct)
return([self.accounts[acct].eth, self.accounts[acct].usd], 2)
def global_state_query(acct):
interest = self.last_interest_rate
usd_exposure = self.usd_exposure
eth_exposure = self.eth_exposure
eth_balance = self.balance
return([epoch, usdprice, interest, usd_exposure, eth_exposure, eth_balance], 6)
def ping(acct):
account_last_epoch = self.accounts[acct].last_epoch
if account_last_epoch != epoch:
cur_usd_balance = self.accounts[acct].usd
new_usd_balance = cur_usd_balance * self.interest_rate_accum[epoch] / self.interest_rate_accum[account_last_epoch]
self.accounts[acct].usd = new_usd_balance
self.accounts[acct].last_epoch = epoch
self.usd_exposure += new_usd_balance - cur_usd_balance
ethbal = self.accounts[acct].eth
if new_usd_balance * usdval * 10 + ethbal * 9 < 0:
self.accounts[acct].eth = 0
self.accounts[acct].usd = 0
self.accounts[msg.sender].eth += ethbal / 50
self.eth_exposure += -ethbal + ethbal / 50
self.usd_exposure += new_usd_balance
return(1)
return(0)

View File

@ -0,0 +1 @@
return(sha3([msg.sender, msg.data[0]], 2))

View File

@ -0,0 +1,3 @@
def register(k, v):
if !self.storage[k]:
self.storage[k] = v

View File

@ -0,0 +1,11 @@
def init():
self.storage[msg.sender] = 1000000
def balance_query(k):
return(self.storage[addr])
def send(to, value):
fromvalue = self.storage[msg.sender]
if fromvalue >= value:
self.storage[from] = fromvalue - value
self.storage[to] += value

View File

@ -0,0 +1,35 @@
#include <stdio.h>
#include <iostream>
#include <vector>
#include "funcs.h"
#include "bignum.h"
#include "util.h"
#include "parser.h"
#include "lllparser.h"
#include "compiler.h"
#include "rewriter.h"
#include "tokenize.h"
Node compileToLLL(std::string input) {
return rewrite(parseSerpent(input));
}
Node compileChunkToLLL(std::string input) {
return rewriteChunk(parseSerpent(input));
}
std::string compile(std::string input) {
return compileLLL(compileToLLL(input));
}
std::vector<Node> prettyCompile(std::string input) {
return prettyCompileLLL(compileToLLL(input));
}
std::string compileChunk(std::string input) {
return compileLLL(compileChunkToLLL(input));
}
std::vector<Node> prettyCompileChunk(std::string input) {
return prettyCompileLLL(compileChunkToLLL(input));
}

View File

@ -0,0 +1,35 @@
#include <stdio.h>
#include <iostream>
#include <vector>
#include "bignum.h"
#include "util.h"
#include "parser.h"
#include "lllparser.h"
#include "compiler.h"
#include "rewriter.h"
#include "tokenize.h"
// Function listing:
//
// parseSerpent (serpent -> AST) std::string -> Node
// parseLLL (LLL -> AST) std::string -> Node
// rewrite (apply rewrite rules) Node -> Node
// compileToLLL (serpent -> LLL) std::string -> Node
// compileLLL (LLL -> EVMhex) Node -> std::string
// prettyCompileLLL (LLL -> EVMasm) Node -> std::vector<Node>
// prettyCompile (serpent -> EVMasm) std::string -> std::vector>Node>
// compile (serpent -> EVMhex) std::string -> std::string
// get_file_contents (filename -> file) std::string -> std::string
// exists (does file exist?) std::string -> bool
Node compileToLLL(std::string input);
Node compileChunkToLLL(std::string input);
std::string compile(std::string input);
std::vector<Node> prettyCompile(std::string input);
std::string compileChunk(std::string input);
std::vector<Node> prettyCompileChunk(std::string input);

View File

@ -0,0 +1,203 @@
#include <stdio.h>
#include <iostream>
#include <vector>
#include <map>
#include "util.h"
#include "lllparser.h"
#include "bignum.h"
#include "optimize.h"
#include "rewriteutils.h"
#include "preprocess.h"
#include "functions.h"
std::string getSignature(std::vector<Node> args) {
std::string o;
for (unsigned i = 0; i < args.size(); i++) {
if (args[i].val == ":" && args[i].args[1].val == "s")
o += "s";
else if (args[i].val == ":" && args[i].args[1].val == "a")
o += "a";
else
o += "i";
}
return o;
}
// Convert a list of arguments into a node containing a
// < datastart, datasz > pair
Node packArguments(std::vector<Node> args, std::string sig,
int funId, Metadata m) {
// Plain old 32 byte arguments
std::vector<Node> nargs;
// Variable-sized arguments
std::vector<Node> vargs;
// Variable sizes
std::vector<Node> sizes;
// Is a variable an array?
std::vector<bool> isArray;
// Fill up above three argument lists
int argCount = 0;
for (unsigned i = 0; i < args.size(); i++) {
Metadata m = args[i].metadata;
if (args[i].val == "=") {
// do nothing
}
else {
// Determine the correct argument type
char argType;
if (sig.size() > 0) {
if (argCount >= (signed)sig.size())
err("Too many args", m);
argType = sig[argCount];
}
else argType = 'i';
// Integer (also usable for short strings)
if (argType == 'i') {
if (args[i].val == ":")
err("Function asks for int, provided string or array", m);
nargs.push_back(args[i]);
}
// Long string
else if (argType == 's') {
if (args[i].val != ":")
err("Must specify string length", m);
vargs.push_back(args[i].args[0]);
sizes.push_back(args[i].args[1]);
isArray.push_back(false);
}
// Array
else if (argType == 'a') {
if (args[i].val != ":")
err("Must specify array length", m);
vargs.push_back(args[i].args[0]);
sizes.push_back(args[i].args[1]);
isArray.push_back(true);
}
else err("Invalid arg type in signature", m);
argCount++;
}
}
int static_arg_size = 1 + (vargs.size() + nargs.size()) * 32;
// Start off by saving the size variables and calculating the total
msn kwargs;
kwargs["funid"] = tkn(utd(funId), m);
std::string pattern =
"(with _sztot "+utd(static_arg_size)+" "
" (with _sizes (alloc "+utd(sizes.size() * 32)+") "
" (seq ";
for (unsigned i = 0; i < sizes.size(); i++) {
std::string sizeIncrement =
isArray[i] ? "(mul 32 _x)" : "_x";
pattern +=
"(with _x $sz"+utd(i)+"(seq "
" (mstore (add _sizes "+utd(i * 32)+") _x) "
" (set _sztot (add _sztot "+sizeIncrement+" )))) ";
kwargs["sz"+utd(i)] = sizes[i];
}
// Allocate memory, and set first data byte
pattern +=
"(with _datastart (alloc (add _sztot 32)) (seq "
" (mstore8 _datastart $funid) ";
// Copy over size variables
for (unsigned i = 0; i < sizes.size(); i++) {
int v = 1 + i * 32;
pattern +=
" (mstore "
" (add _datastart "+utd(v)+") "
" (mload (add _sizes "+utd(v-1)+"))) ";
}
// Store normal arguments
for (unsigned i = 0; i < nargs.size(); i++) {
int v = 1 + (i + sizes.size()) * 32;
pattern +=
" (mstore (add _datastart "+utd(v)+") $"+utd(i)+") ";
kwargs[utd(i)] = nargs[i];
}
// Loop through variable-sized arguments, store them
pattern +=
" (with _pos (add _datastart "+utd(static_arg_size)+") (seq";
for (unsigned i = 0; i < vargs.size(); i++) {
std::string copySize =
isArray[i] ? "(mul 32 (mload (add _sizes "+utd(i * 32)+")))"
: "(mload (add _sizes "+utd(i * 32)+"))";
pattern +=
" (unsafe_mcopy _pos $vl"+utd(i)+" "+copySize+") "
" (set _pos (add _pos "+copySize+")) ";
kwargs["vl"+utd(i)] = vargs[i];
}
// Return a 2-item array containing the start and size
pattern += " (array_lit _datastart _sztot))))))))";
std::string prefix = "_temp_"+mkUniqueToken();
// Fill in pattern, return triple
return subst(parseLLL(pattern), kwargs, prefix, m);
}
// Create a node for argument unpacking
Node unpackArguments(std::vector<Node> vars, Metadata m) {
std::vector<std::string> varNames;
std::vector<std::string> longVarNames;
std::vector<bool> longVarIsArray;
// Fill in variable and long variable names, as well as which
// long variables are arrays and which are strings
for (unsigned i = 0; i < vars.size(); i++) {
if (vars[i].val == ":") {
if (vars[i].args.size() != 2)
err("Malformed def!", m);
longVarNames.push_back(vars[i].args[0].val);
std::string tag = vars[i].args[1].val;
if (tag == "s")
longVarIsArray.push_back(false);
else if (tag == "a")
longVarIsArray.push_back(true);
else
err("Function value can only be string or array", m);
}
else {
varNames.push_back(vars[i].val);
}
}
std::vector<Node> sub;
if (!varNames.size() && !longVarNames.size()) {
// do nothing if we have no arguments
}
else {
std::vector<Node> varNodes;
for (unsigned i = 0; i < longVarNames.size(); i++)
varNodes.push_back(token(longVarNames[i], m));
for (unsigned i = 0; i < varNames.size(); i++)
varNodes.push_back(token(varNames[i], m));
// Copy over variable lengths and short variables
for (unsigned i = 0; i < varNodes.size(); i++) {
int pos = 1 + i * 32;
std::string prefix = (i < longVarNames.size()) ? "_len_" : "";
sub.push_back(asn("untyped", asn("set",
token(prefix+varNodes[i].val, m),
asn("calldataload", tkn(utd(pos), m), m),
m)));
}
// Copy over long variables
if (longVarNames.size() > 0) {
std::vector<Node> sub2;
int pos = varNodes.size() * 32 + 1;
Node tot = tkn("_tot", m);
for (unsigned i = 0; i < longVarNames.size(); i++) {
Node var = tkn(longVarNames[i], m);
Node varlen = longVarIsArray[i]
? asn("mul", tkn("32", m), tkn("_len_"+longVarNames[i], m))
: tkn("_len_"+longVarNames[i], m);
sub2.push_back(asn("untyped",
asn("set", var, asn("alloc", varlen))));
sub2.push_back(asn("calldatacopy", var, tot, varlen));
sub2.push_back(asn("set", tot, asn("add", tot, varlen)));
}
std::string prefix = "_temp_"+mkUniqueToken();
sub.push_back(subst(
astnode("with", tot, tkn(utd(pos), m), asn("seq", sub2)),
msn(),
prefix,
m));
}
}
return asn("seq", sub, m);
}

View File

@ -0,0 +1,39 @@
#ifndef ETHSERP_FUNCTIONS
#define ETHSERP_FUNCTIONS
#include <stdio.h>
#include <iostream>
#include <vector>
#include <map>
#include "util.h"
#include "lllparser.h"
#include "bignum.h"
#include "optimize.h"
#include "rewriteutils.h"
#include "preprocess.h"
class argPack {
public:
argPack(Node a, Node b, Node c) {
pre = a;
datastart = b;
datasz = c;
}
Node pre;
Node datastart;
Node datasz;
};
// Get a signature from a function
std::string getSignature(std::vector<Node> args);
// Convert a list of arguments into a <pre, mstart, msize> node
// triple, given the signature of a function
Node packArguments(std::vector<Node> args, std::string sig,
int funId, Metadata m);
// Create a node for argument unpacking
Node unpackArguments(std::vector<Node> vars, Metadata m);
#endif

View File

@ -0,0 +1,70 @@
#include <stdio.h>
#include <iostream>
#include <vector>
#include <map>
#include "util.h"
#include "lllparser.h"
#include "tokenize.h"
struct _parseOutput {
Node node;
int newpos;
};
// Helper, returns subtree and position of start of next node
_parseOutput _parse(std::vector<Node> inp, int pos) {
Metadata met = inp[pos].metadata;
_parseOutput o;
// Bracket: keep grabbing tokens until we get to the
// corresponding closing bracket
if (inp[pos].val == "(" || inp[pos].val == "[") {
std::string fun, rbrack;
std::vector<Node> args;
pos += 1;
if (inp[pos].val == "[") {
fun = "access";
rbrack = "]";
}
else rbrack = ")";
// First argument is the function
while (inp[pos].val != ")") {
_parseOutput po = _parse(inp, pos);
if (fun.length() == 0 && po.node.type == 1) {
std::cerr << "Error: first arg must be function\n";
fun = po.node.val;
}
else if (fun.length() == 0) {
fun = po.node.val;
}
else {
args.push_back(po.node);
}
pos = po.newpos;
}
o.newpos = pos + 1;
o.node = astnode(fun, args, met);
}
// Normal token, return it and advance to next token
else {
o.newpos = pos + 1;
o.node = token(inp[pos].val, met);
}
return o;
}
// stream of tokens -> lisp parse tree
Node parseLLLTokenStream(std::vector<Node> inp) {
_parseOutput o = _parse(inp, 0);
return o.node;
}
// Parses LLL
Node parseLLL(std::string s, bool allowFileRead) {
std::string input = s;
std::string file = "main";
if (exists(s) && allowFileRead) {
file = s;
input = get_file_contents(s);
}
return parseLLLTokenStream(tokenize(s, Metadata(file, 0, 0), true));
}

View File

@ -0,0 +1,13 @@
#ifndef ETHSERP_LLLPARSER
#define ETHSERP_LLLPARSER
#include <stdio.h>
#include <iostream>
#include <vector>
#include <map>
#include "util.h"
// LLL text -> parse tree
Node parseLLL(std::string s, bool allowFileRead=false);
#endif

View File

@ -0,0 +1,154 @@
#include <stdio.h>
#include <iostream>
#include <vector>
#include <map>
#include "opcodes.h"
#include "util.h"
#include "bignum.h"
Mapping mapping[] = {
Mapping("STOP", 0x00, 0, 0),
Mapping("ADD", 0x01, 2, 1),
Mapping("MUL", 0x02, 2, 1),
Mapping("SUB", 0x03, 2, 1),
Mapping("DIV", 0x04, 2, 1),
Mapping("SDIV", 0x05, 2, 1),
Mapping("MOD", 0x06, 2, 1),
Mapping("SMOD", 0x07, 2, 1),
Mapping("ADDMOD", 0x08, 3, 1),
Mapping("MULMOD", 0x09, 3, 1),
Mapping("EXP", 0x0a, 2, 1),
Mapping("SIGNEXTEND", 0x0b, 2, 1),
Mapping("LT", 0x10, 2, 1),
Mapping("GT", 0x11, 2, 1),
Mapping("SLT", 0x12, 2, 1),
Mapping("SGT", 0x13, 2, 1),
Mapping("EQ", 0x14, 2, 1),
Mapping("ISZERO", 0x15, 1, 1),
Mapping("AND", 0x16, 2, 1),
Mapping("OR", 0x17, 2, 1),
Mapping("XOR", 0x18, 2, 1),
Mapping("NOT", 0x19, 1, 1),
Mapping("BYTE", 0x1a, 2, 1),
Mapping("SHA3", 0x20, 2, 1),
Mapping("ADDRESS", 0x30, 0, 1),
Mapping("BALANCE", 0x31, 1, 1),
Mapping("ORIGIN", 0x32, 0, 1),
Mapping("CALLER", 0x33, 0, 1),
Mapping("CALLVALUE", 0x34, 0, 1),
Mapping("CALLDATALOAD", 0x35, 1, 1),
Mapping("CALLDATASIZE", 0x36, 0, 1),
Mapping("CALLDATACOPY", 0x37, 3, 0),
Mapping("CODESIZE", 0x38, 0, 1),
Mapping("CODECOPY", 0x39, 3, 0),
Mapping("GASPRICE", 0x3a, 0, 1),
Mapping("EXTCODESIZE", 0x3b, 1, 1),
Mapping("EXTCODECOPY", 0x3c, 4, 0),
Mapping("PREVHASH", 0x40, 0, 1),
Mapping("COINBASE", 0x41, 0, 1),
Mapping("TIMESTAMP", 0x42, 0, 1),
Mapping("NUMBER", 0x43, 0, 1),
Mapping("DIFFICULTY", 0x44, 0, 1),
Mapping("GASLIMIT", 0x45, 0, 1),
Mapping("POP", 0x50, 1, 0),
Mapping("MLOAD", 0x51, 1, 1),
Mapping("MSTORE", 0x52, 2, 0),
Mapping("MSTORE8", 0x53, 2, 0),
Mapping("SLOAD", 0x54, 1, 1),
Mapping("SSTORE", 0x55, 2, 0),
Mapping("JUMP", 0x56, 1, 0),
Mapping("JUMPI", 0x57, 2, 0),
Mapping("PC", 0x58, 0, 1),
Mapping("MSIZE", 0x59, 0, 1),
Mapping("GAS", 0x5a, 0, 1),
Mapping("JUMPDEST", 0x5b, 0, 0),
Mapping("LOG0", 0xa0, 2, 0),
Mapping("LOG1", 0xa1, 3, 0),
Mapping("LOG2", 0xa2, 4, 0),
Mapping("LOG3", 0xa3, 5, 0),
Mapping("LOG4", 0xa4, 6, 0),
Mapping("CREATE", 0xf0, 3, 1),
Mapping("CALL", 0xf1, 7, 1),
Mapping("CALLCODE", 0xf2, 7, 1),
Mapping("RETURN", 0xf3, 2, 0),
Mapping("SUICIDE", 0xff, 1, 0),
Mapping("---END---", 0x00, 0, 0),
};
std::map<std::string, std::vector<int> > opcodes;
std::map<int, std::string> reverseOpcodes;
// Fetches everything EXCEPT PUSH1..32
std::pair<std::string, std::vector<int> > _opdata(std::string ops, int opi) {
if (!opcodes.size()) {
int i = 0;
while (mapping[i].op != "---END---") {
Mapping mi = mapping[i];
opcodes[mi.op] = triple(mi.opcode, mi.in, mi.out);
i++;
}
for (i = 1; i <= 16; i++) {
opcodes["DUP"+unsignedToDecimal(i)] = triple(0x7f + i, i, i+1);
opcodes["SWAP"+unsignedToDecimal(i)] = triple(0x8f + i, i+1, i+1);
}
for (std::map<std::string, std::vector<int> >::iterator it=opcodes.begin();
it != opcodes.end();
it++) {
reverseOpcodes[(*it).second[0]] = (*it).first;
}
}
ops = upperCase(ops);
std::string op;
std::vector<int> opdata;
op = reverseOpcodes.count(opi) ? reverseOpcodes[opi] : "";
opdata = opcodes.count(ops) ? opcodes[ops] : triple(-1, -1, -1);
return std::pair<std::string, std::vector<int> >(op, opdata);
}
int opcode(std::string op) {
return _opdata(op, -1).second[0];
}
int opinputs(std::string op) {
return _opdata(op, -1).second[1];
}
int opoutputs(std::string op) {
return _opdata(op, -1).second[2];
}
std::string op(int opcode) {
return _opdata("", opcode).first;
}
std::string lllSpecials[][3] = {
{ "ref", "1", "1" },
{ "get", "1", "1" },
{ "set", "2", "2" },
{ "with", "3", "3" },
{ "comment", "0", "2147483647" },
{ "ops", "0", "2147483647" },
{ "lll", "2", "2" },
{ "seq", "0", "2147483647" },
{ "if", "3", "3" },
{ "unless", "2", "2" },
{ "until", "2", "2" },
{ "alloc", "1", "1" },
{ "---END---", "0", "0" },
};
std::map<std::string, std::pair<int, int> > lllMap;
// Is a function name one of the valid functions above?
bool isValidLLLFunc(std::string f, int argc) {
if (lllMap.size() == 0) {
for (int i = 0; ; i++) {
if (lllSpecials[i][0] == "---END---") break;
lllMap[lllSpecials[i][0]] = std::pair<int, int>(
dtu(lllSpecials[i][1]), dtu(lllSpecials[i][2]));
}
}
return lllMap.count(f)
&& argc >= lllMap[f].first
&& argc <= lllMap[f].second;
}

View File

@ -0,0 +1,45 @@
#ifndef ETHSERP_OPCODES
#define ETHSERP_OPCODES
#include <stdio.h>
#include <iostream>
#include <vector>
#include <map>
#include "util.h"
class Mapping {
public:
Mapping(std::string Op, int Opcode, int In, int Out) {
op = Op;
opcode = Opcode;
in = In;
out = Out;
}
std::string op;
int opcode;
int in;
int out;
};
extern Mapping mapping[];
extern std::map<std::string, std::vector<int> > opcodes;
extern std::map<int, std::string> reverseOpcodes;
std::pair<std::string, std::vector<int> > _opdata(std::string ops, int opi);
int opcode(std::string op);
int opinputs(std::string op);
int opoutputs(std::string op);
std::string op(int opcode);
extern std::string lllSpecials[][3];
extern std::map<std::string, std::pair<int, int> > lllMap;
bool isValidLLLFunc(std::string f, int argc);
#endif

View File

@ -0,0 +1,98 @@
#include <stdio.h>
#include <iostream>
#include <vector>
#include <map>
#include "util.h"
#include "lllparser.h"
#include "bignum.h"
// Compile-time arithmetic calculations
Node optimize(Node inp) {
if (inp.type == TOKEN) {
Node o = tryNumberize(inp);
if (decimalGt(o.val, tt256, true))
err("Value too large (exceeds 32 bytes or 2^256)", inp.metadata);
return o;
}
for (unsigned i = 0; i < inp.args.size(); i++) {
inp.args[i] = optimize(inp.args[i]);
}
// Arithmetic-specific transform
if (inp.val == "+") inp.val = "add";
if (inp.val == "*") inp.val = "mul";
if (inp.val == "-") inp.val = "sub";
if (inp.val == "/") inp.val = "sdiv";
if (inp.val == "^") inp.val = "exp";
if (inp.val == "**") inp.val = "exp";
if (inp.val == "%") inp.val = "smod";
// Degenerate cases for add and mul
if (inp.args.size() == 2) {
if (inp.val == "add" && inp.args[0].type == TOKEN &&
inp.args[0].val == "0") {
Node x = inp.args[1];
inp = x;
}
if (inp.val == "add" && inp.args[1].type == TOKEN &&
inp.args[1].val == "0") {
Node x = inp.args[0];
inp = x;
}
if (inp.val == "mul" && inp.args[0].type == TOKEN &&
inp.args[0].val == "1") {
Node x = inp.args[1];
inp = x;
}
if (inp.val == "mul" && inp.args[1].type == TOKEN &&
inp.args[1].val == "1") {
Node x = inp.args[0];
inp = x;
}
}
// Arithmetic computation
if (inp.args.size() == 2
&& inp.args[0].type == TOKEN
&& inp.args[1].type == TOKEN) {
std::string o;
if (inp.val == "add") {
o = decimalMod(decimalAdd(inp.args[0].val, inp.args[1].val), tt256);
}
else if (inp.val == "sub") {
if (decimalGt(inp.args[0].val, inp.args[1].val, true))
o = decimalSub(inp.args[0].val, inp.args[1].val);
}
else if (inp.val == "mul") {
o = decimalMod(decimalMul(inp.args[0].val, inp.args[1].val), tt256);
}
else if (inp.val == "div" && inp.args[1].val != "0") {
o = decimalDiv(inp.args[0].val, inp.args[1].val);
}
else if (inp.val == "sdiv" && inp.args[1].val != "0"
&& decimalGt(tt255, inp.args[0].val)
&& decimalGt(tt255, inp.args[1].val)) {
o = decimalDiv(inp.args[0].val, inp.args[1].val);
}
else if (inp.val == "mod" && inp.args[1].val != "0") {
o = decimalMod(inp.args[0].val, inp.args[1].val);
}
else if (inp.val == "smod" && inp.args[1].val != "0"
&& decimalGt(tt255, inp.args[0].val)
&& decimalGt(tt255, inp.args[1].val)) {
o = decimalMod(inp.args[0].val, inp.args[1].val);
}
else if (inp.val == "exp") {
o = decimalModExp(inp.args[0].val, inp.args[1].val, tt256);
}
if (o.length()) return token(o, inp.metadata);
}
return inp;
}
// Is a node degenerate (ie. trivial to calculate) ?
bool isDegenerate(Node n) {
return optimize(n).type == TOKEN;
}
// Is a node purely arithmetic?
bool isPureArithmetic(Node n) {
return isNumberLike(optimize(n));
}

View File

@ -0,0 +1,19 @@
#ifndef ETHSERP_OPTIMIZER
#define ETHSERP_OPTIMIZER
#include <stdio.h>
#include <iostream>
#include <vector>
#include <map>
#include "util.h"
// Compile-time arithmetic calculations
Node optimize(Node inp);
// Is a node degenerate (ie. trivial to calculate) ?
bool isDegenerate(Node n);
// Is a node purely arithmetic?
bool isPureArithmetic(Node n);
#endif

View File

@ -0,0 +1,430 @@
#include <stdio.h>
#include <iostream>
#include <vector>
#include <map>
#include "util.h"
#include "parser.h"
#include "tokenize.h"
// Extended BEDMAS precedence order
int precedence(Node tok) {
std::string v = tok.val;
if (v == ".") return -1;
else if (v == "!" || v == "not") return 1;
else if (v=="^" || v == "**") return 2;
else if (v=="*" || v=="/" || v=="%") return 3;
else if (v=="+" || v=="-") return 4;
else if (v=="<" || v==">" || v=="<=" || v==">=") return 5;
else if (v=="&" || v=="|" || v=="xor" || v=="==" || v == "!=") return 6;
else if (v=="&&" || v=="and") return 7;
else if (v=="||" || v=="or") return 8;
else if (v=="=") return 10;
else if (v=="+=" || v=="-=" || v=="*=" || v=="/=" || v=="%=") return 10;
else if (v==":" || v == "::") return 11;
else return 0;
}
// Token classification for shunting-yard purposes
int toktype(Node tok) {
if (tok.type == ASTNODE) return COMPOUND;
std::string v = tok.val;
if (v == "(" || v == "[" || v == "{") return LPAREN;
else if (v == ")" || v == "]" || v == "}") return RPAREN;
else if (v == ",") return COMMA;
else if (v == "!" || v == "~" || v == "not") return UNARY_OP;
else if (precedence(tok) > 0) return BINARY_OP;
else if (precedence(tok) < 0) return TOKEN_SPLITTER;
if (tok.val[0] != '"' && tok.val[0] != '\'') {
for (unsigned i = 0; i < tok.val.length(); i++) {
if (chartype(tok.val[i]) == SYMB) {
err("Invalid symbol: "+tok.val, tok.metadata);
}
}
}
return ALPHANUM;
}
// Converts to reverse polish notation
std::vector<Node> shuntingYard(std::vector<Node> tokens) {
std::vector<Node> iq;
for (int i = tokens.size() - 1; i >= 0; i--) {
iq.push_back(tokens[i]);
}
std::vector<Node> oq;
std::vector<Node> stack;
Node prev, tok;
int prevtyp = 0, toktyp = 0;
while (iq.size()) {
prev = tok;
prevtyp = toktyp;
tok = iq.back();
toktyp = toktype(tok);
iq.pop_back();
// Alphanumerics go straight to output queue
if (toktyp == ALPHANUM) {
oq.push_back(tok);
}
// Left parens go on stack and output queue
else if (toktyp == LPAREN) {
while (stack.size() && toktype(stack.back()) == TOKEN_SPLITTER) {
oq.push_back(stack.back());
stack.pop_back();
}
if (prevtyp != ALPHANUM && prevtyp != RPAREN) {
oq.push_back(token("id", tok.metadata));
}
stack.push_back(tok);
oq.push_back(tok);
}
// If rparen, keep moving from stack to output queue until lparen
else if (toktyp == RPAREN) {
while (stack.size() && toktype(stack.back()) != LPAREN) {
oq.push_back(stack.back());
stack.pop_back();
}
if (stack.size()) {
stack.pop_back();
}
oq.push_back(tok);
}
else if (toktyp == UNARY_OP) {
stack.push_back(tok);
}
// If token splitter, just push it to the stack
else if (toktyp == TOKEN_SPLITTER) {
while (stack.size() && toktype(stack.back()) == TOKEN_SPLITTER) {
oq.push_back(stack.back());
stack.pop_back();
}
stack.push_back(tok);
}
// If binary op, keep popping from stack while higher bedmas precedence
else if (toktyp == BINARY_OP) {
if (tok.val == "-" && prevtyp != ALPHANUM && prevtyp != RPAREN) {
stack.push_back(tok);
oq.push_back(token("0", tok.metadata));
}
else {
int prec = precedence(tok);
while (stack.size()
&& (toktype(stack.back()) == BINARY_OP
|| toktype(stack.back()) == UNARY_OP
|| toktype(stack.back()) == TOKEN_SPLITTER)
&& precedence(stack.back()) <= prec) {
oq.push_back(stack.back());
stack.pop_back();
}
stack.push_back(tok);
}
}
// Comma means finish evaluating the argument
else if (toktyp == COMMA) {
while (stack.size() && toktype(stack.back()) != LPAREN) {
oq.push_back(stack.back());
stack.pop_back();
}
}
}
while (stack.size()) {
oq.push_back(stack.back());
stack.pop_back();
}
return oq;
}
// Converts reverse polish notation into tree
Node treefy(std::vector<Node> stream) {
std::vector<Node> iq;
for (int i = stream.size() -1; i >= 0; i--) {
iq.push_back(stream[i]);
}
std::vector<Node> oq;
while (iq.size()) {
Node tok = iq.back();
iq.pop_back();
int typ = toktype(tok);
// If unary, take node off end of oq and wrap it with the operator
// If binary, do the same with two nodes
if (typ == UNARY_OP || typ == BINARY_OP || typ == TOKEN_SPLITTER) {
std::vector<Node> args;
int rounds = (typ == UNARY_OP) ? 1 : 2;
for (int i = 0; i < rounds; i++) {
if (oq.size() == 0) {
err("Line malformed, not enough args for "+tok.val,
tok.metadata);
}
args.push_back(oq.back());
oq.pop_back();
}
std::vector<Node> args2;
while (args.size()) {
args2.push_back(args.back());
args.pop_back();
}
oq.push_back(astnode(tok.val, args2, tok.metadata));
}
// If rparen, keep grabbing until we get to an lparen
else if (typ == RPAREN) {
std::vector<Node> args;
while (1) {
if (toktype(oq.back()) == LPAREN) break;
args.push_back(oq.back());
oq.pop_back();
if (!oq.size()) err("Bracket without matching", tok.metadata);
}
oq.pop_back();
args.push_back(oq.back());
oq.pop_back();
// We represent a[b] as (access a b)
if (tok.val == "]")
args.push_back(token("access", tok.metadata));
if (args.back().type == ASTNODE)
args.push_back(token("fun", tok.metadata));
std::string fun = args.back().val;
args.pop_back();
// We represent [1,2,3] as (array_lit 1 2 3)
if (fun == "access" && args.size() && args.back().val == "id") {
fun = "array_lit";
args.pop_back();
}
std::vector<Node> args2;
while (args.size()) {
args2.push_back(args.back());
args.pop_back();
}
// When evaluating 2 + (3 * 5), the shunting yard algo turns that
// into 2 ( id 3 5 * ) +, effectively putting "id" as a dummy
// function where the algo was expecting a function to call the
// thing inside the brackets. This reverses that step
if (fun == "id" && args2.size() == 1) {
oq.push_back(args2[0]);
}
else {
oq.push_back(astnode(fun, args2, tok.metadata));
}
}
else oq.push_back(tok);
// This is messy, but has to be done. Import/inset other files here
std::string v = oq.back().val;
if ((v == "inset" || v == "import" || v == "create")
&& oq.back().args.size() == 1
&& oq.back().args[0].type == TOKEN) {
int lastSlashPos = tok.metadata.file.rfind("/");
std::string root;
if (lastSlashPos >= 0)
root = tok.metadata.file.substr(0, lastSlashPos) + "/";
else
root = "";
std::string filename = oq.back().args[0].val;
filename = filename.substr(1, filename.length() - 2);
if (!exists(root + filename))
err("File does not exist: "+root + filename, tok.metadata);
oq.back().args.pop_back();
oq.back().args.push_back(parseSerpent(root + filename));
}
//Useful for debugging
//for (int i = 0; i < oq.size(); i++) {
// std::cerr << printSimple(oq[i]) << " ";
//}
//std::cerr << " <-\n";
}
// Output must have one argument
if (oq.size() == 0) {
err("Output blank", Metadata());
}
else if (oq.size() > 1) {
return asn("multi", oq, oq[0].metadata);
}
return oq[0];
}
// Parses one line of serpent
Node parseSerpentTokenStream(std::vector<Node> s) {
return treefy(shuntingYard(s));
}
// Count spaces at beginning of line
int spaceCount(std::string s) {
unsigned pos = 0;
while (pos < s.length() && (s[pos] == ' ' || s[pos] == '\t'))
pos++;
return pos;
}
// Is this a command that takes an argument on the same line?
bool bodied(std::string tok) {
return tok == "if" || tok == "elif" || tok == "while"
|| tok == "with" || tok == "def" || tok == "extern"
|| tok == "data" || tok == "assert" || tok == "return"
|| tok == "fun" || tok == "scope" || tok == "macro"
|| tok == "type";
}
// Are the two commands meant to continue each other?
bool bodiedContinued(std::string prev, std::string tok) {
return (prev == "if" && tok == "elif")
|| (prev == "elif" && tok == "else")
|| (prev == "elif" && tok == "elif")
|| (prev == "if" && tok == "else");
}
// Is a line of code empty?
bool isLineEmpty(std::string line) {
std::vector<Node> tokens = tokenize(line);
if (!tokens.size() || tokens[0].val == "#" || tokens[0].val == "//")
return true;
return false;
}
// Parse lines of serpent (helper function)
Node parseLines(std::vector<std::string> lines, Metadata metadata, int sp) {
std::vector<Node> o;
int origLine = metadata.ln;
unsigned i = 0;
while (i < lines.size()) {
metadata.ln = origLine + i;
std::string main = lines[i];
if (isLineEmpty(main)) {
i += 1;
continue;
}
int spaces = spaceCount(main);
if (spaces != sp) {
err("Indent mismatch", metadata);
}
// Tokenize current line
std::vector<Node> tokens = tokenize(main.substr(sp), metadata);
// Remove comments
std::vector<Node> tokens2;
for (unsigned j = 0; j < tokens.size(); j++) {
if (tokens[j].val == "#" || tokens[j].val == "//") break;
tokens2.push_back(tokens[j]);
}
bool expectingChildBlock = false;
if (tokens2.size() > 0 && tokens2.back().val == ":") {
tokens2.pop_back();
expectingChildBlock = true;
}
// Parse current line
Node out = parseSerpentTokenStream(tokens2);
// Parse child block
int childIndent = 999999;
std::vector<std::string> childBlock;
while (1) {
i++;
if (i >= lines.size())
break;
bool ile = isLineEmpty(lines[i]);
if (!ile) {
int spaces = spaceCount(lines[i]);
if (spaces <= sp) break;
childBlock.push_back(lines[i]);
if (spaces < childIndent) childIndent = spaces;
}
else childBlock.push_back("");
}
// Child block empty?
bool cbe = true;
for (unsigned i = 0; i < childBlock.size(); i++) {
if (childBlock[i].length() > 0) { cbe = false; break; }
}
// Add child block to AST
if (expectingChildBlock) {
if (cbe)
err("Expected indented child block!", out.metadata);
out.type = ASTNODE;
metadata.ln += 1;
out.args.push_back(parseLines(childBlock, metadata, childIndent));
metadata.ln -= 1;
}
else if (!cbe)
err("Did not expect indented child block!", out.metadata);
else if (out.args.size() && out.args[out.args.size() - 1].val == ":") {
Node n = out.args[out.args.size() - 1];
out.args.pop_back();
out.args.push_back(n.args[0]);
out.args.push_back(n.args[1]);
}
// Bring back if / elif into AST
if (bodied(tokens[0].val)) {
if (out.val != "multi") {
// token not being used in bodied form
}
else if (out.args[0].val == "id")
out = astnode(tokens[0].val, out.args[1].args, out.metadata);
else if (out.args[0].type == TOKEN) {
std::vector<Node> out2;
for (unsigned i = 1; i < out.args.size(); i++)
out2.push_back(out.args[i]);
out = astnode(tokens[0].val, out2, out.metadata);
}
else
out = astnode("fun", out.args, out.metadata);
}
// Multi not supported
if (out.val == "multi")
err("Multiple expressions or unclosed bracket", out.metadata);
// Convert top-level colon expressions into non-colon expressions;
// makes if statements and the like equivalent indented or not
//if (out.val == ":" && out.args[0].type == TOKEN)
// out = asn(out.args[0].val, out.args[1], out.metadata);
//if (bodied(tokens[0].val) && out.args[0].val == ":")
// out = asn(tokens[0].val, out.args[0].args);
if (o.size() == 0 || o.back().type == TOKEN) {
o.push_back(out);
continue;
}
// This is a little complicated. Basically, the idea here is to build
// constructions like [if [< x 5] [a] [elif [< x 10] [b] [else [c]]]]
std::vector<Node> u;
u.push_back(o.back());
if (bodiedContinued(o.back().val, out.val)) {
while (1) {
if (!bodiedContinued(u.back().val, out.val)) {
u.pop_back();
break;
}
if (!u.back().args.size()
|| !bodiedContinued(u.back().val, u.back().args.back().val)) {
break;
}
u.push_back(u.back().args.back());
}
u.back().args.push_back(out);
while (u.size() > 1) {
Node v = u.back();
u.pop_back();
u.back().args.pop_back();
u.back().args.push_back(v);
}
o.pop_back();
o.push_back(u[0]);
}
else o.push_back(out);
}
if (o.size() == 1)
return o[0];
else if (o.size())
return astnode("seq", o, o[0].metadata);
else
return astnode("seq", o, Metadata());
}
// Parses serpent code
Node parseSerpent(std::string s) {
std::string input = s;
std::string file = "main";
if (exists(s)) {
file = s;
input = get_file_contents(s);
}
return parseLines(splitLines(input), Metadata(file, 0, 0), 0);
}
using namespace std;

View File

@ -0,0 +1,13 @@
#ifndef ETHSERP_PARSER
#define ETHSERP_PARSER
#include <stdio.h>
#include <iostream>
#include <vector>
#include <map>
#include "util.h"
// Serpent text -> parse tree
Node parseSerpent(std::string s);
#endif

View File

@ -0,0 +1,299 @@
#include <stdio.h>
#include <iostream>
#include <vector>
#include <map>
#include "util.h"
#include "lllparser.h"
#include "bignum.h"
#include "rewriteutils.h"
#include "optimize.h"
#include "preprocess.h"
#include "functions.h"
#include "opcodes.h"
// Convert a function of the form (def (f x y z) (do stuff)) into
// (if (first byte of ABI is correct) (seq (setup x y z) (do stuff)))
Node convFunction(Node node, int functionCount) {
std::string prefix = "_temp"+mkUniqueToken()+"_";
Metadata m = node.metadata;
if (node.args.size() != 2)
err("Malformed def!", m);
// Collect the list of variable names and variable byte counts
Node unpack = unpackArguments(node.args[0].args, m);
// And the actual code
Node body = node.args[1];
// Main LLL-based function body
return astnode("if",
astnode("eq",
astnode("get", token("__funid", m), m),
token(unsignedToDecimal(functionCount), m),
m),
astnode("seq", unpack, body, m));
}
// Populate an svObj with the arguments needed to determine
// the storage position of a node
svObj getStorageVars(svObj pre, Node node, std::string prefix,
int index) {
Metadata m = node.metadata;
if (!pre.globalOffset.size()) pre.globalOffset = "0";
std::vector<Node> h;
std::vector<std::string> coefficients;
// Array accesses or atoms
if (node.val == "access" || node.type == TOKEN) {
std::string tot = "1";
h = listfyStorageAccess(node);
coefficients.push_back("1");
for (unsigned i = h.size() - 1; i >= 1; i--) {
// Array sizes must be constant or at least arithmetically
// evaluable at compile time
if (!isPureArithmetic(h[i]))
err("Array size must be fixed value", m);
// Create a list of the coefficient associated with each
// array index
coefficients.push_back(decimalMul(coefficients.back(), h[i].val));
}
}
// Tuples
else {
int startc;
// Handle the (fun <fun_astnode> args...) case
if (node.val == "fun") {
startc = 1;
h = listfyStorageAccess(node.args[0]);
}
// Handle the (<fun_name> args...) case, which
// the serpent parser produces when the function
// is a simple name and not a complex astnode
else {
startc = 0;
h = listfyStorageAccess(token(node.val, m));
}
svObj sub = pre;
sub.globalOffset = "0";
// Evaluate tuple elements recursively
for (unsigned i = startc; i < node.args.size(); i++) {
sub = getStorageVars(sub,
node.args[i],
prefix+h[0].val.substr(2)+".",
i-startc);
}
coefficients.push_back(sub.globalOffset);
for (unsigned i = h.size() - 1; i >= 1; i--) {
// Array sizes must be constant or at least arithmetically
// evaluable at compile time
if (!isPureArithmetic(h[i]))
err("Array size must be fixed value", m);
// Create a list of the coefficient associated with each
// array index
coefficients.push_back(decimalMul(coefficients.back(), h[i].val));
}
pre.offsets = sub.offsets;
pre.coefficients = sub.coefficients;
pre.nonfinal = sub.nonfinal;
pre.nonfinal[prefix+h[0].val.substr(2)] = true;
}
pre.coefficients[prefix+h[0].val.substr(2)] = coefficients;
pre.offsets[prefix+h[0].val.substr(2)] = pre.globalOffset;
pre.indices[prefix+h[0].val.substr(2)] = index;
if (decimalGt(tt176, coefficients.back()))
pre.globalOffset = decimalAdd(pre.globalOffset, coefficients.back());
return pre;
}
// Preprocess input containing functions
//
// localExterns is a map of the form, eg,
//
// { x: { foo: 0, bar: 1, baz: 2 }, y: { qux: 0, foo: 1 } ... }
//
// localExternSigs is a map of the form, eg,
//
// { x : { foo: iii, bar: iis, baz: ia }, y: { qux: i, foo: as } ... }
//
// Signifying that x.foo = 0, x.baz = 2, y.foo = 1, etc
// and that x.foo has three integers as arguments, x.bar has two
// integers and a variable-length string, and baz has an integer
// and an array
//
// globalExterns is a one-level map, eg from above
//
// { foo: 1, bar: 1, baz: 2, qux: 0 }
//
// globalExternSigs is a one-level map, eg from above
//
// { foo: as, bar: iis, baz: ia, qux: i}
//
// Note that globalExterns and globalExternSigs may be ambiguous
// Also, a null signature implies an infinite tail of integers
preprocessResult preprocessInit(Node inp) {
Metadata m = inp.metadata;
if (inp.val != "seq")
inp = astnode("seq", inp, m);
std::vector<Node> empty = std::vector<Node>();
Node init = astnode("seq", empty, m);
Node shared = astnode("seq", empty, m);
std::vector<Node> any;
std::vector<Node> functions;
preprocessAux out = preprocessAux();
out.localExterns["self"] = std::map<std::string, int>();
int functionCount = 0;
int storageDataCount = 0;
for (unsigned i = 0; i < inp.args.size(); i++) {
Node obj = inp.args[i];
// Functions
if (obj.val == "def") {
if (obj.args.size() == 0)
err("Empty def", m);
std::string funName = obj.args[0].val;
// Init, shared and any are special functions
if (funName == "init" || funName == "shared" || funName == "any") {
if (obj.args[0].args.size())
err(funName+" cannot have arguments", m);
}
if (funName == "init") init = obj.args[1];
else if (funName == "shared") shared = obj.args[1];
else if (funName == "any") any.push_back(obj.args[1]);
else {
// Other functions
functions.push_back(convFunction(obj, functionCount));
out.localExterns["self"][obj.args[0].val] = functionCount;
out.localExternSigs["self"][obj.args[0].val]
= getSignature(obj.args[0].args);
functionCount++;
}
}
// Extern declarations
else if (obj.val == "extern") {
std::string externName = obj.args[0].val;
Node al = obj.args[1];
if (!out.localExterns.count(externName))
out.localExterns[externName] = std::map<std::string, int>();
for (unsigned i = 0; i < al.args.size(); i++) {
if (al.args[i].val == ":") {
std::string v = al.args[i].args[0].val;
std::string sig = al.args[i].args[1].val;
out.globalExterns[v] = i;
out.globalExternSigs[v] = sig;
out.localExterns[externName][v] = i;
out.localExternSigs[externName][v] = sig;
}
else {
std::string v = al.args[i].val;
out.globalExterns[v] = i;
out.globalExternSigs[v] = "";
out.localExterns[externName][v] = i;
out.localExternSigs[externName][v] = "";
}
}
}
// Custom macros
else if (obj.val == "macro") {
// Rules for valid macros:
//
// There are only four categories of valid macros:
//
// 1. a macro where the outer function is something
// which is NOT an existing valid function/extern/datum
// 2. a macro of the form set(c(x), d) where c must NOT
// be an existing valid function/extern/datum
// 3. something of the form access(c(x)), where c must NOT
// be an existing valid function/extern/datum
// 4. something of the form set(access(c(x)), d) where c must
// NOT be an existing valid function/extern/datum
bool valid = false;
Node pattern = obj.args[0];
Node substitution = obj.args[1];
if (opcode(pattern.val) < 0 && !isValidFunctionName(pattern.val))
valid = true;
if (pattern.val == "set" &&
opcode(pattern.args[0].val) < 0 &&
!isValidFunctionName(pattern.args[0].val))
valid = true;
if (pattern.val == "access" &&
opcode(pattern.args[0].val) < 0 &&
!isValidFunctionName(pattern.args[0].val))
if (pattern.val == "set" &&
pattern.args[0].val == "access" &&
opcode(pattern.args[0].args[0].val) < 0 &&
!isValidFunctionName(pattern.args[0].args[0].val))
valid = true;
if (valid) {
out.customMacros.push_back(rewriteRule(pattern, substitution));
}
}
// Variable types
else if (obj.val == "type") {
std::string typeName = obj.args[0].val;
std::vector<Node> vars = obj.args[1].args;
for (unsigned i = 0; i < vars.size(); i++)
out.types[vars[i].val] = typeName;
}
// Storage variables/structures
else if (obj.val == "data") {
out.storageVars = getStorageVars(out.storageVars,
obj.args[0],
"",
storageDataCount);
storageDataCount += 1;
}
else any.push_back(obj);
}
std::vector<Node> main;
if (shared.args.size()) main.push_back(shared);
if (init.args.size()) main.push_back(init);
std::vector<Node> code;
if (shared.args.size()) code.push_back(shared);
for (unsigned i = 0; i < any.size(); i++)
code.push_back(any[i]);
for (unsigned i = 0; i < functions.size(); i++)
code.push_back(functions[i]);
Node codeNode;
if (functions.size() > 0) {
codeNode = astnode("with",
token("__funid", m),
astnode("byte",
token("0", m),
astnode("calldataload", token("0", m), m),
m),
astnode("seq", code, m),
m);
}
else codeNode = astnode("seq", code, m);
main.push_back(astnode("~return",
token("0", m),
astnode("lll",
codeNode,
token("0", m),
m),
m));
Node result;
if (main.size() == 1) result = main[0];
else result = astnode("seq", main, inp.metadata);
return preprocessResult(result, out);
}
preprocessResult processTypes (preprocessResult pr) {
preprocessAux aux = pr.second;
Node node = pr.first;
if (node.type == TOKEN && aux.types.count(node.val)) {
node = asn(aux.types[node.val], node, node.metadata);
}
else if (node.val == "untyped")
return preprocessResult(node.args[0], aux);
else {
for (unsigned i = 0; i < node.args.size(); i++) {
node.args[i] =
processTypes(preprocessResult(node.args[i], aux)).first;
}
}
return preprocessResult(node, aux);
}
preprocessResult preprocess(Node n) {
return processTypes(preprocessInit(n));
}

View File

@ -0,0 +1,58 @@
#ifndef ETHSERP_PREPROCESSOR
#define ETHSERP_PREPROCESSOR
#include <stdio.h>
#include <iostream>
#include <vector>
#include <map>
#include "util.h"
// Storage variable index storing object
struct svObj {
std::map<std::string, std::string> offsets;
std::map<std::string, int> indices;
std::map<std::string, std::vector<std::string> > coefficients;
std::map<std::string, bool> nonfinal;
std::string globalOffset;
};
class rewriteRule {
public:
rewriteRule(Node p, Node s) {
pattern = p;
substitution = s;
}
Node pattern;
Node substitution;
};
// Preprocessing result storing object
class preprocessAux {
public:
preprocessAux() {
globalExterns = std::map<std::string, int>();
localExterns = std::map<std::string, std::map<std::string, int> >();
localExterns["self"] = std::map<std::string, int>();
}
std::map<std::string, int> globalExterns;
std::map<std::string, std::string> globalExternSigs;
std::map<std::string, std::map<std::string, int> > localExterns;
std::map<std::string, std::map<std::string, std::string> > localExternSigs;
std::vector<rewriteRule> customMacros;
std::map<std::string, std::string> types;
svObj storageVars;
};
#define preprocessResult std::pair<Node, preprocessAux>
// Populate an svObj with the arguments needed to determine
// the storage position of a node
svObj getStorageVars(svObj pre, Node node, std::string prefix="",
int index=0);
// Preprocess a function (see cpp for details)
preprocessResult preprocess(Node inp);
#endif

View File

@ -0,0 +1,173 @@
#include <Python.h>
#include "structmember.h"
#include <stdlib.h>
#include <stdio.h>
#include <iostream>
#include "funcs.h"
#define PYMETHOD(name, FROM, method, TO) \
static PyObject * name(PyObject *, PyObject *args) { \
try { \
FROM(med) \
return TO(method(med)); \
} \
catch (std::string e) { \
PyErr_SetString(PyExc_Exception, e.c_str()); \
return NULL; \
} \
}
#define FROMSTR(v) \
const char *command; \
int len; \
if (!PyArg_ParseTuple(args, "s#", &command, &len)) \
return NULL; \
std::string v = std::string(command, len); \
#define FROMNODE(v) \
PyObject *node; \
if (!PyArg_ParseTuple(args, "O", &node)) \
return NULL; \
Node v = cppifyNode(node);
#define FROMLIST(v) \
PyObject *node; \
if (!PyArg_ParseTuple(args, "O", &node)) \
return NULL; \
std::vector<Node> v = cppifyNodeList(node);
// Convert metadata into python wrapper form [file, ln, ch]
PyObject* pyifyMetadata(Metadata m) {
PyObject* a = PyList_New(0);
PyList_Append(a, Py_BuildValue("s#", m.file.c_str(), m.file.length()));
PyList_Append(a, Py_BuildValue("i", m.ln));
PyList_Append(a, Py_BuildValue("i", m.ch));
return a;
}
// Convert node into python wrapper form
// [token=0/astnode=1, val, metadata, args]
PyObject* pyifyNode(Node n) {
PyObject* a = PyList_New(0);
PyList_Append(a, Py_BuildValue("i", n.type == ASTNODE));
PyList_Append(a, Py_BuildValue("s#", n.val.c_str(), n.val.length()));
PyList_Append(a, pyifyMetadata(n.metadata));
for (unsigned i = 0; i < n.args.size(); i++)
PyList_Append(a, pyifyNode(n.args[i]));
return a;
}
// Convert string into python wrapper form
PyObject* pyifyString(std::string s) {
return Py_BuildValue("s#", s.c_str(), s.length());
}
// Convert list of nodes into python wrapper form
PyObject* pyifyNodeList(std::vector<Node> n) {
PyObject* a = PyList_New(0);
for (unsigned i = 0; i < n.size(); i++)
PyList_Append(a, pyifyNode(n[i]));
return a;
}
// Convert pyobject int into normal form
int cppifyInt(PyObject* o) {
int out;
if (!PyArg_Parse(o, "i", &out))
err("Argument should be integer", Metadata());
return out;
}
// Convert pyobject string into normal form
std::string cppifyString(PyObject* o) {
const char *command;
if (!PyArg_Parse(o, "s", &command))
err("Argument should be string", Metadata());
return std::string(command);
}
// Convert metadata from python wrapper form
Metadata cppifyMetadata(PyObject* o) {
std::string file = cppifyString(PyList_GetItem(o, 0));
int ln = cppifyInt(PyList_GetItem(o, 1));
int ch = cppifyInt(PyList_GetItem(o, 2));
return Metadata(file, ln, ch);
}
// Convert node from python wrapper form
Node cppifyNode(PyObject* o) {
Node n;
int isAstNode = cppifyInt(PyList_GetItem(o, 0));
n.type = isAstNode ? ASTNODE : TOKEN;
n.val = cppifyString(PyList_GetItem(o, 1));
n.metadata = cppifyMetadata(PyList_GetItem(o, 2));
std::vector<Node> args;
for (int i = 3; i < PyList_Size(o); i++) {
args.push_back(cppifyNode(PyList_GetItem(o, i)));
}
n.args = args;
return n;
}
//Convert list of nodes into normal form
std::vector<Node> cppifyNodeList(PyObject* o) {
std::vector<Node> out;
for (int i = 0; i < PyList_Size(o); i++) {
out.push_back(cppifyNode(PyList_GetItem(o,i)));
}
return out;
}
PYMETHOD(ps_compile, FROMSTR, compile, pyifyString)
PYMETHOD(ps_compile_chunk, FROMSTR, compileChunk, pyifyString)
PYMETHOD(ps_compile_to_lll, FROMSTR, compileToLLL, pyifyNode)
PYMETHOD(ps_compile_chunk_to_lll, FROMSTR, compileChunkToLLL, pyifyNode)
PYMETHOD(ps_compile_lll, FROMNODE, compileLLL, pyifyString)
PYMETHOD(ps_parse, FROMSTR, parseSerpent, pyifyNode)
PYMETHOD(ps_rewrite, FROMNODE, rewrite, pyifyNode)
PYMETHOD(ps_rewrite_chunk, FROMNODE, rewriteChunk, pyifyNode)
PYMETHOD(ps_pretty_compile, FROMSTR, prettyCompile, pyifyNodeList)
PYMETHOD(ps_pretty_compile_chunk, FROMSTR, prettyCompileChunk, pyifyNodeList)
PYMETHOD(ps_pretty_compile_lll, FROMNODE, prettyCompileLLL, pyifyNodeList)
PYMETHOD(ps_serialize, FROMLIST, serialize, pyifyString)
PYMETHOD(ps_deserialize, FROMSTR, deserialize, pyifyNodeList)
PYMETHOD(ps_parse_lll, FROMSTR, parseLLL, pyifyNode)
static PyMethodDef PyextMethods[] = {
{"compile", ps_compile, METH_VARARGS,
"Compile code."},
{"compile_chunk", ps_compile_chunk, METH_VARARGS,
"Compile code chunk (no wrappers)."},
{"compile_to_lll", ps_compile_to_lll, METH_VARARGS,
"Compile code to LLL."},
{"compile_chunk_to_lll", ps_compile_chunk_to_lll, METH_VARARGS,
"Compile code chunk to LLL (no wrappers)."},
{"compile_lll", ps_compile_lll, METH_VARARGS,
"Compile LLL to EVM."},
{"parse", ps_parse, METH_VARARGS,
"Parse serpent"},
{"rewrite", ps_rewrite, METH_VARARGS,
"Rewrite parsed serpent to LLL"},
{"rewrite_chunk", ps_rewrite_chunk, METH_VARARGS,
"Rewrite parsed serpent to LLL (no wrappers)"},
{"pretty_compile", ps_pretty_compile, METH_VARARGS,
"Compile to EVM opcodes"},
{"pretty_compile_chunk", ps_pretty_compile_chunk, METH_VARARGS,
"Compile chunk to EVM opcodes (no wrappers)"},
{"pretty_compile_lll", ps_pretty_compile_lll, METH_VARARGS,
"Compile LLL to EVM opcodes"},
{"serialize", ps_serialize, METH_VARARGS,
"Convert EVM opcodes to bin"},
{"deserialize", ps_deserialize, METH_VARARGS,
"Convert EVM bin to opcodes"},
{"parse_lll", ps_parse_lll, METH_VARARGS,
"Parse LLL"},
{NULL, NULL, 0, NULL} /* Sentinel */
};
PyMODINIT_FUNC initserpent_pyext(void)
{
Py_InitModule( "serpent_pyext", PyextMethods );
}

View File

@ -0,0 +1 @@
from serpent import *

View File

@ -0,0 +1,804 @@
#include <stdio.h>
#include <iostream>
#include <vector>
#include <map>
#include "util.h"
#include "lllparser.h"
#include "bignum.h"
#include "optimize.h"
#include "rewriteutils.h"
#include "preprocess.h"
#include "functions.h"
#include "opcodes.h"
// Rewrite rules
std::string macros[][2] = {
{
"(seq $x)",
"$x"
},
{
"(seq (seq) $x)",
"$x"
},
{
"(+= $a $b)",
"(set $a (+ $a $b))"
},
{
"(*= $a $b)",
"(set $a (* $a $b))"
},
{
"(-= $a $b)",
"(set $a (- $a $b))"
},
{
"(/= $a $b)",
"(set $a (/ $a $b))"
},
{
"(%= $a $b)",
"(set $a (% $a $b))"
},
{
"(^= $a $b)",
"(set $a (^ $a $b))"
},
{
"(!= $a $b)",
"(iszero (eq $a $b))"
},
{
"(assert $x)",
"(unless $x (stop))"
},
{
"(min $a $b)",
"(with $1 $a (with $2 $b (if (lt $1 $2) $1 $2)))"
},
{
"(max $a $b)",
"(with $1 $a (with $2 $b (if (lt $1 $2) $2 $1)))"
},
{
"(smin $a $b)",
"(with $1 $a (with $2 $b (if (slt $1 $2) $1 $2)))"
},
{
"(smax $a $b)",
"(with $1 $a (with $2 $b (if (slt $1 $2) $2 $1)))"
},
{
"(if $cond $do (else $else))",
"(if $cond $do $else)"
},
{
"(code $code)",
"$code"
},
{
"(slice $arr $pos)",
"(add $arr (mul 32 $pos))",
},
{
"(array $len)",
"(alloc (mul 32 $len))"
},
{
"(while $cond $do)",
"(until (iszero $cond) $do)",
},
{
"(while (iszero $cond) $do)",
"(until $cond $do)",
},
{
"(if $cond $do)",
"(unless (iszero $cond) $do)",
},
{
"(if (iszero $cond) $do)",
"(unless $cond $do)",
},
{
"(access (. self storage) $ind)",
"(sload $ind)"
},
{
"(access $var $ind)",
"(mload (add $var (mul 32 $ind)))"
},
{
"(set (access (. self storage) $ind) $val)",
"(sstore $ind $val)"
},
{
"(set (access $var $ind) $val)",
"(mstore (add $var (mul 32 $ind)) $val)"
},
{
"(getch $var $ind)",
"(mod (mload (sub (add $var $ind) 31)) 256)"
},
{
"(setch $var $ind $val)",
"(mstore8 (add $var $ind) $val)",
},
{
"(send $to $value)",
"(~call (sub (gas) 25) $to $value 0 0 0 0)"
},
{
"(send $gas $to $value)",
"(~call $gas $to $value 0 0 0 0)"
},
{
"(sha3 $x)",
"(seq (set $1 $x) (~sha3 (ref $1) 32))"
},
{
"(sha3 $mstart (= chars $msize))",
"(~sha3 $mstart $msize)"
},
{
"(sha3 $mstart $msize)",
"(~sha3 $mstart (mul 32 $msize))"
},
{
"(id $0)",
"$0"
},
{
"(return $x)",
"(seq (set $1 $x) (~return (ref $1) 32))"
},
{
"(return $mstart (= chars $msize))",
"(~return $mstart $msize)"
},
{
"(return $start $len)",
"(~return $start (mul 32 $len))"
},
{
"(&& $x $y)",
"(if $x $y 0)"
},
{
"(|| $x $y)",
"(with $1 $x (if $1 $1 $y))"
},
{
"(>= $x $y)",
"(iszero (slt $x $y))"
},
{
"(<= $x $y)",
"(iszero (sgt $x $y))"
},
{
"(create $code)",
"(create 0 $code)"
},
{
"(create $endowment $code)",
"(with $1 (msize) (create $endowment (get $1) (lll (outer $code) (msize))))"
},
{
"(sha256 $x)",
"(with $1 (alloc 64) (seq (mstore (add (get $1) 32) $x) (pop (~call 101 2 0 (add (get $1) 32) 32 (get $1) 32)) (mload (get $1))))"
},
{
"(sha256 $arr (= chars $sz))",
"(with $1 (alloc 32) (seq (pop (~call 101 2 0 $arr $sz (get $1) 32)) (mload (get $1))))"
},
{
"(sha256 $arr $sz)",
"(with $1 (alloc 32) (seq (pop (~call 101 2 0 $arr (mul 32 $sz) (get $1) 32)) (mload (get $1))))"
},
{
"(ripemd160 $x)",
"(with $1 (alloc 64) (seq (mstore (add (get $1) 32) $x) (pop (~call 101 3 0 (add (get $1) 32) 32 (get $1) 32)) (mload (get $1))))"
},
{
"(ripemd160 $arr (= chars $sz))",
"(with $1 (alloc 32) (seq (pop (~call 101 3 0 $arr $sz (mload $1) 32)) (mload (get $1))))"
},
{
"(ripemd160 $arr $sz)",
"(with $1 (alloc 32) (seq (pop (~call 101 3 0 $arr (mul 32 $sz) (get $1) 32)) (mload (get $1))))"
},
{
"(ecrecover $h $v $r $s)",
"(with $1 (alloc 160) (seq (mstore (get $1) $h) (mstore (add (get $1) 32) $v) (mstore (add (get $1) 64) $r) (mstore (add (get $1) 96) $s) (pop (~call 101 1 0 (get $1) 128 (add (get $1 128)) 32)) (mload (add (get $1) 128))))"
},
{
"(inset $x)",
"$x"
},
{
"(create $x)",
"(with $1 (msize) (create $val (get $1) (lll $code (get $1))))"
},
{
"(with (= $var $val) $cond)",
"(with $var $val $cond)"
},
{
"(log $t1)",
"(~log1 0 0 $t1)"
},
{
"(log $t1 $t2)",
"(~log2 0 0 $t1 $t2)"
},
{
"(log $t1 $t2 $t3)",
"(~log3 0 0 $t1 $t2 $t3)"
},
{
"(log $t1 $t2 $t3 $t4)",
"(~log4 0 0 $t1 $t2 $t3 $t4)"
},
{
"(logarr $a $sz)",
"(~log0 $a (mul 32 $sz))"
},
{
"(logarr $a $sz $t1)",
"(~log1 $a (mul 32 $sz) $t1)"
},
{
"(logarr $a $sz $t1 $t2)",
"(~log2 $a (mul 32 $sz) $t1 $t2)"
},
{
"(logarr $a $sz $t1 $t2 $t3)",
"(~log3 $a (mul 32 $sz) $t1 $t2 $t3)"
},
{
"(logarr $a $sz $t1 $t2 $t3 $t4)",
"(~log4 $a (mul 32 $sz) $t1 $t2 $t3 $t4)"
},
{
"(save $loc $array (= chars $count))",
"(with $location (ref $loc) (with $c $count (with $end (div $c 32) (with $i 0 (seq (while (slt $i $end) (seq (sstore (add $i $location) (access $array $i)) (set $i (add $i 1)))) (sstore (add $i $location) (~and (access $array $i) (sub 0 (exp 256 (sub 32 (mod $c 32)))))))))))"
},
{
"(save $loc $array $count)",
"(with $location (ref $loc) (with $end $count (with $i 0 (while (slt $i $end) (seq (sstore (add $i $location) (access $array $i)) (set $i (add $i 1)))))))"
},
{
"(load $loc (= chars $count))",
"(with $location (ref $loc) (with $c $count (with $a (alloc $c) (with $i 0 (seq (while (slt $i (div $c 32)) (seq (set (access $a $i) (sload (add $location $i))) (set $i (add $i 1)))) (set (access $a $i) (~and (sload (add $location $i)) (sub 0 (exp 256 (sub 32 (mod $c 32)))))) $a)))))"
},
{
"(load $loc $count)",
"(with $location (ref $loc) (with $c $count (with $a (alloc $c) (with $i 0 (seq (while (slt $i $c) (seq (set (access $a $i) (sload (add $location $i))) (set $i (add $i 1)))) $a)))))"
},
{
"(unsafe_mcopy $to $from $sz)",
"(with _sz $sz (with _from $from (with _to $to (seq (comment STARTING UNSAFE MCOPY) (with _i 0 (while (lt _i _sz) (seq (mstore (add $to _i) (mload (add _from _i))) (set _i (add _i 32)))))))))"
},
{
"(mcopy $to $from $_sz)",
"(with _to $to (with _from $from (with _sz $sz (seq (comment STARTING MCOPY (with _i 0 (seq (while (lt (add _i 31) _sz) (seq (mstore (add _to _i) (mload (add _from _i))) (set _i (add _i 32)))) (with _mask (exp 256 (sub 32 (mod _sz 32))) (mstore (add $to _i) (add (mod (mload (add $to _i)) _mask) (and (mload (add $from _i)) (sub 0 _mask))))))))))))"
},
{ "(. msg sender)", "(caller)" },
{ "(. msg value)", "(callvalue)" },
{ "(. tx gasprice)", "(gasprice)" },
{ "(. tx origin)", "(origin)" },
{ "(. tx gas)", "(gas)" },
{ "(. $x balance)", "(balance $x)" },
{ "self", "(address)" },
{ "(. block prevhash)", "(prevhash)" },
{ "(. block coinbase)", "(coinbase)" },
{ "(. block timestamp)", "(timestamp)" },
{ "(. block number)", "(number)" },
{ "(. block difficulty)", "(difficulty)" },
{ "(. block gaslimit)", "(gaslimit)" },
{ "stop", "(stop)" },
{ "---END---", "" } //Keep this line at the end of the list
};
std::vector<rewriteRule> nodeMacros;
// Token synonyms
std::string synonyms[][2] = {
{ "or", "||" },
{ "and", "&&" },
{ "|", "~or" },
{ "&", "~and" },
{ "elif", "if" },
{ "!", "iszero" },
{ "~", "~not" },
{ "not", "iszero" },
{ "string", "alloc" },
{ "+", "add" },
{ "-", "sub" },
{ "*", "mul" },
{ "/", "sdiv" },
{ "^", "exp" },
{ "**", "exp" },
{ "%", "smod" },
{ "<", "slt" },
{ ">", "sgt" },
{ "=", "set" },
{ "==", "eq" },
{ ":", "kv" },
{ "---END---", "" } //Keep this line at the end of the list
};
// Custom setters (need to be registered separately
// for use with managed storage)
std::string setters[][2] = {
{ "+=", "+" },
{ "-=", "-" },
{ "*=", "*" },
{ "/=", "/" },
{ "%=", "%" },
{ "^=", "^" },
{ "---END---", "" } //Keep this line at the end of the list
};
// Processes mutable array literals
Node array_lit_transform(Node node) {
std::string prefix = "_temp"+mkUniqueToken() + "_";
Metadata m = node.metadata;
std::map<std::string, Node> d;
std::string o = "(seq (set $arr (alloc "+utd(node.args.size()*32)+"))";
for (unsigned i = 0; i < node.args.size(); i++) {
o += " (mstore (add (get $arr) "+utd(i * 32)+") $"+utd(i)+")";
d[utd(i)] = node.args[i];
}
o += " (get $arr))";
return subst(parseLLL(o), d, prefix, m);
}
Node apply_rules(preprocessResult pr);
// Transform "<variable>.<fun>(args...)" into
// a call
Node dotTransform(Node node, preprocessAux aux) {
Metadata m = node.metadata;
// We're gonna make lots of temporary variables,
// so set up a unique flag for them
std::string prefix = "_temp"+mkUniqueToken()+"_";
// Check that the function name is a token
if (node.args[0].args[1].type == ASTNODE)
err("Function name must be static", m);
Node dotOwner = node.args[0].args[0];
std::string dotMember = node.args[0].args[1].val;
// kwargs = map of special arguments
std::map<std::string, Node> kwargs;
kwargs["value"] = token("0", m);
kwargs["gas"] = subst(parseLLL("(- (gas) 25)"), msn(), prefix, m);
// Search for as=? and call=code keywords, and isolate the actual
// function arguments
std::vector<Node> fnargs;
std::string as = "";
std::string op = "call";
for (unsigned i = 1; i < node.args.size(); i++) {
fnargs.push_back(node.args[i]);
Node arg = fnargs.back();
if (arg.val == "=" || arg.val == "set") {
if (arg.args[0].val == "as")
as = arg.args[1].val;
if (arg.args[0].val == "call" && arg.args[1].val == "code")
op = "callcode";
if (arg.args[0].val == "gas")
kwargs["gas"] = arg.args[1];
if (arg.args[0].val == "value")
kwargs["value"] = arg.args[1];
if (arg.args[0].val == "outsz")
kwargs["outsz"] = arg.args[1];
}
}
if (dotOwner.val == "self") {
if (as.size()) err("Cannot use \"as\" when calling self!", m);
as = dotOwner.val;
}
// Determine the funId and sig assuming the "as" keyword was used
int funId = 0;
std::string sig;
if (as.size() > 0 && aux.localExterns.count(as)) {
if (!aux.localExterns[as].count(dotMember))
err("Invalid call: "+printSimple(dotOwner)+"."+dotMember, m);
funId = aux.localExterns[as][dotMember];
sig = aux.localExternSigs[as][dotMember];
}
// Determine the funId and sig otherwise
else if (!as.size()) {
if (!aux.globalExterns.count(dotMember))
err("Invalid call: "+printSimple(dotOwner)+"."+dotMember, m);
std::string key = unsignedToDecimal(aux.globalExterns[dotMember]);
funId = aux.globalExterns[dotMember];
sig = aux.globalExternSigs[dotMember];
}
else err("Invalid call: "+printSimple(dotOwner)+"."+dotMember, m);
// Pack arguments
kwargs["data"] = packArguments(fnargs, sig, funId, m);
kwargs["to"] = dotOwner;
Node main;
// Pack output
if (!kwargs.count("outsz")) {
main = parseLLL(
"(with _data $data (seq "
"(pop (~"+op+" $gas $to $value (access _data 0) (access _data 1) (ref $dataout) 32))"
"(get $dataout)))");
}
else {
main = parseLLL(
"(with _data $data (with _outsz (mul 32 $outsz) (with _out (alloc _outsz) (seq "
"(pop (~"+op+" $gas $to $value (access _data 0) (access _data 1) _out _outsz))"
"(get _out)))))");
}
// Set up main call
Node o = subst(main, kwargs, prefix, m);
return o;
}
// Transform an access of the form self.bob, self.users[5], etc into
// a storage access
//
// There exist two types of objects: finite objects, and infinite
// objects. Finite objects are packed optimally tightly into storage
// accesses; for example:
//
// data obj[100](a, b[2][4], c)
//
// obj[0].a -> 0
// obj[0].b[0][0] -> 1
// obj[0].b[1][3] -> 8
// obj[45].c -> 459
//
// Infinite objects are accessed by sha3([v1, v2, v3 ... ]), where
// the values are a list of array indices and keyword indices, for
// example:
// data obj[](a, b[2][4], c)
// data obj2[](a, b[][], c)
//
// obj[0].a -> sha3([0, 0, 0])
// obj[5].b[1][3] -> sha3([0, 5, 1, 1, 3])
// obj[45].c -> sha3([0, 45, 2])
// obj2[0].a -> sha3([1, 0, 0])
// obj2[5].b[1][3] -> sha3([1, 5, 1, 1, 3])
// obj2[45].c -> sha3([1, 45, 2])
Node storageTransform(Node node, preprocessAux aux,
bool mapstyle=false, bool ref=false) {
Metadata m = node.metadata;
// Get a list of all of the "access parameters" used in order
// eg. self.users[5].cow[4][m[2]][woof] ->
// [--self, --users, 5, --cow, 4, m[2], woof]
std::vector<Node> hlist = listfyStorageAccess(node);
// For infinite arrays, the terms array will just provide a list
// of indices. For finite arrays, it's a list of index*coefficient
std::vector<Node> terms;
std::string offset = "0";
std::string prefix = "";
std::string varPrefix = "_temp"+mkUniqueToken()+"_";
int c = 0;
std::vector<std::string> coefficients;
coefficients.push_back("");
for (unsigned i = 1; i < hlist.size(); i++) {
// We pre-add the -- flag to parameter-like terms. For example,
// self.users[m] -> [--self, --users, m]
// self.users.m -> [--self, --users, --m]
if (hlist[i].val.substr(0, 2) == "--") {
prefix += hlist[i].val.substr(2) + ".";
std::string tempPrefix = prefix.substr(0, prefix.size()-1);
if (!aux.storageVars.offsets.count(tempPrefix))
return node;
if (c < (signed)coefficients.size() - 1)
err("Too few array index lookups", m);
if (c > (signed)coefficients.size() - 1)
err("Too many array index lookups", m);
coefficients = aux.storageVars.coefficients[tempPrefix];
// If the size of an object exceeds 2^176, we make it an infinite
// array
if (decimalGt(coefficients.back(), tt176) && !mapstyle)
return storageTransform(node, aux, true, ref);
offset = decimalAdd(offset, aux.storageVars.offsets[tempPrefix]);
c = 0;
if (mapstyle)
terms.push_back(token(unsignedToDecimal(
aux.storageVars.indices[tempPrefix])));
}
else if (mapstyle) {
terms.push_back(hlist[i]);
c += 1;
}
else {
if (c > (signed)coefficients.size() - 2)
err("Too many array index lookups", m);
terms.push_back(
astnode("mul",
hlist[i],
token(coefficients[coefficients.size() - 2 - c], m),
m));
c += 1;
}
}
if (aux.storageVars.nonfinal.count(prefix.substr(0, prefix.size()-1)))
err("Storage variable access not deep enough", m);
if (c < (signed)coefficients.size() - 1) {
err("Too few array index lookups", m);
}
if (c > (signed)coefficients.size() - 1) {
err("Too many array index lookups", m);
}
Node o;
if (mapstyle) {
std::string t = "_temp_"+mkUniqueToken();
std::vector<Node> sub;
for (unsigned i = 0; i < terms.size(); i++)
sub.push_back(asn("mstore",
asn("add",
tkn(utd(i * 32), m),
asn("get", tkn(t+"pos", m), m),
m),
terms[i],
m));
sub.push_back(tkn(t+"pos", m));
Node main = asn("with",
tkn(t+"pos", m),
asn("alloc", tkn(utd(terms.size() * 32), m), m),
asn("seq", sub, m),
m);
Node sz = token(utd(terms.size() * 32), m);
o = astnode("~sha3",
main,
sz,
m);
}
else {
// We add up all the index*coefficients
Node out = token(offset, node.metadata);
for (unsigned i = 0; i < terms.size(); i++) {
std::vector<Node> temp;
temp.push_back(out);
temp.push_back(terms[i]);
out = astnode("add", temp, node.metadata);
}
o = out;
}
if (ref) return o;
else return astnode("sload", o, node.metadata);
}
// Recursively applies rewrite rules
std::pair<Node, bool> apply_rules_iter(preprocessResult pr) {
bool changed = false;
Node node = pr.first;
// If the rewrite rules have not yet been parsed, parse them
if (!nodeMacros.size()) {
for (int i = 0; i < 9999; i++) {
std::vector<Node> o;
if (macros[i][0] == "---END---") break;
nodeMacros.push_back(rewriteRule(
parseLLL(macros[i][0]),
parseLLL(macros[i][1])
));
}
}
// Assignment transformations
for (int i = 0; i < 9999; i++) {
if (setters[i][0] == "---END---") break;
if (node.val == setters[i][0]) {
node = astnode("=",
node.args[0],
astnode(setters[i][1],
node.args[0],
node.args[1],
node.metadata),
node.metadata);
}
}
// Do nothing to macros
if (node.val == "macro") {
return std::pair<Node, bool>(node, changed);
}
// Ignore comments
if (node.val == "comment") {
return std::pair<Node, bool>(node, changed);
}
// Special storage transformation
if (isNodeStorageVariable(node)) {
node = storageTransform(node, pr.second);
changed = true;
}
if (node.val == "ref" && isNodeStorageVariable(node.args[0])) {
node = storageTransform(node.args[0], pr.second, false, true);
changed = true;
}
if (node.val == "=" && isNodeStorageVariable(node.args[0])) {
Node t = storageTransform(node.args[0], pr.second);
if (t.val == "sload") {
std::vector<Node> o;
o.push_back(t.args[0]);
o.push_back(node.args[1]);
node = astnode("sstore", o, node.metadata);
}
changed = true;
}
// Main code
unsigned pos = 0;
std::string prefix = "_temp"+mkUniqueToken()+"_";
while(1) {
if (synonyms[pos][0] == "---END---") {
break;
}
else if (node.type == ASTNODE && node.val == synonyms[pos][0]) {
node.val = synonyms[pos][1];
changed = true;
}
pos++;
}
for (pos = 0; pos < nodeMacros.size() + pr.second.customMacros.size(); pos++) {
rewriteRule macro = pos < nodeMacros.size()
? nodeMacros[pos]
: pr.second.customMacros[pos - nodeMacros.size()];
matchResult mr = match(macro.pattern, node);
if (mr.success) {
node = subst(macro.substitution, mr.map, prefix, node.metadata);
std::pair<Node, bool> o =
apply_rules_iter(preprocessResult(node, pr.second));
o.second = true;
return o;
}
}
// Special transformations
if (node.val == "outer") {
node = apply_rules(preprocess(node.args[0]));
changed = true;
}
if (node.val == "array_lit") {
node = array_lit_transform(node);
changed = true;
}
if (node.val == "fun" && node.args[0].val == ".") {
node = dotTransform(node, pr.second);
changed = true;
}
if (node.type == ASTNODE) {
unsigned i = 0;
if (node.val == "set" || node.val == "ref"
|| node.val == "get" || node.val == "with") {
if (node.args[0].val.size() > 0 && node.args[0].val[0] != '\''
&& node.args[0].type == TOKEN && node.args[0].val[0] != '$') {
node.args[0].val = "'" + node.args[0].val;
changed = true;
}
i = 1;
}
else if (node.val == "arglen") {
node.val = "get";
node.args[0].val = "'_len_" + node.args[0].val;
i = 1;
changed = true;
}
for (; i < node.args.size(); i++) {
std::pair<Node, bool> r =
apply_rules_iter(preprocessResult(node.args[i], pr.second));
node.args[i] = r.first;
changed = changed || r.second;
}
}
else if (node.type == TOKEN && !isNumberLike(node)) {
if (node.val.size() >= 2
&& node.val[0] == '"'
&& node.val[node.val.size() - 1] == '"') {
std::string bin = node.val.substr(1, node.val.size() - 2);
unsigned sz = bin.size();
std::vector<Node> o;
for (unsigned i = 0; i < sz; i += 32) {
std::string t = binToNumeric(bin.substr(i, 32));
if ((sz - i) < 32 && (sz - i) > 0) {
while ((sz - i) < 32) {
t = decimalMul(t, "256");
i--;
}
i = sz;
}
o.push_back(token(t, node.metadata));
}
node = astnode("array_lit", o, node.metadata);
std::pair<Node, bool> r =
apply_rules_iter(preprocessResult(node, pr.second));
node = r.first;
changed = true;
}
else if (node.val.size() && node.val[0] != '\'' && node.val[0] != '$') {
node.val = "'" + node.val;
std::vector<Node> args;
args.push_back(node);
std::string v = node.val.substr(1);
node = astnode("get", args, node.metadata);
changed = true;
}
}
return std::pair<Node, bool>(node, changed);
}
Node apply_rules(preprocessResult pr) {
for (unsigned i = 0; i < pr.second.customMacros.size(); i++) {
pr.second.customMacros[i].pattern =
apply_rules(preprocessResult(pr.second.customMacros[i].pattern, preprocessAux()));
}
while (1) {
//std::cerr << printAST(pr.first) <<
// " " << pr.second.customMacros.size() << "\n";
std::pair<Node, bool> r = apply_rules_iter(pr);
if (!r.second) {
return r.first;
}
pr.first = r.first;
}
}
Node validate(Node inp) {
Metadata m = inp.metadata;
if (inp.type == ASTNODE) {
int i = 0;
while(validFunctions[i][0] != "---END---") {
if (inp.val == validFunctions[i][0]) {
std::string sz = unsignedToDecimal(inp.args.size());
if (decimalGt(validFunctions[i][1], sz)) {
err("Too few arguments for "+inp.val, inp.metadata);
}
if (decimalGt(sz, validFunctions[i][2])) {
err("Too many arguments for "+inp.val, inp.metadata);
}
}
i++;
}
}
for (unsigned i = 0; i < inp.args.size(); i++) validate(inp.args[i]);
return inp;
}
Node postValidate(Node inp) {
// This allows people to use ~x as a way of having functions with the same
// name and arity as macros; the idea is that ~x is a "final" form, and
// should not be remacroed, but it is converted back at the end
if (inp.val.size() > 0 && inp.val[0] == '~') {
inp.val = inp.val.substr(1);
}
if (inp.type == ASTNODE) {
if (inp.val == ".")
err("Invalid object member (ie. a foo.bar not mapped to anything)",
inp.metadata);
else if (opcode(inp.val) >= 0) {
if ((signed)inp.args.size() < opinputs(inp.val))
err("Too few arguments for "+inp.val, inp.metadata);
if ((signed)inp.args.size() > opinputs(inp.val))
err("Too many arguments for "+inp.val, inp.metadata);
}
else if (isValidLLLFunc(inp.val, inp.args.size())) {
// do nothing
}
else err ("Invalid argument count or LLL function: "+inp.val, inp.metadata);
for (unsigned i = 0; i < inp.args.size(); i++) {
inp.args[i] = postValidate(inp.args[i]);
}
}
return inp;
}
Node rewrite(Node inp) {
return postValidate(optimize(apply_rules(preprocess(inp))));
}
Node rewriteChunk(Node inp) {
return postValidate(optimize(apply_rules(
preprocessResult(
validate(inp), preprocessAux()))));
}
using namespace std;

View File

@ -0,0 +1,16 @@
#ifndef ETHSERP_REWRITER
#define ETHSERP_REWRITER
#include <stdio.h>
#include <iostream>
#include <vector>
#include <map>
#include "util.h"
// Applies rewrite rules
Node rewrite(Node inp);
// Applies rewrite rules adding without wrapper
Node rewriteChunk(Node inp);
#endif

Some files were not shown because too many files have changed in this diff Show More