aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--ethchain/closure.go68
-rw-r--r--ethchain/vm.go98
-rw-r--r--ethchain/vm_test.go56
3 files changed, 217 insertions, 5 deletions
diff --git a/ethchain/closure.go b/ethchain/closure.go
new file mode 100644
index 000000000..4ef43a2da
--- /dev/null
+++ b/ethchain/closure.go
@@ -0,0 +1,68 @@
+package ethchain
+
+// TODO Re write VM to use values instead of big integers?
+
+import (
+ "github.com/ethereum/eth-go/ethutil"
+ "math/big"
+)
+
+type Callee interface {
+ ReturnGas(*big.Int, *State)
+}
+
+type ClosureBody interface {
+ Callee
+ ethutil.RlpEncodable
+ GetMem(int64) *ethutil.Value
+}
+
+// Basic inline closure object which implement the 'closure' interface
+type Closure struct {
+ callee Callee
+ object ClosureBody
+ state *State
+
+ gas *big.Int
+ val *big.Int
+}
+
+// Create a new closure for the given data items
+func NewClosure(callee Callee, object ClosureBody, state *State, gas, val *big.Int) *Closure {
+ return &Closure{callee, object, state, gas, val}
+}
+
+// Retuns the x element in data slice
+func (c *Closure) GetMem(x int64) *ethutil.Value {
+ m := c.object.GetMem(x)
+ if m == nil {
+ return ethutil.EmptyValue()
+ }
+
+ return m
+}
+
+func (c *Closure) Return(ret []byte) []byte {
+ // Return the remaining gas to the callee
+ // If no callee is present return it to
+ // the origin (i.e. contract or tx)
+ if c.callee != nil {
+ c.callee.ReturnGas(c.gas, c.state)
+ } else {
+ c.object.ReturnGas(c.gas, c.state)
+ // TODO incase it's a POST contract we gotta serialise the contract again.
+ // But it's not yet defined
+ }
+
+ return ret
+}
+
+// Implement the Callee interface
+func (c *Closure) ReturnGas(gas *big.Int, state *State) {
+ // Return the gas to the closure
+ c.gas.Add(c.gas, gas)
+}
+
+func (c *Closure) GetGas() *big.Int {
+ return c.gas
+}
diff --git a/ethchain/vm.go b/ethchain/vm.go
index 7e119ac99..861b041d8 100644
--- a/ethchain/vm.go
+++ b/ethchain/vm.go
@@ -32,13 +32,101 @@ type RuntimeVars struct {
txData []string
}
+func (vm *Vm) RunClosure(closure *Closure, state *State, vars RuntimeVars) []byte {
+ // If the amount of gas supplied is less equal to 0
+ if closure.GetGas().Cmp(big.NewInt(0)) <= 0 {
+ // TODO Do something
+ }
+
+ // Memory for the current closure
+ var mem []byte
+ // New stack (should this be shared?)
+ stack := NewStack()
+ // Instruction pointer
+ pc := int64(0)
+ // Current address
+ //addr := vars.address
+ step := 0
+
+ if ethutil.Config.Debug {
+ ethutil.Config.Log.Debugf("# op\n")
+ }
+
+ for {
+ step++
+ // Get the memory location of pc
+ val := closure.GetMem(pc)
+ // Get the opcode (it must be an opcode!)
+ op := OpCode(val.Uint())
+ if ethutil.Config.Debug {
+ ethutil.Config.Log.Debugf("%-3d %-4s", pc, op.String())
+ }
+
+ // TODO Get each instruction cost properly
+ fee := new(big.Int)
+ fee.Add(fee, big.NewInt(1000))
+
+ if closure.GetGas().Cmp(fee) < 0 {
+ return closure.Return(nil)
+ }
+
+ switch op {
+ case oSTOP:
+ return closure.Return(nil)
+ case oPUSH:
+ pc++
+ val := closure.GetMem(pc).BigInt()
+ stack.Push(val)
+ case oMSTORE:
+ // Pop value of the stack
+ val := stack.Pop()
+ // Set the bytes to the memory field
+ mem = append(mem, ethutil.BigToBytes(val, 256)...)
+ case oCALL:
+ // Pop return size and offset
+ retSize, retOffset := stack.Popn()
+ // Pop input size and offset
+ inSize, inOffset := stack.Popn()
+ // TODO remove me.
+ fmt.Sprintln(inSize, inOffset)
+ // Pop gas and value of the stack.
+ gas, value := stack.Popn()
+ // Closure addr
+ addr := stack.Pop()
+
+ contract := state.GetContract(addr.Bytes())
+ closure := NewClosure(closure, contract, state, gas, value)
+ ret := vm.RunClosure(closure, state, vars)
+
+ // Ensure that memory is large enough to hold the returned data
+ totSize := new(big.Int).Add(retOffset, retSize)
+ lenSize := big.NewInt(int64(len(mem)))
+ // Resize the current memory slice so that the return value may fit
+ if totSize.Cmp(lenSize) > 0 {
+ diff := new(big.Int).Sub(totSize, lenSize)
+ newSlice := make([]byte, diff.Int64()+1)
+ mem = append(mem, newSlice...)
+ }
+ copy(mem[retOffset.Int64():retOffset.Int64()+retSize.Int64()+1], ret)
+ case oRETURN:
+ size, offset := stack.Popn()
+ ret := mem[offset.Int64() : offset.Int64()+size.Int64()+1]
+
+ return closure.Return(ret)
+ }
+
+ pc++
+ }
+}
+
+// Old VM code
func (vm *Vm) Process(contract *Contract, state *State, vars RuntimeVars) {
vm.mem = make(map[string]*big.Int)
vm.stack = NewStack()
addr := vars.address // tx.Hash()[12:]
// Instruction pointer
- pc := 0
+ pc := int64(0)
if contract == nil {
fmt.Println("Contract not found")
@@ -344,7 +432,7 @@ out:
contract.SetAddr(addr, y)
//contract.State().Update(string(idx), string(y))
case oJMP:
- x := int(vm.stack.Pop().Uint64())
+ x := vm.stack.Pop().Int64()
// Set pc to x - 1 (minus one so the incrementing at the end won't effect it)
pc = x
pc--
@@ -352,7 +440,7 @@ out:
x := vm.stack.Pop()
// Set pc to x if it's non zero
if x.Cmp(ethutil.BigFalse) != 0 {
- pc = int(x.Uint64())
+ pc = x.Int64()
pc--
}
case oIND:
@@ -400,9 +488,9 @@ out:
func makeInlineTx(addr []byte, value, from, length *big.Int, contract *Contract, state *State) {
ethutil.Config.Log.Debugf(" => creating inline tx %x %v %v %v", addr, value, from, length)
- j := 0
+ j := int64(0)
dataItems := make([]string, int(length.Uint64()))
- for i := from.Uint64(); i < length.Uint64(); i++ {
+ for i := from.Int64(); i < length.Int64(); i++ {
dataItems[j] = contract.GetMem(j).Str()
j++
}
diff --git a/ethchain/vm_test.go b/ethchain/vm_test.go
index 6ceb0ff15..4e72c9249 100644
--- a/ethchain/vm_test.go
+++ b/ethchain/vm_test.go
@@ -8,6 +8,8 @@ import (
"testing"
)
+/*
+
func TestRun(t *testing.T) {
InitFees()
@@ -104,3 +106,57 @@ func TestRun2(t *testing.T) {
txData: tx.Data,
})
}
+*/
+
+// XXX Full stack test
+func TestRun3(t *testing.T) {
+ ethutil.ReadConfig("")
+
+ db, _ := ethdb.NewMemDatabase()
+ state := NewState(ethutil.NewTrie(db, ""))
+
+ script := Compile([]string{
+ "PUSH", "300",
+ "MSTORE",
+ "PUSH", "300",
+ "MSTORE",
+ "PUSH", "62",
+ "PUSH", "0",
+ "RETURN",
+ })
+ tx := NewTransaction(ContractAddr, ethutil.Big("100000000000000000000000000000000000000000000000000"), script)
+ addr := tx.Hash()[12:]
+ fmt.Printf("addr contract %x\n", addr)
+ contract := MakeContract(tx, state)
+ state.UpdateContract(addr, contract)
+
+ callerScript := Compile([]string{
+ "PUSH", "62", // REND
+ "PUSH", "0", // RSTART
+ "PUSH", "22", // MEND
+ "PUSH", "15", // MSTART
+ "PUSH", "1000", /// Gas
+ "PUSH", "0", /// value
+ "PUSH", string(addr), // Sender
+ "CALL",
+ })
+ callerTx := NewTransaction(ContractAddr, ethutil.Big("100000000000000000000000000000000000000000000000000"), callerScript)
+ callerAddr := callerTx.Hash()[12:]
+ executer := NewTransaction(ContractAddr, ethutil.Big("10000"), nil)
+
+ executer.Sign([]byte("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"))
+ callerClosure := NewClosure(executer, MakeContract(callerTx, state), state, big.NewInt(1000000000), new(big.Int))
+
+ vm := &Vm{}
+ vm.RunClosure(callerClosure, state, RuntimeVars{
+ address: callerAddr,
+ blockNumber: 1,
+ sender: ethutil.FromHex("cd1722f3947def4cf144679da39c4c32bdc35681"),
+ prevHash: ethutil.FromHex("5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6"),
+ coinbase: ethutil.FromHex("2adc25665018aa1fe0e6bc666dac8fc2697ff9ba"),
+ time: 1,
+ diff: big.NewInt(256),
+ txValue: big.NewInt(10000),
+ txData: nil,
+ })
+}