From fa1db8d2dcbc12fd9b343e6572c541d92fe7cb55 Mon Sep 17 00:00:00 2001 From: obscuren Date: Fri, 21 Mar 2014 11:54:36 +0100 Subject: [PATCH] Implemented closure arguments --- ethchain/stack.go | 28 +++++-- ethchain/vm.go | 184 +++++++++++++++++++++++++++++++++++++++++--- ethchain/vm_test.go | 29 +++++-- ethutil/parsing.go | 6 +- 4 files changed, 225 insertions(+), 22 deletions(-) diff --git a/ethchain/stack.go b/ethchain/stack.go index 429c31d08..c75d02dda 100644 --- a/ethchain/stack.go +++ b/ethchain/stack.go @@ -68,12 +68,16 @@ const ( oJUMP = 0x59 oJUMPI = 0x5a oPC = 0x5b - oMEMSIZE = 0x5c + oMSIZE = 0x5c // 0x60 range - closures oCREATE = 0x60 oCALL = 0x61 oRETURN = 0x62 + + // 0x70 range - other + oLOG = 0x70 // XXX Unofficial + oSUICIDE = 0x7f ) // Since the opcodes aren't all in order we can't use a regular slice @@ -136,12 +140,16 @@ var opCodeToString = map[OpCode]string{ oJUMP: "JUMP", oJUMPI: "JUMPI", oPC: "PC", - oMEMSIZE: "MEMSIZE", + oMSIZE: "MSIZE", // 0x60 range - closures oCREATE: "CREATE", oCALL: "CALL", oRETURN: "RETURN", + + // 0x70 range - other + oLOG: "LOG", + oSUICIDE: "SUICIDE", } func (o OpCode) String() string { @@ -215,20 +223,30 @@ type Memory struct { func (m *Memory) Set(offset, size int64, value []byte) { totSize := offset + size - lenSize := int64(len(m.store)) + 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) + newSlice := make([]byte, diff-1) // Resize slice m.store = append(m.store, newSlice...) } } - copy(m.store[offset:offset+size+1], value) + copy(m.store[offset:offset+size], value) } func (m *Memory) Get(offset, size int64) []byte { return m.store[offset : offset+size] } + +func (m *Memory) Print() { + fmt.Println("### MEM ###") + if len(m.store) > 0 { + fmt.Println(m.store) + } else { + fmt.Println("-- empty --") + } + fmt.Println("###########") +} diff --git a/ethchain/vm.go b/ethchain/vm.go index ba19231cc..3d2ee4c86 100644 --- a/ethchain/vm.go +++ b/ethchain/vm.go @@ -2,7 +2,7 @@ package ethchain import ( _ "bytes" - "fmt" + _ "fmt" "github.com/ethereum/eth-go/ethutil" _ "github.com/obscuren/secp256k1-go" "log" @@ -36,6 +36,8 @@ func NewVm(state *State, vars RuntimeVars) *Vm { return &Vm{vars: vars, state: state} } +var Pow256 = ethutil.BigPow(2, 256) + func (vm *Vm) RunClosure(closure *Closure) []byte { // If the amount of gas supplied is less equal to 0 if closure.GetGas().Cmp(big.NewInt(0)) <= 0 { @@ -48,9 +50,10 @@ func (vm *Vm) RunClosure(closure *Closure) []byte { stack := NewStack() // Instruction pointer pc := int64(0) - // Current address - //addr := vars.address + // Current step count step := 0 + // The base for all big integer arithmetic + base := new(big.Int) if ethutil.Config.Debug { ethutil.Config.Log.Debugf("# op\n") @@ -75,27 +78,171 @@ func (vm *Vm) RunClosure(closure *Closure) []byte { } switch op { + case oLOG: + stack.Print() + mem.Print() case oSTOP: // Stop the closure return closure.Return(nil) + + // 0x20 range + case oADD: + x, y := stack.Popn() + // (x + y) % 2 ** 256 + base.Add(x, y) + base.Mod(base, Pow256) + // Pop result back on the stack + stack.Push(base) + case oSUB: + x, y := stack.Popn() + // (x - y) % 2 ** 256 + base.Sub(x, y) + base.Mod(base, Pow256) + // Pop result back on the stack + stack.Push(base) + case oMUL: + x, y := stack.Popn() + // (x * y) % 2 ** 256 + base.Mul(x, y) + base.Mod(base, Pow256) + // Pop result back on the stack + stack.Push(base) + case oDIV: + x, y := stack.Popn() + // floor(x / y) + base.Div(x, y) + // Pop result back on the stack + stack.Push(base) + case oSDIV: + 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 oMOD: + x, y := stack.Popn() + base.Mod(x, y) + stack.Push(base) + case oSMOD: + 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 oEXP: + x, y := stack.Popn() + base.Exp(x, y, Pow256) + + stack.Push(base) + case oNEG: + base.Sub(Pow256, stack.Pop()) + stack.Push(base) + case oLT: + x, y := stack.Popn() + // x < y + if x.Cmp(y) < 0 { + stack.Push(ethutil.BigTrue) + } else { + stack.Push(ethutil.BigFalse) + } + case oGT: + x, y := stack.Popn() + // x > y + if x.Cmp(y) > 0 { + stack.Push(ethutil.BigTrue) + } else { + stack.Push(ethutil.BigFalse) + } + case oNOT: + x, y := stack.Popn() + // x != y + if x.Cmp(y) != 0 { + stack.Push(ethutil.BigTrue) + } else { + stack.Push(ethutil.BigFalse) + } + + // 0x10 range + case oAND: + case oOR: + case oXOR: + case oBYTE: + + // 0x20 range + case oSHA3: + + // 0x30 range + case oADDRESS: + case oBALANCE: + case oORIGIN: + case oCALLER: + case oCALLVALUE: + case oCALLDATA: + offset := stack.Pop() + mem.Set(offset.Int64(), int64(len(closure.Args)), closure.Args) + case oCALLDATASIZE: + case oRETURNDATASIZE: + case oTXGASPRICE: + + // 0x40 range + case oPREVHASH: + case oPREVNONCE: + case oCOINBASE: + case oTIMESTAMP: + case oNUMBER: + case oDIFFICULTY: + case oGASLIMIT: + + // 0x50 range case oPUSH: // Push PC+1 on to the stack pc++ val := closure.GetMem(pc).BigInt() stack.Push(val) + case oPOP: + case oDUP: + case oSWAP: + case oMLOAD: + offset := stack.Pop() + stack.Push(ethutil.BigD(mem.Get(offset.Int64(), 32))) case oMSTORE: // Store the value at stack top-1 in to memory at location stack top // Pop value of the stack val, mStart := stack.Popn() mem.Set(mStart.Int64(), 32, ethutil.BigToBytes(val, 256)) + case oMSTORE8: + case oSLOAD: + case oSSTORE: + case oJUMP: + case oJUMPI: + case oPC: + case oMSIZE: - case oCALLDATA: - offset := stack.Pop() - mem.Set(offset.Int64(), int64(len(closure.Args)), closure.Args) + // 0x60 range 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) + // Get the arguments from the memory + args := mem.Get(inOffset.Int64(), inSize.Int64()) // Pop gas and value of the stack. gas, value := stack.Popn() // Closure addr @@ -105,7 +252,7 @@ func (vm *Vm) RunClosure(closure *Closure) []byte { // Create a new callable closure closure := NewClosure(closure, contract, vm.state, gas, value) // Executer the closure and get the return value (if any) - ret := closure.Call(vm, nil) + ret := closure.Call(vm, args) mem.Set(retOffset.Int64(), retSize.Int64(), ret) case oRETURN: @@ -113,6 +260,25 @@ func (vm *Vm) RunClosure(closure *Closure) []byte { ret := mem.Get(offset.Int64(), size.Int64()) return closure.Return(ret) + case oSUICIDE: + /* + recAddr := stack.Pop().Bytes() + // Purge all memory + deletedMemory := contract.state.Purge() + // Add refunds to the pop'ed address + refund := new(big.Int).Mul(StoreFee, big.NewInt(int64(deletedMemory))) + account := state.GetAccount(recAddr) + account.Amount.Add(account.Amount, refund) + // Update the refunding address + state.UpdateAccount(recAddr, account) + // Delete the contract + state.trie.Update(string(addr), "") + + ethutil.Config.Log.Debugf("(%d) => %x\n", deletedMemory, recAddr) + break out + */ + default: + ethutil.Config.Log.Debugln("Invalid opcode", op) } pc++ diff --git a/ethchain/vm_test.go b/ethchain/vm_test.go index 1dca5cfb7..ce8c7a4de 100644 --- a/ethchain/vm_test.go +++ b/ethchain/vm_test.go @@ -1,6 +1,7 @@ package ethchain import ( + "bytes" "fmt" "github.com/ethereum/eth-go/ethdb" "github.com/ethereum/eth-go/ethutil" @@ -119,11 +120,13 @@ func TestRun3(t *testing.T) { "PUSH", "300", "PUSH", "0", "MSTORE", - "PUSH", "300", - "PUSH", "31", - "MSTORE", - "PUSH", "62", + + "PUSH", "32", + "CALLDATA", + + "PUSH", "64", "PUSH", "0", + "LOG", "RETURN", }) tx := NewTransaction(ContractAddr, ethutil.Big("100000000000000000000000000000000000000000000000000"), script) @@ -133,14 +136,21 @@ func TestRun3(t *testing.T) { state.UpdateContract(contract) callerScript := Compile([]string{ - "PUSH", "62", // ret size + "PUSH", "1337", // Argument + "PUSH", "65", // argument mem offset + "MSTORE", + "PUSH", "64", // ret size "PUSH", "0", // ret offset + "PUSH", "32", // arg size - "PUSH", "63", // arg offset + "PUSH", "65", // arg offset "PUSH", "1000", /// Gas "PUSH", "0", /// value "PUSH", string(addr), // Sender "CALL", + "PUSH", "64", + "PUSH", "0", + "RETURN", }) callerTx := NewTransaction(ContractAddr, ethutil.Big("100000000000000000000000000000000000000000000000000"), callerScript) @@ -158,5 +168,10 @@ func TestRun3(t *testing.T) { // XXX Tx data? Could be just an argument to the closure instead txData: nil, }) - callerClosure.Call(vm, nil) + ret := callerClosure.Call(vm, nil) + + exp := []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 44, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 57} + if bytes.Compare(ret, exp) != 0 { + t.Errorf("expected return value to be %v, got %v", exp, ret) + } } diff --git a/ethutil/parsing.go b/ethutil/parsing.go index 9ff2827a0..b2e9d9fee 100644 --- a/ethutil/parsing.go +++ b/ethutil/parsing.go @@ -65,12 +65,16 @@ var OpCodes = map[string]byte{ "JUMP": 0x59, "JUMPI": 0x5a, "PC": 0x5b, - "MEMSIZE": 0x5c, + "MSIZE": 0x5c, // 0x60 range - closures "CREATE": 0x60, "CALL": 0x61, "RETURN": 0x62, + + // 0x70 range - other + "LOG": 0x70, + "SUICIDE": 0x7f, } func IsOpCode(s string) bool {