Refactored state, state object and vm

* The State and StateObject have been moved to their own package
* The VM is moved to it's own package
This commit is contained in:
obscuren 2014-07-22 11:54:48 +02:00
parent 20ee1ae65e
commit 1e8b54abfb
15 changed files with 2242 additions and 1 deletions

View File

@ -123,9 +123,9 @@ done:
cb := state.GetStateObject(coinbase.Address())
st := NewStateTransition(cb, tx, state, block)
//fmt.Printf("#%d\n", i+1)
err = st.TransitionState()
if err != nil {
statelogger.Infoln(err)
switch {
case IsNonceErr(err):
err = nil // ignore error

View File

@ -48,6 +48,12 @@ type PEthereum struct {
}
func NewPEthereum(manager ethchain.EthManager) *PEthereum {
logger.Warnln("DEPRECATED: ethpub.New should be used in favour of ethpub.NewPEthereum")
return New(manager)
}
func New(manager ethchain.EthManager) *PEthereum {
return &PEthereum{
manager,
manager.StateManager(),

0
ethstate/.ethtest Normal file
View File

23
ethstate/errors.go Normal file
View File

@ -0,0 +1,23 @@
package ethstate
import (
"fmt"
"math/big"
)
type GasLimitErr struct {
Message string
Is, Max *big.Int
}
func IsGasLimitErr(err error) bool {
_, ok := err.(*GasLimitErr)
return ok
}
func (err *GasLimitErr) Error() string {
return err.Message
}
func GasLimitError(is, max *big.Int) *GasLimitErr {
return &GasLimitErr{Message: fmt.Sprintf("GasLimit error. Max %s, transaction would take it to %s", max, is), Is: is, Max: max}
}

259
ethstate/state.go Normal file
View File

@ -0,0 +1,259 @@
package ethstate
import (
"github.com/ethereum/eth-go/ethcrypto"
"github.com/ethereum/eth-go/ethlog"
"github.com/ethereum/eth-go/ethtrie"
"github.com/ethereum/eth-go/ethutil"
"math/big"
)
var statelogger = ethlog.NewLogger("STATE")
// States within the ethereum protocol are used to store anything
// within the merkle trie. States take care of caching and storing
// nested states. It's the general query interface to retrieve:
// * Contracts
// * Accounts
type State struct {
// The trie for this structure
trie *ethtrie.Trie
stateObjects map[string]*StateObject
manifest *Manifest
}
// Create a new state from a given trie
func NewState(trie *ethtrie.Trie) *State {
return &State{trie: trie, stateObjects: make(map[string]*StateObject), manifest: NewManifest()}
}
// Retrieve the balance from the given address or 0 if object not found
func (self *State) GetBalance(addr []byte) *big.Int {
stateObject := self.GetStateObject(addr)
if stateObject != nil {
return stateObject.Amount
}
return ethutil.Big0
}
func (self *State) GetNonce(addr []byte) uint64 {
stateObject := self.GetStateObject(addr)
if stateObject != nil {
return stateObject.Nonce
}
return 0
}
//
// Setting, updating & deleting state object methods
//
// Update the given state object and apply it to state trie
func (self *State) UpdateStateObject(stateObject *StateObject) {
addr := stateObject.Address()
ethutil.Config.Db.Put(ethcrypto.Sha3Bin(stateObject.Code), stateObject.Code)
self.trie.Update(string(addr), string(stateObject.RlpEncode()))
self.manifest.AddObjectChange(stateObject)
}
// Delete the given state object and delete it from the state trie
func (self *State) DeleteStateObject(stateObject *StateObject) {
self.trie.Delete(string(stateObject.Address()))
delete(self.stateObjects, string(stateObject.Address()))
}
// Retrieve a state object given my the address. Nil if not found
func (self *State) GetStateObject(addr []byte) *StateObject {
addr = ethutil.Address(addr)
stateObject := self.stateObjects[string(addr)]
if stateObject != nil {
return stateObject
}
data := self.trie.Get(string(addr))
if len(data) == 0 {
return nil
}
stateObject = NewStateObjectFromBytes(addr, []byte(data))
self.stateObjects[string(addr)] = stateObject
return stateObject
}
// Retrieve a state object or create a new state object if nil
func (self *State) GetOrNewStateObject(addr []byte) *StateObject {
stateObject := self.GetStateObject(addr)
if stateObject == nil {
stateObject = self.NewStateObject(addr)
}
return stateObject
}
// Create a state object whether it exist in the trie or not
func (self *State) NewStateObject(addr []byte) *StateObject {
addr = ethutil.Address(addr)
statelogger.Infof("(+) %x\n", addr)
stateObject := NewStateObject(addr)
self.stateObjects[string(addr)] = stateObject
return stateObject
}
// Deprecated
func (self *State) GetAccount(addr []byte) *StateObject {
return self.GetOrNewStateObject(addr)
}
//
// Setting, copying of the state methods
//
func (s *State) Cmp(other *State) bool {
return s.trie.Cmp(other.trie)
}
func (self *State) Copy() *State {
if self.trie != nil {
state := NewState(self.trie.Copy())
for k, stateObject := range self.stateObjects {
state.stateObjects[k] = stateObject.Copy()
}
return state
}
return nil
}
func (self *State) Set(state *State) {
if state == nil {
panic("Tried setting 'state' to nil through 'Set'")
}
self.trie = state.trie
self.stateObjects = state.stateObjects
}
func (s *State) Root() interface{} {
return s.trie.Root
}
// Resets the trie and all siblings
func (s *State) Reset() {
s.trie.Undo()
// Reset all nested states
for _, stateObject := range s.stateObjects {
if stateObject.state == nil {
continue
}
//stateObject.state.Reset()
stateObject.Reset()
}
s.Empty()
}
// Syncs the trie and all siblings
func (s *State) Sync() {
// Sync all nested states
for _, stateObject := range s.stateObjects {
//s.UpdateStateObject(stateObject)
if stateObject.state == nil {
continue
}
stateObject.state.Sync()
}
s.trie.Sync()
s.Empty()
}
func (self *State) Empty() {
self.stateObjects = make(map[string]*StateObject)
}
func (self *State) Update() {
for _, stateObject := range self.stateObjects {
if stateObject.remove {
self.DeleteStateObject(stateObject)
} else {
stateObject.Sync()
self.UpdateStateObject(stateObject)
}
}
// FIXME trie delete is broken
valid, t2 := ethtrie.ParanoiaCheck(self.trie)
if !valid {
statelogger.Infof("Warn: PARANOIA: Different state root during copy %x vs %x\n", self.trie.Root, t2.Root)
self.trie = t2
}
}
// Debug stuff
func (self *State) CreateOutputForDiff() {
for _, stateObject := range self.stateObjects {
stateObject.CreateOutputForDiff()
}
}
func (self *State) Manifest() *Manifest {
return self.manifest
}
// Object manifest
//
// The object manifest is used to keep changes to the state so we can keep track of the changes
// that occurred during a state transitioning phase.
type Manifest struct {
// XXX These will be handy in the future. Not important for now.
objectAddresses map[string]bool
storageAddresses map[string]map[string]bool
objectChanges map[string]*StateObject
storageChanges map[string]map[string]*big.Int
}
func NewManifest() *Manifest {
m := &Manifest{objectAddresses: make(map[string]bool), storageAddresses: make(map[string]map[string]bool)}
m.Reset()
return m
}
func (m *Manifest) Reset() {
m.objectChanges = make(map[string]*StateObject)
m.storageChanges = make(map[string]map[string]*big.Int)
}
func (m *Manifest) AddObjectChange(stateObject *StateObject) {
m.objectChanges[string(stateObject.Address())] = stateObject
}
func (m *Manifest) AddStorageChange(stateObject *StateObject, storageAddr []byte, storage *big.Int) {
if m.storageChanges[string(stateObject.Address())] == nil {
m.storageChanges[string(stateObject.Address())] = make(map[string]*big.Int)
}
m.storageChanges[string(stateObject.Address())][string(storageAddr)] = storage
}

339
ethstate/state_object.go Normal file
View File

@ -0,0 +1,339 @@
package ethstate
import (
"fmt"
"github.com/ethereum/eth-go/ethcrypto"
"github.com/ethereum/eth-go/ethtrie"
"github.com/ethereum/eth-go/ethutil"
"math/big"
)
type Code []byte
func (self Code) String() string {
return "" //strings.Join(Disassemble(self), " ")
}
type Storage map[string]*ethutil.Value
func (self Storage) Copy() Storage {
cpy := make(Storage)
for key, value := range self {
// XXX Do we need a 'value' copy or is this sufficient?
cpy[key] = value
}
return cpy
}
type StateObject struct {
// Address of the object
address []byte
// Shared attributes
Amount *big.Int
CodeHash []byte
Nonce uint64
// Contract related attributes
state *State
Code Code
initCode Code
storage Storage
// Total gas pool is the total amount of gas currently
// left if this object is the coinbase. Gas is directly
// purchased of the coinbase.
gasPool *big.Int
// Mark for deletion
// When an object is marked for deletion it will be delete from the trie
// during the "update" phase of the state transition
remove bool
}
func (self *StateObject) Reset() {
self.storage = make(Storage)
self.state.Reset()
}
/*
// Converts an transaction in to a state object
func MakeContract(tx *Transaction, state *State) *StateObject {
// Create contract if there's no recipient
if tx.IsContract() {
addr := tx.CreationAddress()
contract := state.NewStateObject(addr)
contract.initCode = tx.Data
contract.state = NewState(ethtrie.NewTrie(ethutil.Config.Db, ""))
return contract
}
return nil
}
*/
func NewStateObject(addr []byte) *StateObject {
// This to ensure that it has 20 bytes (and not 0 bytes), thus left or right pad doesn't matter.
address := ethutil.Address(addr)
object := &StateObject{address: address, Amount: new(big.Int), gasPool: new(big.Int)}
object.state = NewState(ethtrie.NewTrie(ethutil.Config.Db, ""))
object.storage = make(Storage)
object.gasPool = new(big.Int)
return object
}
func NewContract(address []byte, Amount *big.Int, root []byte) *StateObject {
contract := NewStateObject(address)
contract.Amount = Amount
contract.state = NewState(ethtrie.NewTrie(ethutil.Config.Db, string(root)))
return contract
}
func NewStateObjectFromBytes(address, data []byte) *StateObject {
object := &StateObject{address: address}
object.RlpDecode(data)
return object
}
func (self *StateObject) MarkForDeletion() {
self.remove = true
statelogger.DebugDetailf("%x: #%d %v (deletion)\n", self.Address(), self.Nonce, self.Amount)
}
func (c *StateObject) GetAddr(addr []byte) *ethutil.Value {
return ethutil.NewValueFromBytes([]byte(c.state.trie.Get(string(addr))))
}
func (c *StateObject) SetAddr(addr []byte, value interface{}) {
c.state.trie.Update(string(addr), string(ethutil.NewValue(value).Encode()))
}
func (self *StateObject) GetStorage(key *big.Int) *ethutil.Value {
return self.getStorage(key.Bytes())
}
func (self *StateObject) SetStorage(key *big.Int, value *ethutil.Value) {
self.setStorage(key.Bytes(), value)
}
func (self *StateObject) getStorage(k []byte) *ethutil.Value {
key := ethutil.LeftPadBytes(k, 32)
value := self.storage[string(key)]
if value == nil {
value = self.GetAddr(key)
if !value.IsNil() {
self.storage[string(key)] = value
}
}
return value
//return self.GetAddr(key)
}
func (self *StateObject) setStorage(k []byte, value *ethutil.Value) {
key := ethutil.LeftPadBytes(k, 32)
self.storage[string(key)] = value.Copy()
}
// Iterate over each storage address and yield callback
func (self *StateObject) EachStorage(cb ethtrie.EachCallback) {
// First loop over the uncommit/cached values in storage
for key, value := range self.storage {
// XXX Most iterators Fns as it stands require encoded values
encoded := ethutil.NewValue(value.Encode())
cb(key, encoded)
}
it := self.state.trie.NewIterator()
it.Each(func(key string, value *ethutil.Value) {
// If it's cached don't call the callback.
if self.storage[key] == nil {
cb(key, value)
}
})
}
func (self *StateObject) Sync() {
for key, value := range self.storage {
if value.Len() == 0 { // value.BigInt().Cmp(ethutil.Big0) == 0 {
//data := self.getStorage([]byte(key))
//fmt.Printf("deleting %x %x 0x%x\n", self.Address(), []byte(key), data)
self.state.trie.Delete(string(key))
continue
}
self.SetAddr([]byte(key), value)
}
valid, t2 := ethtrie.ParanoiaCheck(self.state.trie)
if !valid {
statelogger.Infof("Warn: PARANOIA: Different state storage root during copy %x vs %x\n", self.state.trie.Root, t2.Root)
self.state.trie = t2
}
}
func (c *StateObject) GetInstr(pc *big.Int) *ethutil.Value {
if int64(len(c.Code)-1) < pc.Int64() {
return ethutil.NewValue(0)
}
return ethutil.NewValueFromBytes([]byte{c.Code[pc.Int64()]})
}
func (c *StateObject) AddAmount(amount *big.Int) {
c.SetAmount(new(big.Int).Add(c.Amount, amount))
statelogger.Debugf("%x: #%d %v (+ %v)\n", c.Address(), c.Nonce, c.Amount, amount)
}
func (c *StateObject) SubAmount(amount *big.Int) {
c.SetAmount(new(big.Int).Sub(c.Amount, amount))
statelogger.Debugf("%x: #%d %v (- %v)\n", c.Address(), c.Nonce, c.Amount, amount)
}
func (c *StateObject) SetAmount(amount *big.Int) {
c.Amount = amount
}
//
// Gas setters and getters
//
// Return the gas back to the origin. Used by the Virtual machine or Closures
func (c *StateObject) ReturnGas(gas, price *big.Int) {}
func (c *StateObject) ConvertGas(gas, price *big.Int) error {
total := new(big.Int).Mul(gas, price)
if total.Cmp(c.Amount) > 0 {
return fmt.Errorf("insufficient amount: %v, %v", c.Amount, total)
}
c.SubAmount(total)
return nil
}
func (self *StateObject) SetGasPool(gasLimit *big.Int) {
self.gasPool = new(big.Int).Set(gasLimit)
statelogger.DebugDetailf("%x: fuel (+ %v)", self.Address(), self.gasPool)
}
func (self *StateObject) BuyGas(gas, price *big.Int) error {
if self.gasPool.Cmp(gas) < 0 {
return GasLimitError(self.gasPool, gas)
}
rGas := new(big.Int).Set(gas)
rGas.Mul(rGas, price)
self.AddAmount(rGas)
return nil
}
func (self *StateObject) RefundGas(gas, price *big.Int) {
self.gasPool.Add(self.gasPool, gas)
rGas := new(big.Int).Set(gas)
rGas.Mul(rGas, price)
self.Amount.Sub(self.Amount, rGas)
}
func (self *StateObject) Copy() *StateObject {
stateObject := NewStateObject(self.Address())
stateObject.Amount.Set(self.Amount)
stateObject.CodeHash = ethutil.CopyBytes(self.CodeHash)
stateObject.Nonce = self.Nonce
if self.state != nil {
stateObject.state = self.state.Copy()
}
stateObject.Code = ethutil.CopyBytes(self.Code)
stateObject.initCode = ethutil.CopyBytes(self.initCode)
stateObject.storage = self.storage.Copy()
stateObject.gasPool.Set(self.gasPool)
return stateObject
}
func (self *StateObject) Set(stateObject *StateObject) {
*self = *stateObject
}
//
// Attribute accessors
//
func (c *StateObject) State() *State {
return c.state
}
func (c *StateObject) N() *big.Int {
return big.NewInt(int64(c.Nonce))
}
// Returns the address of the contract/account
func (c *StateObject) Address() []byte {
return c.address
}
// Returns the initialization Code
func (c *StateObject) Init() Code {
return c.initCode
}
// Debug stuff
func (self *StateObject) CreateOutputForDiff() {
fmt.Printf("%x %x %x %x\n", self.Address(), self.state.Root(), self.Amount.Bytes(), self.Nonce)
self.EachStorage(func(addr string, value *ethutil.Value) {
fmt.Printf("%x %x\n", addr, value.Bytes())
})
}
//
// Encoding
//
// State object encoding methods
func (c *StateObject) RlpEncode() []byte {
var root interface{}
if c.state != nil {
root = c.state.trie.Root
} else {
root = ""
}
return ethutil.Encode([]interface{}{c.Nonce, c.Amount, root, ethcrypto.Sha3Bin(c.Code)})
}
func (c *StateObject) RlpDecode(data []byte) {
decoder := ethutil.NewValueFromBytes(data)
c.Nonce = decoder.Get(0).Uint()
c.Amount = decoder.Get(1).BigInt()
c.state = NewState(ethtrie.NewTrie(ethutil.Config.Db, decoder.Get(2).Interface()))
c.storage = make(map[string]*ethutil.Value)
c.gasPool = new(big.Int)
c.CodeHash = decoder.Get(3).Bytes()
c.Code, _ = ethutil.Config.Db.Get(c.CodeHash)
}
// Storage change object. Used by the manifest for notifying changes to
// the sub channels.
type StorageState struct {
StateAddress []byte
Address []byte
Value *big.Int
}

35
ethstate/state_test.go Normal file
View File

@ -0,0 +1,35 @@
package ethstate
import (
"github.com/ethereum/eth-go/ethdb"
"github.com/ethereum/eth-go/ethtrie"
"github.com/ethereum/eth-go/ethutil"
"testing"
)
var ZeroHash256 = make([]byte, 32)
func TestSnapshot(t *testing.T) {
db, _ := ethdb.NewMemDatabase()
ethutil.ReadConfig(".ethtest", "/tmp/ethtest", "")
ethutil.Config.Db = db
state := NewState(ethtrie.NewTrie(db, ""))
stateObject := state.GetOrNewStateObject([]byte("aa"))
stateObject.SetStorage(ethutil.Big("0"), ethutil.NewValue(42))
snapshot := state.Copy()
stateObject = state.GetStateObject([]byte("aa"))
stateObject.SetStorage(ethutil.Big("0"), ethutil.NewValue(43))
state.Set(snapshot)
stateObject = state.GetStateObject([]byte("aa"))
res := stateObject.GetStorage(ethutil.Big("0"))
if !res.Cmp(ethutil.NewValue(42)) {
t.Error("Expected storage 0 to be 42", res)
}
}

0
ethvm/.ethtest Normal file
View File

44
ethvm/asm.go Normal file
View File

@ -0,0 +1,44 @@
package ethvm
import (
"fmt"
"github.com/ethereum/eth-go/ethutil"
"math/big"
)
func Disassemble(script []byte) (asm []string) {
pc := new(big.Int)
for {
if pc.Cmp(big.NewInt(int64(len(script)))) >= 0 {
return
}
// Get the memory location of pc
val := script[pc.Int64()]
// Get the opcode (it must be an opcode!)
op := OpCode(val)
asm = append(asm, fmt.Sprintf("%04v: %v", pc, op))
switch op {
case PUSH1, PUSH2, PUSH3, PUSH4, PUSH5, PUSH6, PUSH7, PUSH8, PUSH9, PUSH10, PUSH11, PUSH12, PUSH13, PUSH14, PUSH15, PUSH16, PUSH17, PUSH18, PUSH19, PUSH20, PUSH21, PUSH22, PUSH23, PUSH24, PUSH25, PUSH26, PUSH27, PUSH28, PUSH29, PUSH30, PUSH31, PUSH32:
pc.Add(pc, ethutil.Big1)
a := int64(op) - int64(PUSH1) + 1
if int(pc.Int64()+a) > len(script) {
return nil
}
data := script[pc.Int64() : pc.Int64()+a]
if len(data) == 0 {
data = []byte{0}
}
asm = append(asm, fmt.Sprintf("%04v: 0x%x", pc, data))
pc.Add(pc, big.NewInt(a-1))
}
pc.Add(pc, ethutil.Big1)
}
return
}

116
ethvm/closure.go Normal file
View File

@ -0,0 +1,116 @@
package ethvm
// TODO Re write VM to use values instead of big integers?
import (
"github.com/ethereum/eth-go/ethstate"
"github.com/ethereum/eth-go/ethutil"
"math/big"
)
type ClosureRef interface {
ReturnGas(*big.Int, *big.Int)
Address() []byte
GetStorage(*big.Int) *ethutil.Value
SetStorage(*big.Int, *ethutil.Value)
}
// Basic inline closure object which implement the 'closure' interface
type Closure struct {
caller ClosureRef
object *ethstate.StateObject
Code []byte
Gas, UsedGas, Price *big.Int
Args []byte
}
// Create a new closure for the given data items
func NewClosure(caller ClosureRef, object *ethstate.StateObject, code []byte, gas, price *big.Int) *Closure {
c := &Closure{caller: caller, object: object, Code: code, Args: nil}
// Gas should be a pointer so it can safely be reduced through the run
// This pointer will be off the state transition
c.Gas = gas //new(big.Int).Set(gas)
// In most cases price and value are pointers to transaction objects
// and we don't want the transaction's values to change.
c.Price = new(big.Int).Set(price)
c.UsedGas = new(big.Int)
return c
}
// Retuns the x element in data slice
func (c *Closure) GetStorage(x *big.Int) *ethutil.Value {
m := c.object.GetStorage(x)
if m == nil {
return ethutil.EmptyValue()
}
return m
}
func (c *Closure) Get(x *big.Int) *ethutil.Value {
return c.Gets(x, big.NewInt(1))
}
func (c *Closure) Gets(x, y *big.Int) *ethutil.Value {
if x.Int64() >= int64(len(c.Code)) || y.Int64() >= int64(len(c.Code)) {
return ethutil.NewValue(0)
}
partial := c.Code[x.Int64() : x.Int64()+y.Int64()]
return ethutil.NewValue(partial)
}
func (c *Closure) SetStorage(x *big.Int, val *ethutil.Value) {
c.object.SetStorage(x, val)
}
func (c *Closure) Address() []byte {
return c.object.Address()
}
func (c *Closure) Call(vm *Vm, args []byte) ([]byte, *big.Int, error) {
c.Args = args
ret, err := vm.RunClosure(c)
return ret, c.UsedGas, err
}
func (c *Closure) Return(ret []byte) []byte {
// Return the remaining gas to the caller
c.caller.ReturnGas(c.Gas, c.Price)
return ret
}
func (c *Closure) UseGas(gas *big.Int) bool {
if c.Gas.Cmp(gas) < 0 {
return false
}
// Sub the amount of gas from the remaining
c.Gas.Sub(c.Gas, gas)
c.UsedGas.Add(c.UsedGas, gas)
return true
}
// Implement the caller interface
func (c *Closure) ReturnGas(gas, price *big.Int) {
// Return the gas to the closure
c.Gas.Add(c.Gas, gas)
c.UsedGas.Sub(c.UsedGas, gas)
}
func (c *Closure) Object() *ethstate.StateObject {
return c.object
}
func (c *Closure) Caller() ClosureRef {
return c.caller
}

27
ethvm/common.go Normal file
View File

@ -0,0 +1,27 @@
package ethvm
import (
"github.com/ethereum/eth-go/ethlog"
"github.com/ethereum/eth-go/ethutil"
"math/big"
)
var vmlogger = ethlog.NewLogger("VM")
var (
GasStep = big.NewInt(1)
GasSha = big.NewInt(20)
GasSLoad = big.NewInt(20)
GasSStore = big.NewInt(100)
GasBalance = big.NewInt(20)
GasCreate = big.NewInt(100)
GasCall = big.NewInt(20)
GasMemory = big.NewInt(1)
GasData = big.NewInt(5)
GasTx = big.NewInt(500)
Pow256 = ethutil.BigPow(2, 256)
LogTyPretty byte = 0x1
LogTyDiff byte = 0x2
)

150
ethvm/stack.go Normal file
View File

@ -0,0 +1,150 @@
package ethvm
import (
"fmt"
"math"
"math/big"
)
type OpType int
const (
tNorm = iota
tData
tExtro
tCrypto
)
type TxCallback func(opType OpType) bool
// Simple push/pop stack mechanism
type Stack struct {
data []*big.Int
}
func NewStack() *Stack {
return &Stack{}
}
func (st *Stack) Data() []*big.Int {
return st.data
}
func (st *Stack) Len() int {
return len(st.data)
}
func (st *Stack) Pop() *big.Int {
str := st.data[len(st.data)-1]
copy(st.data[:len(st.data)-1], st.data[:len(st.data)-1])
st.data = st.data[:len(st.data)-1]
return str
}
func (st *Stack) Popn() (*big.Int, *big.Int) {
ints := st.data[len(st.data)-2:]
copy(st.data[:len(st.data)-2], st.data[:len(st.data)-2])
st.data = st.data[:len(st.data)-2]
return ints[0], ints[1]
}
func (st *Stack) Peek() *big.Int {
str := st.data[len(st.data)-1]
return str
}
func (st *Stack) Peekn() (*big.Int, *big.Int) {
ints := st.data[:2]
return ints[0], ints[1]
}
func (st *Stack) Push(d *big.Int) {
st.data = append(st.data, new(big.Int).Set(d))
}
func (st *Stack) Get(amount *big.Int) []*big.Int {
// offset + size <= len(data)
length := big.NewInt(int64(len(st.data)))
if amount.Cmp(length) <= 0 {
start := new(big.Int).Sub(length, amount)
return st.data[start.Int64():length.Int64()]
}
return nil
}
func (st *Stack) Print() {
fmt.Println("### stack ###")
if len(st.data) > 0 {
for i, val := range st.data {
fmt.Printf("%-3d %v\n", i, val)
}
} else {
fmt.Println("-- empty --")
}
fmt.Println("#############")
}
type Memory struct {
store []byte
}
func (m *Memory) Set(offset, size int64, value []byte) {
totSize := offset + size
lenSize := int64(len(m.store) - 1)
if totSize > lenSize {
// Calculate the diff between the sizes
diff := totSize - lenSize
if diff > 0 {
// Create a new empty slice and append it
newSlice := make([]byte, diff-1)
// Resize slice
m.store = append(m.store, newSlice...)
}
}
copy(m.store[offset:offset+size], value)
}
func (m *Memory) Resize(size uint64) {
if uint64(m.Len()) < size {
m.store = append(m.store, make([]byte, size-uint64(m.Len()))...)
}
}
func (m *Memory) Get(offset, size int64) []byte {
if len(m.store) > int(offset) {
end := int(math.Min(float64(len(m.store)), float64(offset+size)))
return m.store[offset:end]
}
return nil
}
func (m *Memory) Len() int {
return len(m.store)
}
func (m *Memory) Data() []byte {
return m.store
}
func (m *Memory) Print() {
fmt.Printf("### mem %d bytes ###\n", len(m.store))
if len(m.store) > 0 {
addr := 0
for i := 0; i+32 <= len(m.store); i += 32 {
fmt.Printf("%03d: % x\n", addr, m.store[i:i+32])
addr++
}
} else {
fmt.Println("-- empty --")
}
fmt.Println("####################")
}

346
ethvm/types.go Normal file
View File

@ -0,0 +1,346 @@
package ethvm
import (
"fmt"
)
type OpCode int
// Op codes
const (
// 0x0 range - arithmetic ops
STOP = 0x00
ADD = 0x01
MUL = 0x02
SUB = 0x03
DIV = 0x04
SDIV = 0x05
MOD = 0x06
SMOD = 0x07
EXP = 0x08
NEG = 0x09
LT = 0x0a
GT = 0x0b
SLT = 0x0c
SGT = 0x0d
EQ = 0x0e
NOT = 0x0f
// 0x10 range - bit ops
AND = 0x10
OR = 0x11
XOR = 0x12
BYTE = 0x13
// 0x20 range - crypto
SHA3 = 0x20
// 0x30 range - closure state
ADDRESS = 0x30
BALANCE = 0x31
ORIGIN = 0x32
CALLER = 0x33
CALLVALUE = 0x34
CALLDATALOAD = 0x35
CALLDATASIZE = 0x36
CALLDATACOPY = 0x37
CODESIZE = 0x38
CODECOPY = 0x39
GASPRICE = 0x3a
// 0x40 range - block operations
PREVHASH = 0x40
COINBASE = 0x41
TIMESTAMP = 0x42
NUMBER = 0x43
DIFFICULTY = 0x44
GASLIMIT = 0x45
// 0x50 range - 'storage' and execution
POP = 0x50
DUP = 0x51
SWAP = 0x52
MLOAD = 0x53
MSTORE = 0x54
MSTORE8 = 0x55
SLOAD = 0x56
SSTORE = 0x57
JUMP = 0x58
JUMPI = 0x59
PC = 0x5a
MSIZE = 0x5b
GAS = 0x5c
// 0x60 range
PUSH1 = 0x60
PUSH2 = 0x61
PUSH3 = 0x62
PUSH4 = 0x63
PUSH5 = 0x64
PUSH6 = 0x65
PUSH7 = 0x66
PUSH8 = 0x67
PUSH9 = 0x68
PUSH10 = 0x69
PUSH11 = 0x6a
PUSH12 = 0x6b
PUSH13 = 0x6c
PUSH14 = 0x6d
PUSH15 = 0x6e
PUSH16 = 0x6f
PUSH17 = 0x70
PUSH18 = 0x71
PUSH19 = 0x72
PUSH20 = 0x73
PUSH21 = 0x74
PUSH22 = 0x75
PUSH23 = 0x76
PUSH24 = 0x77
PUSH25 = 0x78
PUSH26 = 0x79
PUSH27 = 0x7a
PUSH28 = 0x7b
PUSH29 = 0x7c
PUSH30 = 0x7d
PUSH31 = 0x7e
PUSH32 = 0x7f
// 0xf0 range - closures
CREATE = 0xf0
CALL = 0xf1
RETURN = 0xf2
// 0x70 range - other
LOG = 0xfe // XXX Unofficial
SUICIDE = 0xff
)
// Since the opcodes aren't all in order we can't use a regular slice
var opCodeToString = map[OpCode]string{
// 0x0 range - arithmetic ops
STOP: "STOP",
ADD: "ADD",
MUL: "MUL",
SUB: "SUB",
DIV: "DIV",
SDIV: "SDIV",
MOD: "MOD",
SMOD: "SMOD",
EXP: "EXP",
NEG: "NEG",
LT: "LT",
GT: "GT",
SLT: "SLT",
SGT: "SGT",
EQ: "EQ",
NOT: "NOT",
// 0x10 range - bit ops
AND: "AND",
OR: "OR",
XOR: "XOR",
BYTE: "BYTE",
// 0x20 range - crypto
SHA3: "SHA3",
// 0x30 range - closure state
ADDRESS: "ADDRESS",
BALANCE: "BALANCE",
ORIGIN: "ORIGIN",
CALLER: "CALLER",
CALLVALUE: "CALLVALUE",
CALLDATALOAD: "CALLDATALOAD",
CALLDATASIZE: "CALLDATASIZE",
CALLDATACOPY: "CALLDATACOPY",
CODESIZE: "CODESIZE",
CODECOPY: "CODECOPY",
GASPRICE: "TXGASPRICE",
// 0x40 range - block operations
PREVHASH: "PREVHASH",
COINBASE: "COINBASE",
TIMESTAMP: "TIMESTAMP",
NUMBER: "NUMBER",
DIFFICULTY: "DIFFICULTY",
GASLIMIT: "GASLIMIT",
// 0x50 range - 'storage' and execution
POP: "POP",
DUP: "DUP",
SWAP: "SWAP",
MLOAD: "MLOAD",
MSTORE: "MSTORE",
MSTORE8: "MSTORE8",
SLOAD: "SLOAD",
SSTORE: "SSTORE",
JUMP: "JUMP",
JUMPI: "JUMPI",
PC: "PC",
MSIZE: "MSIZE",
GAS: "GAS",
// 0x60 range - push
PUSH1: "PUSH1",
PUSH2: "PUSH2",
PUSH3: "PUSH3",
PUSH4: "PUSH4",
PUSH5: "PUSH5",
PUSH6: "PUSH6",
PUSH7: "PUSH7",
PUSH8: "PUSH8",
PUSH9: "PUSH9",
PUSH10: "PUSH10",
PUSH11: "PUSH11",
PUSH12: "PUSH12",
PUSH13: "PUSH13",
PUSH14: "PUSH14",
PUSH15: "PUSH15",
PUSH16: "PUSH16",
PUSH17: "PUSH17",
PUSH18: "PUSH18",
PUSH19: "PUSH19",
PUSH20: "PUSH20",
PUSH21: "PUSH21",
PUSH22: "PUSH22",
PUSH23: "PUSH23",
PUSH24: "PUSH24",
PUSH25: "PUSH25",
PUSH26: "PUSH26",
PUSH27: "PUSH27",
PUSH28: "PUSH28",
PUSH29: "PUSH29",
PUSH30: "PUSH30",
PUSH31: "PUSH31",
PUSH32: "PUSH32",
// 0xf0 range
CREATE: "CREATE",
CALL: "CALL",
RETURN: "RETURN",
// 0x70 range - other
LOG: "LOG",
SUICIDE: "SUICIDE",
}
func (o OpCode) String() string {
str := opCodeToString[o]
if len(str) == 0 {
return fmt.Sprintf("Missing opcode 0x%x", int(o))
}
return str
}
// Op codes for assembling
var OpCodes = map[string]byte{
// 0x0 range - arithmetic ops
"STOP": 0x00,
"ADD": 0x01,
"MUL": 0x02,
"SUB": 0x03,
"DIV": 0x04,
"SDIV": 0x05,
"MOD": 0x06,
"SMOD": 0x07,
"EXP": 0x08,
"NEG": 0x09,
"LT": 0x0a,
"GT": 0x0b,
"EQ": 0x0c,
"NOT": 0x0d,
// 0x10 range - bit ops
"AND": 0x10,
"OR": 0x11,
"XOR": 0x12,
"BYTE": 0x13,
// 0x20 range - crypto
"SHA3": 0x20,
// 0x30 range - closure state
"ADDRESS": 0x30,
"BALANCE": 0x31,
"ORIGIN": 0x32,
"CALLER": 0x33,
"CALLVALUE": 0x34,
"CALLDATALOAD": 0x35,
"CALLDATASIZE": 0x36,
"GASPRICE": 0x38,
// 0x40 range - block operations
"PREVHASH": 0x40,
"COINBASE": 0x41,
"TIMESTAMP": 0x42,
"NUMBER": 0x43,
"DIFFICULTY": 0x44,
"GASLIMIT": 0x45,
// 0x50 range - 'storage' and execution
"POP": 0x51,
"DUP": 0x52,
"SWAP": 0x53,
"MLOAD": 0x54,
"MSTORE": 0x55,
"MSTORE8": 0x56,
"SLOAD": 0x57,
"SSTORE": 0x58,
"JUMP": 0x59,
"JUMPI": 0x5a,
"PC": 0x5b,
"MSIZE": 0x5c,
// 0x70 range - 'push'
"PUSH1": 0x60,
"PUSH2": 0x61,
"PUSH3": 0x62,
"PUSH4": 0x63,
"PUSH5": 0x64,
"PUSH6": 0x65,
"PUSH7": 0x66,
"PUSH8": 0x67,
"PUSH9": 0x68,
"PUSH10": 0x69,
"PUSH11": 0x6a,
"PUSH12": 0x6b,
"PUSH13": 0x6c,
"PUSH14": 0x6d,
"PUSH15": 0x6e,
"PUSH16": 0x6f,
"PUSH17": 0x70,
"PUSH18": 0x71,
"PUSH19": 0x72,
"PUSH20": 0x73,
"PUSH21": 0x74,
"PUSH22": 0x75,
"PUSH23": 0x76,
"PUSH24": 0x77,
"PUSH25": 0x78,
"PUSH26": 0x70,
"PUSH27": 0x7a,
"PUSH28": 0x7b,
"PUSH29": 0x7c,
"PUSH30": 0x7d,
"PUSH31": 0x7e,
"PUSH32": 0x7f,
// 0xf0 range - closures
"CREATE": 0xf0,
"CALL": 0xf1,
"RETURN": 0xf2,
// 0x70 range - other
"LOG": 0xfe,
"SUICIDE": 0x7f,
}
func IsOpCode(s string) bool {
for key, _ := range OpCodes {
if key == s {
return true
}
}
return false
}

848
ethvm/vm.go Normal file
View File

@ -0,0 +1,848 @@
package ethvm
import (
"fmt"
"github.com/ethereum/eth-go/ethcrypto"
"github.com/ethereum/eth-go/ethstate"
"github.com/ethereum/eth-go/ethutil"
"math"
"math/big"
)
type Debugger interface {
BreakHook(step int, op OpCode, mem *Memory, stack *Stack, object *ethstate.StateObject) bool
StepHook(step int, op OpCode, mem *Memory, stack *Stack, object *ethstate.StateObject) bool
BreakPoints() []int64
SetCode(byteCode []byte)
}
type Vm struct {
// Stack for processing contracts
stack *Stack
// non-persistent key/value memory storage
mem map[string]*big.Int
env Environment
Verbose bool
logTy byte
logStr string
err error
// Debugging
Dbg Debugger
BreakPoints []int64
Stepping bool
Fn string
}
type Environment interface {
State() *ethstate.State
Origin() []byte
BlockNumber() *big.Int
PrevHash() []byte
Coinbase() []byte
Time() int64
Difficulty() *big.Int
Data() []string
Value() *big.Int
}
type Object interface {
GetStorage(key *big.Int) *ethutil.Value
SetStorage(key *big.Int, value *ethutil.Value)
}
func New(env Environment) *Vm {
lt := LogTyPretty
if ethutil.Config.Diff {
lt = LogTyDiff
}
return &Vm{env: env, logTy: lt}
}
func (self *Vm) RunClosure(closure *Closure) (ret []byte, err error) {
// Recover from any require exception
defer func() {
if r := recover(); r != nil {
ret = closure.Return(nil)
err = fmt.Errorf("%v", r)
vmlogger.Errorln("vm err", err)
}
}()
// Debug hook
if self.Dbg != nil {
self.Dbg.SetCode(closure.Code)
}
// Don't bother with the execution if there's no code.
if len(closure.Code) == 0 {
return closure.Return(nil), nil
}
vmlogger.Debugf("(%s) %x gas: %v (d) %x\n", self.Fn, closure.Address(), closure.Gas, closure.Args)
var (
op OpCode
mem = &Memory{}
stack = NewStack()
pc = big.NewInt(0)
step = 0
prevStep = 0
require = func(m int) {
if stack.Len() < m {
panic(fmt.Sprintf("%04v (%v) stack err size = %d, required = %d", pc, op, stack.Len(), m))
}
}
)
for {
prevStep = step
// The base for all big integer arithmetic
base := new(big.Int)
step++
// Get the memory location of pc
val := closure.Get(pc)
// Get the opcode (it must be an opcode!)
op = OpCode(val.Uint())
// XXX Leave this Println intact. Don't change this to the log system.
// Used for creating diffs between implementations
if self.logTy == LogTyDiff {
/*
switch op {
case STOP, RETURN, SUICIDE:
closure.object.EachStorage(func(key string, value *ethutil.Value) {
value.Decode()
fmt.Printf("%x %x\n", new(big.Int).SetBytes([]byte(key)).Bytes(), value.Bytes())
})
}
b := pc.Bytes()
if len(b) == 0 {
b = []byte{0}
}
fmt.Printf("%x %x %x %x\n", closure.Address(), b, []byte{byte(op)}, closure.Gas.Bytes())
*/
}
gas := new(big.Int)
addStepGasUsage := func(amount *big.Int) {
if amount.Cmp(ethutil.Big0) >= 0 {
gas.Add(gas, amount)
}
}
addStepGasUsage(GasStep)
var newMemSize uint64 = 0
switch op {
case STOP:
gas.Set(ethutil.Big0)
case SUICIDE:
gas.Set(ethutil.Big0)
case SLOAD:
gas.Set(GasSLoad)
case SSTORE:
var mult *big.Int
y, x := stack.Peekn()
val := closure.GetStorage(x)
if val.BigInt().Cmp(ethutil.Big0) == 0 && len(y.Bytes()) > 0 {
mult = ethutil.Big2
} else if val.BigInt().Cmp(ethutil.Big0) != 0 && len(y.Bytes()) == 0 {
mult = ethutil.Big0
} else {
mult = ethutil.Big1
}
gas = new(big.Int).Mul(mult, GasSStore)
case BALANCE:
gas.Set(GasBalance)
case MSTORE:
require(2)
newMemSize = stack.Peek().Uint64() + 32
case MLOAD:
require(1)
newMemSize = stack.Peek().Uint64() + 32
case MSTORE8:
require(2)
newMemSize = stack.Peek().Uint64() + 1
case RETURN:
require(2)
newMemSize = stack.Peek().Uint64() + stack.data[stack.Len()-2].Uint64()
case SHA3:
require(2)
gas.Set(GasSha)
newMemSize = stack.Peek().Uint64() + stack.data[stack.Len()-2].Uint64()
case CALLDATACOPY:
require(3)
newMemSize = stack.Peek().Uint64() + stack.data[stack.Len()-3].Uint64()
case CODECOPY:
require(3)
newMemSize = stack.Peek().Uint64() + stack.data[stack.Len()-3].Uint64()
case CALL:
require(7)
gas.Set(GasCall)
addStepGasUsage(stack.data[stack.Len()-1])
x := stack.data[stack.Len()-6].Uint64() + stack.data[stack.Len()-7].Uint64()
y := stack.data[stack.Len()-4].Uint64() + stack.data[stack.Len()-5].Uint64()
newMemSize = uint64(math.Max(float64(x), float64(y)))
case CREATE:
require(3)
gas.Set(GasCreate)
newMemSize = stack.data[stack.Len()-2].Uint64() + stack.data[stack.Len()-3].Uint64()
}
newMemSize = (newMemSize + 31) / 32 * 32
if newMemSize > uint64(mem.Len()) {
m := GasMemory.Uint64() * (newMemSize - uint64(mem.Len())) / 32
addStepGasUsage(big.NewInt(int64(m)))
}
if !closure.UseGas(gas) {
err := fmt.Errorf("Insufficient gas for %v. req %v has %v", op, gas, closure.Gas)
closure.UseGas(closure.Gas)
return closure.Return(nil), err
}
self.Printf("(pc) %-3d -o- %-14s", pc, op.String())
self.Printf(" (g) %-3v (%v)", gas, closure.Gas)
mem.Resize(newMemSize)
switch op {
case LOG:
stack.Print()
mem.Print()
// 0x20 range
case ADD:
require(2)
x, y := stack.Popn()
self.Printf(" %v + %v", y, x)
base.Add(y, x)
self.Printf(" = %v", base)
// Pop result back on the stack
stack.Push(base)
case SUB:
require(2)
x, y := stack.Popn()
self.Printf(" %v - %v", y, x)
base.Sub(y, x)
self.Printf(" = %v", base)
// Pop result back on the stack
stack.Push(base)
case MUL:
require(2)
x, y := stack.Popn()
self.Printf(" %v * %v", y, x)
base.Mul(y, x)
self.Printf(" = %v", base)
// Pop result back on the stack
stack.Push(base)
case DIV:
require(2)
x, y := stack.Popn()
self.Printf(" %v / %v", y, x)
base.Div(y, x)
self.Printf(" = %v", base)
// Pop result back on the stack
stack.Push(base)
case SDIV:
require(2)
x, y := stack.Popn()
// n > 2**255
if x.Cmp(Pow256) > 0 {
x.Sub(Pow256, x)
}
if y.Cmp(Pow256) > 0 {
y.Sub(Pow256, y)
}
z := new(big.Int)
z.Div(x, y)
if z.Cmp(Pow256) > 0 {
z.Sub(Pow256, z)
}
// Push result on to the stack
stack.Push(z)
case MOD:
require(2)
x, y := stack.Popn()
self.Printf(" %v %% %v", y, x)
base.Mod(y, x)
self.Printf(" = %v", base)
stack.Push(base)
case SMOD:
require(2)
x, y := stack.Popn()
// n > 2**255
if x.Cmp(Pow256) > 0 {
x.Sub(Pow256, x)
}
if y.Cmp(Pow256) > 0 {
y.Sub(Pow256, y)
}
z := new(big.Int)
z.Mod(x, y)
if z.Cmp(Pow256) > 0 {
z.Sub(Pow256, z)
}
// Push result on to the stack
stack.Push(z)
case EXP:
require(2)
x, y := stack.Popn()
self.Printf(" %v ** %v", y, x)
base.Exp(y, x, Pow256)
self.Printf(" = %v", base)
stack.Push(base)
case NEG:
require(1)
base.Sub(Pow256, stack.Pop())
stack.Push(base)
case LT:
require(2)
x, y := stack.Popn()
self.Printf(" %v < %v", y, x)
// x < y
if y.Cmp(x) < 0 {
stack.Push(ethutil.BigTrue)
} else {
stack.Push(ethutil.BigFalse)
}
case GT:
require(2)
x, y := stack.Popn()
self.Printf(" %v > %v", y, x)
// x > y
if y.Cmp(x) > 0 {
stack.Push(ethutil.BigTrue)
} else {
stack.Push(ethutil.BigFalse)
}
case SLT:
require(2)
x, y := stack.Popn()
self.Printf(" %v < %v", y, x)
// x < y
if y.Cmp(x) < 0 {
stack.Push(ethutil.BigTrue)
} else {
stack.Push(ethutil.BigFalse)
}
case SGT:
require(2)
x, y := stack.Popn()
self.Printf(" %v > %v", y, x)
// x > y
if y.Cmp(x) > 0 {
stack.Push(ethutil.BigTrue)
} else {
stack.Push(ethutil.BigFalse)
}
case EQ:
require(2)
x, y := stack.Popn()
self.Printf(" %v == %v", y, x)
// x == y
if x.Cmp(y) == 0 {
stack.Push(ethutil.BigTrue)
} else {
stack.Push(ethutil.BigFalse)
}
case NOT:
require(1)
x := stack.Pop()
if x.Cmp(ethutil.BigFalse) > 0 {
stack.Push(ethutil.BigFalse)
} else {
stack.Push(ethutil.BigTrue)
}
// 0x10 range
case AND:
require(2)
x, y := stack.Popn()
self.Printf(" %v & %v", y, x)
stack.Push(base.And(y, x))
case OR:
require(2)
x, y := stack.Popn()
self.Printf(" %v | %v", y, x)
stack.Push(base.Or(y, x))
case XOR:
require(2)
x, y := stack.Popn()
self.Printf(" %v ^ %v", y, x)
stack.Push(base.Xor(y, x))
case BYTE:
require(2)
val, th := stack.Popn()
if th.Cmp(big.NewInt(32)) < 0 && th.Cmp(big.NewInt(int64(len(val.Bytes())))) < 0 {
byt := big.NewInt(int64(val.Bytes()[th.Int64()]))
stack.Push(byt)
self.Printf(" => 0x%x", byt.Bytes())
} else {
stack.Push(ethutil.BigFalse)
}
// 0x20 range
case SHA3:
require(2)
size, offset := stack.Popn()
data := ethcrypto.Sha3Bin(mem.Get(offset.Int64(), size.Int64()))
stack.Push(ethutil.BigD(data))
self.Printf(" => %x", data)
// 0x30 range
case ADDRESS:
stack.Push(ethutil.BigD(closure.Address()))
self.Printf(" => %x", closure.Address())
case BALANCE:
require(1)
addr := stack.Pop().Bytes()
balance := self.env.State().GetBalance(addr)
stack.Push(balance)
self.Printf(" => %v (%x)", balance, addr)
case ORIGIN:
origin := self.env.Origin()
stack.Push(ethutil.BigD(origin))
self.Printf(" => %x", origin)
case CALLER:
caller := closure.caller.Address()
stack.Push(ethutil.BigD(caller))
self.Printf(" => %x", caller)
case CALLVALUE:
value := self.env.Value()
stack.Push(value)
self.Printf(" => %v", value)
case CALLDATALOAD:
require(1)
var (
offset = stack.Pop()
data = make([]byte, 32)
lenData = big.NewInt(int64(len(closure.Args)))
)
if lenData.Cmp(offset) >= 0 {
length := new(big.Int).Add(offset, ethutil.Big32)
length = ethutil.BigMin(length, lenData)
copy(data, closure.Args[offset.Int64():length.Int64()])
}
self.Printf(" => 0x%x", data)
stack.Push(ethutil.BigD(data))
case CALLDATASIZE:
l := int64(len(closure.Args))
stack.Push(big.NewInt(l))
self.Printf(" => %d", l)
case CALLDATACOPY:
var (
size = int64(len(closure.Args))
mOff = stack.Pop().Int64()
cOff = stack.Pop().Int64()
l = stack.Pop().Int64()
)
if cOff > size {
cOff = 0
l = 0
} else if cOff+l > size {
l = 0
}
code := closure.Args[cOff : cOff+l]
mem.Set(mOff, l, code)
case CODESIZE:
l := big.NewInt(int64(len(closure.Code)))
stack.Push(l)
self.Printf(" => %d", l)
case CODECOPY:
var (
size = int64(len(closure.Code))
mOff = stack.Pop().Int64()
cOff = stack.Pop().Int64()
l = stack.Pop().Int64()
)
if cOff > size {
cOff = 0
l = 0
} else if cOff+l > size {
l = 0
}
code := closure.Code[cOff : cOff+l]
//fmt.Println("len:", l, "code off:", cOff, "mem off:", mOff)
mem.Set(mOff, l, code)
//fmt.Println(Code(mem.Get(mOff, l)))
case GASPRICE:
stack.Push(closure.Price)
self.Printf(" => %v", closure.Price)
// 0x40 range
case PREVHASH:
prevHash := self.env.PrevHash()
stack.Push(ethutil.BigD(prevHash))
self.Printf(" => 0x%x", prevHash)
case COINBASE:
coinbase := self.env.Coinbase()
stack.Push(ethutil.BigD(coinbase))
self.Printf(" => 0x%x", coinbase)
case TIMESTAMP:
time := self.env.Time()
stack.Push(big.NewInt(time))
self.Printf(" => 0x%x", time)
case NUMBER:
number := self.env.BlockNumber()
stack.Push(number)
self.Printf(" => 0x%x", number.Bytes())
case DIFFICULTY:
difficulty := self.env.Difficulty()
stack.Push(difficulty)
self.Printf(" => 0x%x", difficulty.Bytes())
case GASLIMIT:
// TODO
stack.Push(big.NewInt(0))
// 0x50 range
case PUSH1, PUSH2, PUSH3, PUSH4, PUSH5, PUSH6, PUSH7, PUSH8, PUSH9, PUSH10, PUSH11, PUSH12, PUSH13, PUSH14, PUSH15, PUSH16, PUSH17, PUSH18, PUSH19, PUSH20, PUSH21, PUSH22, PUSH23, PUSH24, PUSH25, PUSH26, PUSH27, PUSH28, PUSH29, PUSH30, PUSH31, PUSH32:
a := big.NewInt(int64(op) - int64(PUSH1) + 1)
pc.Add(pc, ethutil.Big1)
data := closure.Gets(pc, a)
val := ethutil.BigD(data.Bytes())
// Push value to stack
stack.Push(val)
pc.Add(pc, a.Sub(a, big.NewInt(1)))
step += int(op) - int(PUSH1) + 1
self.Printf(" => 0x%x", data.Bytes())
case POP:
require(1)
stack.Pop()
case DUP:
require(1)
stack.Push(stack.Peek())
self.Printf(" => 0x%x", stack.Peek().Bytes())
case SWAP:
require(2)
x, y := stack.Popn()
stack.Push(y)
stack.Push(x)
case MLOAD:
require(1)
offset := stack.Pop()
val := ethutil.BigD(mem.Get(offset.Int64(), 32))
stack.Push(val)
self.Printf(" => 0x%x", val.Bytes())
case MSTORE: // Store the value at stack top-1 in to memory at location stack top
require(2)
// Pop value of the stack
val, mStart := stack.Popn()
mem.Set(mStart.Int64(), 32, ethutil.BigToBytes(val, 256))
self.Printf(" => 0x%x", val)
case MSTORE8:
require(2)
val, mStart := stack.Popn()
//base.And(val, new(big.Int).SetInt64(0xff))
//mem.Set(mStart.Int64(), 32, ethutil.BigToBytes(base, 256))
mem.store[mStart.Int64()] = byte(val.Int64() & 0xff)
self.Printf(" => 0x%x", val)
case SLOAD:
require(1)
loc := stack.Pop()
val := closure.GetStorage(loc)
stack.Push(val.BigInt())
self.Printf(" {0x%x : 0x%x}", loc.Bytes(), val.Bytes())
case SSTORE:
require(2)
val, loc := stack.Popn()
closure.SetStorage(loc, ethutil.NewValue(val))
// Add the change to manifest
self.env.State().Manifest().AddStorageChange(closure.Object(), loc.Bytes(), val)
self.Printf(" {0x%x : 0x%x}", loc, val)
case JUMP:
require(1)
pc = stack.Pop()
// Reduce pc by one because of the increment that's at the end of this for loop
self.Printf(" ~> %v", pc).Endl()
continue
case JUMPI:
require(2)
cond, pos := stack.Popn()
if cond.Cmp(ethutil.BigTrue) >= 0 {
pc = pos
self.Printf(" ~> %v (t)", pc).Endl()
continue
} else {
self.Printf(" (f)")
}
case PC:
stack.Push(pc)
case MSIZE:
stack.Push(big.NewInt(int64(mem.Len())))
case GAS:
stack.Push(closure.Gas)
// 0x60 range
case CREATE:
require(3)
var (
err error
value = stack.Pop()
size, offset = stack.Popn()
// Snapshot the current stack so we are able to
// revert back to it later.
snapshot = self.env.State().Copy()
)
// Generate a new address
addr := ethcrypto.CreateAddress(closure.Address(), closure.object.Nonce)
for i := uint64(0); self.env.State().GetStateObject(addr) != nil; i++ {
ethcrypto.CreateAddress(closure.Address(), closure.object.Nonce+i)
}
closure.object.Nonce++
self.Printf(" (*) %x", addr).Endl()
// Create a new contract
contract := self.env.State().NewStateObject(addr)
if contract.Amount.Cmp(value) >= 0 {
closure.object.SubAmount(value)
contract.AddAmount(value)
// Set the init script
initCode := mem.Get(offset.Int64(), size.Int64())
//fmt.Printf("%x\n", initCode)
// Transfer all remaining gas to the new
// contract so it may run the init script
gas := new(big.Int).Set(closure.Gas)
closure.UseGas(closure.Gas)
// Create the closure
c := NewClosure(closure, contract, initCode, gas, closure.Price)
// Call the closure and set the return value as
// main script.
contract.Code, _, err = c.Call(self, nil)
} else {
err = fmt.Errorf("Insufficient funds to transfer value. Req %v, has %v", value, closure.object.Amount)
}
if err != nil {
stack.Push(ethutil.BigFalse)
// Revert the state as it was before.
self.env.State().Set(snapshot)
self.Printf("CREATE err %v", err)
} else {
stack.Push(ethutil.BigD(addr))
self.Printf("CREATE success")
}
self.Endl()
// Debug hook
if self.Dbg != nil {
self.Dbg.SetCode(closure.Code)
}
case CALL:
require(7)
self.Endl()
gas := stack.Pop()
// Pop gas and value of the stack.
value, addr := stack.Popn()
// Pop input size and offset
inSize, inOffset := stack.Popn()
// Pop return size and offset
retSize, retOffset := stack.Popn()
// Get the arguments from the memory
args := mem.Get(inOffset.Int64(), inSize.Int64())
if closure.object.Amount.Cmp(value) < 0 {
vmlogger.Debugf("Insufficient funds to transfer value. Req %v, has %v", value, closure.object.Amount)
closure.ReturnGas(gas, nil)
stack.Push(ethutil.BigFalse)
} else {
snapshot := self.env.State().Copy()
stateObject := self.env.State().GetOrNewStateObject(addr.Bytes())
closure.object.SubAmount(value)
stateObject.AddAmount(value)
// Create a new callable closure
c := NewClosure(closure, stateObject, stateObject.Code, gas, closure.Price)
// Executer the closure and get the return value (if any)
ret, _, err := c.Call(self, args)
if err != nil {
stack.Push(ethutil.BigFalse)
vmlogger.Debugf("Closure execution failed. %v\n", err)
self.env.State().Set(snapshot)
} else {
stack.Push(ethutil.BigTrue)
mem.Set(retOffset.Int64(), retSize.Int64(), ret)
}
// Debug hook
if self.Dbg != nil {
self.Dbg.SetCode(closure.Code)
}
}
case RETURN:
require(2)
size, offset := stack.Popn()
ret := mem.Get(offset.Int64(), size.Int64())
self.Printf(" => (%d) 0x%x", len(ret), ret).Endl()
return closure.Return(ret), nil
case SUICIDE:
require(1)
receiver := self.env.State().GetOrNewStateObject(stack.Pop().Bytes())
receiver.AddAmount(closure.object.Amount)
closure.object.MarkForDeletion()
fallthrough
case STOP: // Stop the closure
self.Endl()
return closure.Return(nil), nil
default:
vmlogger.Debugf("(pc) %-3v Invalid opcode %x\n", pc, op)
fmt.Println(ethstate.Code(closure.Code))
return closure.Return(nil), fmt.Errorf("Invalid opcode %x", op)
}
pc.Add(pc, ethutil.Big1)
self.Endl()
if self.Dbg != nil {
for _, instrNo := range self.Dbg.BreakPoints() {
if pc.Cmp(big.NewInt(instrNo)) == 0 {
self.Stepping = true
if !self.Dbg.BreakHook(prevStep, op, mem, stack, closure.Object()) {
return nil, nil
}
} else if self.Stepping {
if !self.Dbg.StepHook(prevStep, op, mem, stack, closure.Object()) {
return nil, nil
}
}
}
}
}
}
func (self *Vm) Printf(format string, v ...interface{}) *Vm {
if self.Verbose && self.logTy == LogTyPretty {
self.logStr += fmt.Sprintf(format, v...)
}
return self
}
func (self *Vm) Endl() *Vm {
if self.Verbose && self.logTy == LogTyPretty {
vmlogger.Debugln(self.logStr)
self.logStr = ""
}
return self
}

48
ethvm/vm_test.go Normal file
View File

@ -0,0 +1,48 @@
package ethvm
import (
"fmt"
"github.com/ethereum/eth-go/ethdb"
"github.com/ethereum/eth-go/ethlog"
"github.com/ethereum/eth-go/ethstate"
"github.com/ethereum/eth-go/ethutil"
"log"
"math/big"
"os"
"testing"
)
type TestEnv struct {
}
func (self TestEnv) GetObject() Object { return nil }
func (self TestEnv) Origin() []byte { return nil }
func (self TestEnv) BlockNumber() *big.Int { return nil }
func (self TestEnv) PrevHash() []byte { return nil }
func (self TestEnv) Coinbase() []byte { return nil }
func (self TestEnv) Time() int64 { return 0 }
func (self TestEnv) Difficulty() *big.Int { return nil }
func (self TestEnv) Data() []string { return nil }
func (self TestEnv) Value() *big.Int { return nil }
func (self TestEnv) GetBalance(addr []byte) *big.Int { return nil }
func (self TestEnv) State() *ethstate.State { return nil }
func TestVm(t *testing.T) {
ethlog.AddLogSystem(ethlog.NewStdLogSystem(os.Stdout, log.LstdFlags, ethlog.LogLevel(4)))
db, _ := ethdb.NewMemDatabase()
ethutil.ReadConfig(".ethtest", "/tmp/ethtest", "")
ethutil.Config.Db = db
stateObject := ethstate.NewStateObject([]byte{'j', 'e', 'f', 'f'})
callerClosure := NewClosure(stateObject, stateObject, []byte{0x60, 0x01}, big.NewInt(1000000), big.NewInt(0))
vm := New(TestEnv{})
vm.Verbose = true
ret, _, e := callerClosure.Call(vm, nil)
if e != nil {
fmt.Println("error", e)
}
fmt.Println(ret)
}