From b6d88a0e9f9aaeb47d585d79c768d457b545af90 Mon Sep 17 00:00:00 2001 From: Jeffrey Wilcke Date: Tue, 19 Jan 2016 23:50:00 +0100 Subject: core, core/vm, crypto: fixes for homestead * Removed some strange code that didn't apply state reverting properly * Refactored code setting from vm & state transition to the executioner * Updated tests --- core/vm/contract.go | 44 +++++++++++++++++++++++++-------------- core/vm/environment.go | 1 - core/vm/instructions.go | 27 +++++++++++++++++------- core/vm/jit.go | 21 ++++++++++++++++--- core/vm/jit_test.go | 5 +++++ core/vm/jump_table.go | 23 +++++++++++++++++---- core/vm/jump_table_test.go | 24 ++++++++++++++++++++++ core/vm/opcodes.go | 1 + core/vm/vm.go | 51 +++++++--------------------------------------- 9 files changed, 123 insertions(+), 74 deletions(-) create mode 100644 core/vm/jump_table_test.go (limited to 'core/vm') diff --git a/core/vm/contract.go b/core/vm/contract.go index dac81a529..d23995218 100644 --- a/core/vm/contract.go +++ b/core/vm/contract.go @@ -26,7 +26,6 @@ import ( type ContractRef interface { ReturnGas(*big.Int, *big.Int) Address() common.Address - SetAddress(common.Address) Value() *big.Int SetCode([]byte) EachStorage(cb func(key, value []byte)) @@ -35,8 +34,12 @@ type ContractRef interface { // Contract represents an ethereum contract in the state database. It contains // the the contract code, calling arguments. Contract implements ContractRef type Contract struct { - caller ContractRef - self ContractRef + // CallerAddress is the result of the caller which initialised this + // contract. However when the "call method" is delegated this value + // needs to be initialised to that of the caller's caller. + CallerAddress common.Address + caller ContractRef + self ContractRef jumpdests destinations // result of JUMPDEST analysis. @@ -51,9 +54,9 @@ type Contract struct { DelegateCall bool } -// Create a new context for the given data items. +// NewContract returns a new contract environment for the execution of EVM. func NewContract(caller ContractRef, object ContractRef, value, gas, price *big.Int) *Contract { - c := &Contract{caller: caller, self: object, Args: nil} + c := &Contract{CallerAddress: caller.Address(), caller: caller, self: object, Args: nil} if parent, ok := caller.(*Contract); ok { // Reuse JUMPDEST analysis from parent context if available. @@ -74,6 +77,16 @@ func NewContract(caller ContractRef, object ContractRef, value, gas, price *big. return c } +// AsDelegate sets the contract to be a delegate call and returns the current +// contract (for chaining calls) +func (c *Contract) AsDelegate() *Contract { + c.DelegateCall = true + // NOTE: caller must, at all times be a contract. It should never happen + // that caller is something other than a Contract. + c.CallerAddress = c.caller.(*Contract).CallerAddress + return c +} + // GetOp returns the n'th element in the contract's byte array func (c *Contract) GetOp(n uint64) OpCode { return OpCode(c.GetByte(n)) @@ -88,13 +101,19 @@ func (c *Contract) GetByte(n uint64) byte { return 0 } -// Return returns the given ret argument and returns any remaining gas to the -// caller -func (c *Contract) Return(ret []byte) []byte { +// Caller returns the caller of the contract. +// +// Caller will recursively call caller when the contract is a delegate +// call, including that of caller's caller. +func (c *Contract) Caller() common.Address { + return c.CallerAddress +} + +// Finalise finalises the contract and returning any remaining gas to the original +// caller. +func (c *Contract) Finalise() { // Return the remaining gas to the caller c.caller.ReturnGas(c.Gas, c.Price) - - return ret } // UseGas attempts the use gas and subtracts it and returns true on success @@ -118,11 +137,6 @@ func (c *Contract) Address() common.Address { return c.self.Address() } -// SetAddress sets the contracts address -func (c *Contract) SetAddress(addr common.Address) { - c.self.SetAddress(addr) -} - // Value returns the contracts value (sent to it from it's caller) func (c *Contract) Value() *big.Int { return c.value diff --git a/core/vm/environment.go b/core/vm/environment.go index dc60af2ca..a58e3ba2b 100644 --- a/core/vm/environment.go +++ b/core/vm/environment.go @@ -121,7 +121,6 @@ type Account interface { SetNonce(uint64) Balance() *big.Int Address() common.Address - SetAddress(common.Address) ReturnGas(*big.Int, *big.Int) SetCode([]byte) EachStorage(cb func(key, value []byte)) diff --git a/core/vm/instructions.go b/core/vm/instructions.go index 9d3d4e6fe..26f7671ff 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -337,13 +337,7 @@ func opOrigin(instr instruction, pc *uint64, env Environment, contract *Contract } func opCaller(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *stack) { - var bigAddr *big.Int - if contract.DelegateCall { - bigAddr = env.Origin().Big() - } else { - bigAddr = contract.caller.Address().Big() - } - stack.push(bigAddr) + stack.push(contract.Caller().Big()) } func opCallValue(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *stack) { @@ -514,6 +508,25 @@ func opGas(instr instruction, pc *uint64, env Environment, contract *Contract, m } func opCreate(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *stack) { + var ( + value = stack.pop() + offset, size = stack.pop(), stack.pop() + input = memory.Get(offset.Int64(), size.Int64()) + gas = new(big.Int).Set(contract.Gas) + ) + contract.UseGas(contract.Gas) + _, addr, suberr := env.Create(contract, input, gas, contract.Price, value) + // Push item on the stack based on the returned error. If the ruleset is + // homestead we must check for CodeStoreOutOfGasError (homestead only + // rule) and treat as an error, if the ruleset is frontier we must + // ignore this error and pretend the operation was successful. + if params.IsHomestead(env.BlockNumber()) && suberr == CodeStoreOutOfGasError { + stack.push(new(big.Int)) + } else if suberr != nil && suberr != CodeStoreOutOfGasError { + stack.push(new(big.Int)) + } else { + stack.push(addr.Big()) + } } func opCall(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *stack) { diff --git a/core/vm/jit.go b/core/vm/jit.go index 1aa7d7ef2..504aab523 100644 --- a/core/vm/jit.go +++ b/core/vm/jit.go @@ -275,6 +275,11 @@ func CompileProgram(program *Program) (err error) { program.addInstr(op, pc, opGas, nil) case CREATE: program.addInstr(op, pc, opCreate, nil) + case DELEGATECALL: + // Instruction added regardless of homestead phase. + // Homestead (and execution of the opcode) is checked during + // runtime. + program.addInstr(op, pc, opDelegateCall, nil) case CALL: program.addInstr(op, pc, opCall, nil) case CALLCODE: @@ -317,10 +322,14 @@ func runProgram(program *Program, pcstart uint64, mem *Memory, stack *stack, env }() } + homestead := params.IsHomestead(env.BlockNumber()) for pc < uint64(len(program.instructions)) { instrCount++ instr := program.instructions[pc] + if instr.Op() == DELEGATECALL && !homestead { + return nil, fmt.Errorf("Invalid opcode 0x%x", instr.Op()) + } ret, err := instr.do(program, &pc, env, contract, mem, stack) if err != nil { @@ -328,13 +337,13 @@ func runProgram(program *Program, pcstart uint64, mem *Memory, stack *stack, env } if instr.halts() { - return contract.Return(ret), nil + return ret, nil } } contract.Input = nil - return contract.Return(nil), nil + return nil, nil } // validDest checks if the given distination is a valid one given the @@ -457,7 +466,6 @@ func jitCalculateGasAndSize(env Environment, contract *Contract, instr instructi gas.Add(gas, stack.data[stack.len()-1]) if op == CALL { - //if env.Db().GetStateObject(common.BigToAddress(stack.data[stack.len()-2])) == nil { if !env.Db().Exist(common.BigToAddress(stack.data[stack.len()-2])) { gas.Add(gas, params.CallNewAccountGas) } @@ -470,6 +478,13 @@ func jitCalculateGasAndSize(env Environment, contract *Contract, instr instructi x := calcMemSize(stack.data[stack.len()-6], stack.data[stack.len()-7]) y := calcMemSize(stack.data[stack.len()-4], stack.data[stack.len()-5]) + newMemSize = common.BigMax(x, y) + case DELEGATECALL: + gas.Add(gas, stack.data[stack.len()-1]) + + x := calcMemSize(stack.data[stack.len()-5], stack.data[stack.len()-6]) + y := calcMemSize(stack.data[stack.len()-3], stack.data[stack.len()-4]) + newMemSize = common.BigMax(x, y) } quadMemGas(mem, newMemSize, gas) diff --git a/core/vm/jit_test.go b/core/vm/jit_test.go index 8c50ed0f5..e8e078a46 100644 --- a/core/vm/jit_test.go +++ b/core/vm/jit_test.go @@ -127,6 +127,8 @@ type account struct{} func (account) SubBalance(amount *big.Int) {} func (account) AddBalance(amount *big.Int) {} +func (account) SetAddress(common.Address) {} +func (account) Value() *big.Int { return nil } func (account) SetBalance(*big.Int) {} func (account) SetNonce(uint64) {} func (account) Balance() *big.Int { return nil } @@ -206,3 +208,6 @@ func (self *Env) CallCode(caller ContractRef, addr common.Address, data []byte, func (self *Env) Create(caller ContractRef, data []byte, gas, price, value *big.Int) ([]byte, common.Address, error) { return nil, common.Address{}, nil } +func (self *Env) DelegateCall(me ContractRef, addr common.Address, data []byte, gas, price *big.Int) ([]byte, error) { + return nil, nil +} diff --git a/core/vm/jump_table.go b/core/vm/jump_table.go index 222c93854..37d7bb160 100644 --- a/core/vm/jump_table.go +++ b/core/vm/jump_table.go @@ -1,13 +1,29 @@ package vm -import "math/big" +import ( + "math/big" + + "github.com/ethereum/go-ethereum/params" +) type jumpPtr struct { fn instrFn valid bool } -var jumpTable [256]jumpPtr +type vmJumpTable [256]jumpPtr + +func (jt vmJumpTable) init(blockNumber *big.Int) { + // when initialising a new VM execution we must first check the homestead + // changes. + if params.IsHomestead(blockNumber) { + jumpTable[DELEGATECALL] = jumpPtr{opDelegateCall, true} + } else { + jumpTable[DELEGATECALL] = jumpPtr{nil, false} + } +} + +var jumpTable vmJumpTable func init() { jumpTable[ADD] = jumpPtr{opAdd, true} @@ -62,10 +78,9 @@ func init() { jumpTable[PC] = jumpPtr{nil, true} jumpTable[MSIZE] = jumpPtr{opMsize, true} jumpTable[GAS] = jumpPtr{opGas, true} - jumpTable[CREATE] = jumpPtr{nil, true} + jumpTable[CREATE] = jumpPtr{opCreate, true} jumpTable[CALL] = jumpPtr{opCall, true} jumpTable[CALLCODE] = jumpPtr{opCallCode, true} - jumpTable[DELEGATECALL] = jumpPtr{opDelegateCall, true} jumpTable[LOG0] = jumpPtr{makeLog(0), true} jumpTable[LOG1] = jumpPtr{makeLog(1), true} jumpTable[LOG2] = jumpPtr{makeLog(2), true} diff --git a/core/vm/jump_table_test.go b/core/vm/jump_table_test.go new file mode 100644 index 000000000..98d34bef2 --- /dev/null +++ b/core/vm/jump_table_test.go @@ -0,0 +1,24 @@ +package vm + +import ( + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/params" +) + +func TestInit(t *testing.T) { + params.HomesteadBlock = big.NewInt(1) + + jumpTable.init(big.NewInt(0)) + if jumpTable[DELEGATECALL].valid { + t.Error("Expected DELEGATECALL not to be present") + } + + for _, n := range []int64{1, 2, 100} { + jumpTable.init(big.NewInt(n)) + if !jumpTable[DELEGATECALL].valid { + t.Error("Expected DELEGATECALL to be present for block", n) + } + } +} diff --git a/core/vm/opcodes.go b/core/vm/opcodes.go index 00593ae95..7d861f1de 100644 --- a/core/vm/opcodes.go +++ b/core/vm/opcodes.go @@ -404,6 +404,7 @@ var stringToOp = map[string]OpCode{ "CALLDATALOAD": CALLDATALOAD, "CALLDATASIZE": CALLDATASIZE, "CALLDATACOPY": CALLDATACOPY, + "DELEGATECALL": DELEGATECALL, "CODESIZE": CODESIZE, "CODECOPY": CODECOPY, "GASPRICE": GASPRICE, diff --git a/core/vm/vm.go b/core/vm/vm.go index 863b2cc0d..320135ff2 100644 --- a/core/vm/vm.go +++ b/core/vm/vm.go @@ -35,6 +35,9 @@ type Vm struct { // New returns a new Vm func New(env Environment) *Vm { + // init the jump table. Also prepares the homestead changes + jumpTable.init(env.BlockNumber()) + return &Vm{env: env} } @@ -43,16 +46,6 @@ func (self *Vm) Run(contract *Contract, input []byte) (ret []byte, err error) { self.env.SetDepth(self.env.Depth() + 1) defer self.env.SetDepth(self.env.Depth() - 1) - // User defer pattern to check for an error and, based on the error being nil or not, use all gas and return. - defer func() { - if err != nil { - // In case of a VM exception (known exceptions) all gas consumed (panics NOT included). - contract.UseGas(contract.Gas) - - ret = contract.Return(nil) - } - }() - if contract.CodeAddr != nil { if p := Precompiled[contract.CodeAddr.Str()]; p != nil { return self.RunPrecompiled(p, input, contract) @@ -61,7 +54,7 @@ func (self *Vm) Run(contract *Contract, input []byte) (ret []byte, err error) { // Don't bother with the execution if there's no code. if len(contract.Code) == 0 { - return contract.Return(nil), nil + return nil, nil } var ( @@ -199,46 +192,17 @@ func (self *Vm) Run(contract *Contract, input []byte) (ret []byte, err error) { continue } - case CREATE: - var ( - value = stack.pop() - offset, size = stack.pop(), stack.pop() - input = mem.Get(offset.Int64(), size.Int64()) - gas = new(big.Int).Set(contract.Gas) - addr common.Address - ret []byte - suberr error - ) - contract.UseGas(contract.Gas) - ret, addr, suberr = self.env.Create(contract, input, gas, contract.Price, value) - if suberr != nil { - stack.push(new(big.Int)) - } else { - // gas < len(ret) * Createinstr.dataGas == NO_CODE - dataGas := big.NewInt(int64(len(ret))) - dataGas.Mul(dataGas, params.CreateDataGas) - if contract.UseGas(dataGas) { - self.env.Db().SetCode(addr, ret) - } else { - if params.IsHomestead(self.env.BlockNumber()) { - stack.push(new(big.Int)) - return nil, CodeStoreOutOfGasError - } - } - stack.push(addr.Big()) - } - case RETURN: offset, size := stack.pop(), stack.pop() ret := mem.GetPtr(offset.Int64(), size.Int64()) - return contract.Return(ret), nil + return ret, nil case SUICIDE: opSuicide(instruction{}, nil, self.env, contract, mem, stack) fallthrough case STOP: // Stop the contract - return contract.Return(nil), nil + return nil, nil } } } else { @@ -359,7 +323,6 @@ func calculateGasAndSize(env Environment, contract *Contract, caller ContractRef gas.Add(gas, stack.data[stack.len()-1]) if op == CALL { - //if env.Db().GetStateObject(common.BigToAddress(stack.data[stack.len()-2])) == nil { if !env.Db().Exist(common.BigToAddress(stack.data[stack.len()-2])) { gas.Add(gas, params.CallNewAccountGas) } @@ -392,7 +355,7 @@ func (self *Vm) RunPrecompiled(p *PrecompiledAccount, input []byte, contract *Co if contract.UseGas(gas) { ret = p.Call(input) - return contract.Return(ret), nil + return ret, nil } else { return nil, OutOfGasError } -- cgit